@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Rajdhani:wght@400;500;600;700&family=Orbitron:wght@400;500;600;700;800;900&display=swap');

@font-face {
  font-family: 'Pixel Intv';
  src: url('/data/Pixel Intv.otf') format('opentype'),
       url('/data/Pixel Intv.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  /* block (not swap): briefly hide text rather than flash a fallback that
     <input> elements won't repaint away until focused. Paired with a
     <link rel="preload"> in index.html so the block period is tiny. */
  font-display: block;
}

:root {
  --bg: #0a0a12;
  --bg2: #12121f;
  --bg3: #1a1a2e;
  --bg4: #252540;
  --accent: #00f0ff;
  --accent2: #ff00aa;
  --accent3: #aaff00;
  --accent4: #ffaa00;
  --text: #e0e0f0;
  --text2: #8888aa;
  --danger: #ff3366;
  --success: #33ff88;
  /* ── Board scaling system ── */
  --board-scale: 1;
  --card-w: calc(86px * var(--board-scale));
  --card-h: calc(120px * var(--board-scale));
  --zone-w: calc(68px * var(--board-scale));
  --zone-h: calc(95px * var(--board-scale));
  --zone-gap: calc(24px * var(--board-scale));
  --spacer-w: calc(68px * var(--board-scale));
  --sidebar-w: calc(76px * var(--board-scale));
  --board-font: calc(9px * var(--board-scale));
  --hand-min-h: calc(102px * var(--board-scale));
  --chat-col-w: 324px;
  /* ── Typography (pixel-art UI) ──
     One place to retune the whole interface. Pixel Intv is a readable
     pixel font (also used on cards); Press Start 2P is the chunky 8-bit
     display face. --font-display/-body alias the old Orbitron/Rajdhani
     roles so every existing rule flips to pixel at once. */
  --font-pixel: 'Press Start 2P', monospace;
  --font-display: 'Pixel Intv', 'Press Start 2P', monospace;
  --font-body: 'Pixel Intv', 'Press Start 2P', monospace;
  --font-orbit: var(--font-display); /* legacy alias still referenced in app-board */
}

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

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body), sans-serif;
  overflow: hidden;
  height: 100vh;
}

#root { height: 100vh; }

::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--bg2); }
::-webkit-scrollbar-thumb { background: var(--accent); border-radius: 3px; }

.pixel-font { font-family: 'Press Start 2P', monospace; }
.orbit-font { font-family: var(--font-display), sans-serif; }

/* ===== ANIMATIONS ===== */
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.6; } }
@keyframes glow {
  0%,100% { box-shadow: 0 0 5px var(--accent), 0 0 10px rgba(0,240,255,.25); }
  50% { box-shadow: 0 0 15px var(--accent), 0 0 30px rgba(0,240,255,.4); }
}
@keyframes slideIn { from { transform: translateY(20px); opacity:0; } to { transform: translateY(0); opacity:1; } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes flashSave {
  0%, 100% { border-color: #ff3366; color: #ff3366; background: rgba(255,51,102,.08); box-shadow: 0 0 4px rgba(255,51,102,.2); }
  50% { border-color: #ff1144; color: #fff; background: rgba(255,51,102,.45); box-shadow: 0 0 18px rgba(255,51,102,.5); }
}
@keyframes scanline {
  0% { background-position: 0 0; }
  100% { background-position: 0 4px; }
}

.animate-in { animation: fadeIn .3s ease-out; }
.animate-fade { animation: fadeIn .3s ease-out; }
.glow-border { animation: glow 2s ease-in-out infinite; }

/* ===== BUTTONS ===== */
.btn {
  font-family: var(--font-display), sans-serif;
  font-weight: 600;
  padding: 10px 20px;
  border: 2px solid var(--accent);
  background: rgba(0,240,255,.08);
  color: var(--accent);
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 1px;
  transition: all .2s;
  font-size: 13px;
}
.btn:hover { background: rgba(0,240,255,.18); box-shadow: 0 0 15px rgba(0,240,255,.25); }
.btn:active { transform: scale(.97); }
.btn:disabled { opacity: .3; cursor: not-allowed; }
.btn-danger { border-color: var(--danger); color: var(--danger); background: rgba(255,51,102,.08); }
.btn-danger:hover { background: rgba(255,51,102,.18); box-shadow: 0 0 15px rgba(255,51,102,.25); }
.btn-success { border-color: var(--success); color: var(--success); background: rgba(51,255,136,.08); }
.btn-success:hover { background: rgba(51,255,136,.18); box-shadow: 0 0 15px rgba(51,255,136,.25); }
.btn-accent2 { border-color: var(--accent2); color: var(--accent2); background: rgba(255,0,170,.08); }
.btn-accent2:hover { background: rgba(255,0,170,.18); box-shadow: 0 0 15px rgba(255,0,170,.25); }
.btn-gold { border-color: #ffd700; color: #ffd700; background: rgba(255,215,0,.10); }
.btn-gold:hover { background: rgba(255,215,0,.22); box-shadow: 0 0 18px rgba(255,215,0,.45); }
.btn-format-active {
  border-color: #ffd700 !important;
  color: #ffd700 !important;
  background: rgba(255,215,0,.2) !important;
  box-shadow: 0 0 12px rgba(255,215,0,.4), inset 0 0 8px rgba(255,215,0,.1);
  font-weight: 700;
}
.btn-big { padding: 16px 32px; font-size: 15px; }

/* Main-menu button hover emphasis. The generic `.btn:hover` above only
   swaps the `background` colour, but every main-menu button sets
   `background` inline (each has its own tint — pink, purple, gold, …),
   and an inline style outranks class-level `:hover`. The result was that
   Tutorial and Singleplayer had almost-invisible hovers while Play /
   Edit Deck / View Profile (which don't set background inline) lit up
   normally. Fix: scope an additive hover that uses glow + scale +
   outline + brightness — none of which touch `background`, so the
   effect works on top of whatever tint each button was born with.
   `currentColor` makes the glow track the button's own text/border hue. */
.menu-buttons .btn { transition: filter .15s, transform .15s, box-shadow .15s, outline-color .15s; }
.menu-buttons .btn:hover {
  filter: brightness(1.45) saturate(1.25);
  transform: translateY(-1px) scale(1.03);
  box-shadow:
    0 0 22px currentColor,
    inset 0 0 16px rgba(255, 255, 255, .10);
  text-shadow: 0 0 8px currentColor;
  outline: 2px solid currentColor;
  outline-offset: 1px;
}

.btn-flash-save {
  animation: flashSave .8s ease-in-out infinite !important;
  border-color: #ff3366 !important;
  color: #ff3366 !important;
}

/* ===== INPUTS ===== */
.input {
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--text);
  padding: 8px 12px;
  font-family: var(--font-body), sans-serif;
  font-size: 14px;
  outline: none;
  width: 100%;
}
.input:focus { border-color: var(--accent); box-shadow: 0 0 5px rgba(0,240,255,.2); }

.select {
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--text);
  padding: 8px 12px;
  font-family: var(--font-body), sans-serif;
  font-size: 14px;
  outline: none;
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238888aa'%3E%3Cpath d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  padding-right: 28px;
}
.select:focus { border-color: var(--accent); }

/* ── Dropdown-Listen ────────────────────────────────────────────────
   `<option>` erbt die Schrift NICHT vom `<select>`: die aufgeklappte
   Liste malt der Browser selbst und nimmt dafuer die auf dem `<option>`
   berechnete Schrift. Ohne eigene Regel faellt die auf die
   UA-Standardschrift zurueck — das geschlossene Feld sah also nach
   Pixel-Font aus, die geoeffnete Liste nicht (Al im Deckbuilder
   entdeckt, betraf aber JEDES Dropdown der App).

   Bewusst auf den ELEMENTEN statt auf den drei Klassen `.select`,
   `.db-filter-select` und `.input`: so stimmt auch jedes kuenftige
   Dropdown, ohne dass jemand daran denken muss. Hintergrund und Farbe
   werden mitgesetzt, damit die Liste zum dunklen Rest passt, soweit die
   Plattform das zulaesst. */
select,
select option,
select optgroup {
  font-family: var(--font-body), sans-serif;
}
select option,
select optgroup {
  background: var(--bg);
  color: var(--text);
}

/* ===== PANELS ===== */
.panel {
  background: var(--bg2);
  border: 1px solid var(--bg4);
  padding: 16px;
}

/* ===== CARD COMPONENTS ===== */
.card-slot {
  width: var(--card-w);
  height: var(--card-h);
  border: 2px dashed var(--bg4);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all .2s;
  position: relative;
  font-size: 9px;
  text-align: center;
  color: var(--text2);
  background: var(--bg);
  flex-shrink: 0;
}
.card-slot:hover { border-color: var(--accent); background: rgba(0,240,255,.04); }

.card-mini {
  width: var(--card-w);
  height: var(--card-h);
  border: 2px solid var(--bg4);
  padding: 3px;
  cursor: pointer;
  transition: all .15s;
  background: var(--bg2);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-size: 8px;
  overflow: hidden;
  position: relative;
  user-select: none;
  flex-shrink: 0;
}
.card-mini:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,.5);
}
.card-mini.dimmed { opacity: .35; }
.card-mini.drag-over { border-color: var(--accent3); box-shadow: 0 0 12px rgba(170,255,0,.4); }
.card-mini-cover {
  border-color: #ffd700 !important;
  box-shadow: 0 0 8px rgba(255,215,0,.5), 0 0 16px rgba(255,215,0,.25);
  animation: coverCardGlow 2s ease-in-out infinite;
}
.card-mini-cover::after {
  content: '⭐';
  position: absolute;
  top: 2px;
  left: 2px;
  font-size: 10px;
  filter: drop-shadow(0 0 3px rgba(255,215,0,.8));
  z-index: 3;
  pointer-events: none;
  animation: coverStarPulse 1.5s ease-in-out infinite;
}
@keyframes coverCardGlow {
  0%, 100% { box-shadow: 0 0 8px rgba(255,215,0,.5), 0 0 16px rgba(255,215,0,.25); }
  50% { box-shadow: 0 0 12px rgba(255,215,0,.7), 0 0 24px rgba(255,215,0,.4); }
}
@keyframes coverStarPulse {
  0%, 100% { opacity: .8; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.15); }
}
.card-mini .card-name { font-weight: 700; font-size: 8px; line-height: 1.15; word-break: break-word; }
.card-mini .card-type { font-size: 7px; color: var(--text2); text-transform: uppercase; letter-spacing: .3px; }
.card-mini .card-stats { font-size: 7px; display: flex; gap: 3px; flex-wrap: wrap; }
.card-mini .card-count {
  position: absolute; top: 2px; right: 3px;
  font-size: 8px; font-weight: 700;
}

/* Type indicators */
/* Type classes kept for JS reference but no visual border */

/* ===== FOIL SHINE EFFECT ===== */
@keyframes foilSweepOnce {
  0% { transform: translateX(var(--band-from)) rotate(22deg); }
  100% { transform: translateX(var(--band-to)) rotate(22deg); }
}
@keyframes foilShimmer {
  0%, 100% { opacity: .10; background-position: 0% 0%; }
  25% { opacity: .25; background-position: 100% 100%; }
  50% { opacity: .12; background-position: 50% 0%; }
  75% { opacity: .28; background-position: 0% 100%; }
}
@keyframes foilBorderPulse {
  0%, 100% { border-color: rgba(255,215,0,.5); box-shadow: 0 0 8px rgba(255,215,0,.25); }
  33% { border-color: rgba(255,120,255,.5); box-shadow: 0 0 8px rgba(255,120,255,.25); }
  66% { border-color: rgba(100,200,255,.5); box-shadow: 0 0 8px rgba(100,200,255,.25); }
}
@keyframes sparkle {
  0% { transform: scale(0) rotate(0deg); opacity: 0; }
  15% { transform: scale(1.2) rotate(90deg); opacity: 1; }
  30% { transform: scale(0.8) rotate(180deg); opacity: .7; }
  45% { transform: scale(1) rotate(270deg); opacity: .9; }
  60% { transform: scale(0) rotate(360deg); opacity: 0; }
  100% { transform: scale(0) rotate(360deg); opacity: 0; }
}
@keyframes sparkleGlow {
  0%, 60%, 100% { box-shadow: none; }
  20% { box-shadow: 0 0 6px 2px currentColor; }
}
.foil-secret-rare {
  animation: foilBorderPulse 4s ease-in-out infinite !important;
}
.foil-secret-rare:hover {
  border-color: rgba(255,215,0,.9) !important;
  box-shadow: 0 0 20px rgba(255,215,0,.5), 0 0 40px rgba(255,120,255,.25), 0 4px 12px rgba(0,0,0,.5) !important;
}

/* ===== DIAMOND RARE FOIL ===== */
@keyframes foilDiamondBorderPulse {
  0%, 100% { border-color: rgba(120,200,255,.6); box-shadow: 0 0 8px rgba(120,200,255,.3); }
  25% { border-color: rgba(200,230,255,.7); box-shadow: 0 0 10px rgba(200,230,255,.35); }
  50% { border-color: rgba(255,255,255,.6); box-shadow: 0 0 12px rgba(255,255,255,.3); }
  75% { border-color: rgba(160,220,255,.7); box-shadow: 0 0 10px rgba(160,220,255,.35); }
}
@keyframes foilDiamondShimmer {
  0%, 100% { opacity: .12; background-position: 0% 0%; }
  25% { opacity: .30; background-position: 100% 100%; }
  50% { opacity: .15; background-position: 50% 0%; }
  75% { opacity: .35; background-position: 0% 100%; }
}
.foil-diamond-rare {
  animation: foilDiamondBorderPulse 3s ease-in-out infinite !important;
}
.foil-diamond-rare:hover {
  border-color: rgba(180,230,255,.95) !important;
  box-shadow: 0 0 20px rgba(120,200,255,.6), 0 0 40px rgba(200,240,255,.3), 0 4px 12px rgba(0,0,0,.5) !important;
}
.foil-iridescent-diamond {
  position: absolute; inset: 0;
  background: linear-gradient(135deg,
    rgba(120,200,255,.12) 0%, rgba(200,240,255,.1) 15%,
    rgba(255,255,255,.14) 30%, rgba(160,220,255,.1) 45%,
    rgba(200,230,255,.12) 60%, rgba(140,200,255,.1) 75%,
    rgba(255,255,255,.12) 90%
  );
  background-size: 300% 300%;
  animation: foilDiamondShimmer 4s ease-in-out infinite;
  animation-delay: var(--shimmer-offset, 0s);
  mix-blend-mode: screen;
}
.foil-shine-overlay {
  position: absolute; inset: 0; overflow: hidden;
  pointer-events: none; z-index: 2; border-radius: 1px;
}
/* JS-spawned shine band — plays once then removed */
.foil-band {
  position: absolute;
  top: -90%; left: 0;
  width: var(--band-w, 30%);
  height: 280%;
  opacity: var(--band-o, .4);
  animation: foilSweepOnce var(--band-dur, 1.5s) linear forwards;
  will-change: transform;
}
.foil-iridescent {
  position: absolute; inset: 0;
  background: linear-gradient(135deg,
    rgba(255,160,60,.1) 0%, rgba(255,60,200,.09) 15%,
    rgba(60,160,255,.12) 30%, rgba(160,255,60,.09) 45%,
    rgba(255,60,160,.1) 60%, rgba(60,255,255,.09) 75%,
    rgba(255,200,60,.1) 90%
  );
  background-size: 300% 300%;
  animation: foilShimmer 5s ease-in-out infinite;
  animation-delay: var(--shimmer-offset, 0s);
  mix-blend-mode: screen;
}
.foil-sparkle {
  position: absolute; width: 4px; height: 4px; border-radius: 50%;
  animation: sparkle var(--sp-dur, 2.5s) ease-in-out infinite,
             sparkleGlow var(--sp-dur, 2.5s) ease-in-out infinite;
  animation-delay: var(--sp-delay, 0s);
}
.foil-sparkle::before, .foil-sparkle::after {
  content: ''; position: absolute; background: currentColor;
}
.foil-sparkle::before {
  left: 50%; top: -40%; width: 1px; height: 180%;
  transform: translateX(-50%);
}
.foil-sparkle::after {
  top: 50%; left: -40%; height: 1px; width: 180%;
  transform: translateY(-50%);
}
/* Diamond sparkles: bigger, brighter */
.foil-shine-diamond .foil-sparkle {
  width: 5px; height: 5px;
}
.foil-shine-diamond .foil-sparkle::before {
  width: 1.5px; height: 220%;
  top: -60%;
}
.foil-shine-diamond .foil-sparkle::after {
  height: 1.5px; width: 220%;
  left: -60%;
}

/* ===== TOOLTIP (full gallery column) ===== */
.tooltip {
  position: fixed;
  z-index: 9999;
  background: var(--bg2);
  border-left: 1px solid var(--accent);
  border-right: 1px solid var(--accent);
  padding: 0;
  font-size: 14px;
  pointer-events: none;
  box-shadow: -4px 0 20px rgba(0,0,0,.5), 4px 0 20px rgba(0,0,0,.5);
}


/* ===== DECK BUILDER FILTER (bigger) ===== */
.db-filter-input {
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--text);
  padding: 7px 10px;
  font-family: var(--font-body), sans-serif;
  font-size: 15px;
  outline: none;
  width: 100%;
}
.db-filter-input:focus { border-color: var(--accent); box-shadow: 0 0 5px rgba(0,240,255,.2); }
.db-filter-select {
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--text);
  padding: 7px 10px;
  font-family: var(--font-body), sans-serif;
  font-size: 15px;
  outline: none;
  cursor: pointer;
  width: 100%;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238888aa'%3E%3Cpath d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  padding-right: 28px;
}
.db-filter-select:focus { border-color: var(--accent); }

/* ===== CONTEXT MENU ===== */
.ctx-menu {
  position: fixed;
  z-index: 9500;
  background: var(--bg2);
  border: 1px solid var(--accent);
  box-shadow: 0 4px 20px rgba(0,0,0,.8);
  min-width: 160px;
}
.ctx-menu-item {
  padding: 8px 14px;
  cursor: pointer;
  font-size: 13px;
  font-family: var(--font-body), sans-serif;
  font-weight: 600;
  transition: background .1s;
  display: flex;
  align-items: center;
  gap: 8px;
}
.ctx-menu-item:hover { background: rgba(0,240,255,.12); }
.ctx-menu-item.disabled { opacity: .3; cursor: not-allowed; }
.ctx-menu-item.disabled:hover { background: none; }

/* ===== DECK SECTION ===== */
.deck-section {
  background: var(--bg3);
  border: 1px solid var(--bg4);
  margin-bottom: 8px;
}
.deck-section-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--bg4);
  font-family: var(--font-display), sans-serif;
  font-size: 11px;
  font-weight: 700;
}
.deck-section-body {
  padding: 2px;
  display: grid;
  grid-template-columns: repeat(15, 1fr);
  gap: 0px;
  min-height: 50px;
}
.deck-section-body .card-mini {
  width: 100%;
  height: auto;
  aspect-ratio: 5 / 7;
}
.deck-section-body .card-slot {
  width: 100%;
  height: auto;
  aspect-ratio: 5 / 7;
}
.deck-section-body .deck-empty-msg {
  grid-column: 1 / -1;
}
.deck-section-body.drop-target {
  outline: 2px dashed var(--accent3);
  outline-offset: -2px;
  background: rgba(170,255,0,.04);
}

/* Heroes section in the deck editor: 3 columns that grow/shrink with the
   center panel. Each slot holds a big hero card (flex 2) and a vertical
   stack of two ability cards (flex 1). Children use `aspect-ratio: 5/7`
   so heights derive from the available width — the 2:1 flex split makes
   the ability stack end up the same height as the hero card. */
.db-hero-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  padding: 12px;
  align-items: start;
}
/* HeroSlot wraps each column in a plain div — allow it to shrink below
   intrinsic content width so the 1fr columns actually honor their share
   on narrower windows. */
.db-hero-row > * { min-width: 0; }
.db-hero-slot {
  display: flex;
  flex-direction: row;
  gap: 6px;
  align-items: stretch;
  min-width: 0;
  padding: 4px;
  border-radius: 4px;
}
.db-hero-slot.drop-target { outline: 2px solid var(--accent); }
.db-hero-card {
  flex: 2 1 0;
  min-width: 0;
  position: relative;
  aspect-ratio: 5 / 7;
}
.db-hero-card > .card-mini,
.db-hero-card > .card-slot {
  width: 100%;
  height: 100%;
  aspect-ratio: unset;
  font-size: 12px;
}
.db-hero-abilities {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.db-hero-abilities > .card-mini,
.db-hero-abilities > .ability-slot {
  flex: 1 1 0;
  width: 100%;
  height: auto;
  min-height: 0;
  aspect-ratio: 5 / 7;
  font-size: 10px;
}

/* ===== MODALS ===== */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.result-overlay-fade {
  animation: result-fade-in .6s ease-out;
}
/* Speech-bubble SHOUTING: all-caps-word letters bob slightly up/down so the
   CPU reads as yelling. Per-letter animation-delay (set inline) phase-shifts
   adjacent letters into a trembling wave. */
@keyframes cpu-shout-bob {
  0%, 100% { transform: translateY(-1.5px); }
  50%      { transform: translateY(1.5px); }
}
.cpu-shout-letter {
  display: inline-block;
  animation: cpu-shout-bob 0.5s ease-in-out infinite;
}
.result-overlay-fading {
  animation: result-fade-out .7s ease-in forwards;
  pointer-events: none;
}
@keyframes result-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes result-fade-out { from { opacity: 1; } to { opacity: 0; } }
.modal {
  background: var(--bg2);
  border: 2px solid var(--accent);
  padding: 24px;
  min-width: 340px;
  max-width: 500px;
  max-height: 80vh;
  overflow-y: auto;
}

/* ===== TABS ===== */
.tab-bar { display: flex; gap: 0; border-bottom: 2px solid var(--bg4); }
.tab {
  padding: 8px 16px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  font-weight: 600;
  color: var(--text2);
  transition: all .2s;
  font-family: var(--font-display), sans-serif;
  font-size: 11px;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }

/* ===== BADGE ===== */
.badge {
  display: inline-block;
  padding: 2px 6px;
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .5px;
}

/* ===== SCREEN LAYOUTS ===== */
.screen-center {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background: linear-gradient(135deg, #0a0a12 0%, #1a0a2e 50%, #0a1a1e 100%);
}

/* Auth screen: the 1920x1080 title artwork sits behind the login panel.
   `100% 100%` squashes/stretches the piece to fully cover the viewport
   — no letterbox bars at any aspect ratio, at the cost of distorting
   the image on non-16:9 windows. The gradient fallback stays for the
   brief pre-load frame. The login panel has its own dark backdrop so
   the form stays readable on top of the stretched image. */
.auth-screen {
  /* The still title.png is replaced by <AnimatedTitleBackdrop/> (rendered as
     the first child). This base colour matches the animated scene's letterbox
     tone and shows only for the brief moment before the layer art loads. */
  background: #1a1020;
  overflow: hidden;
}

/* ═══ Animated title backdrop (see AnimatedTitleBackdrop in app-screens) ═══
   Faithful recreation of data/animated_screen. Sits behind the auth panel
   (z-index:0; panel is z-index:2). Motion knobs mirror the reference:
   --pf parallax, --shake shake amount, --ef explosion light, --tH/--tV
   tether amplitudes, --sAmp core-scale amplitude. */
.anim-backdrop {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #1a1020;
  z-index: 0;
  pointer-events: none;
  --pf: 1;
  --shake: 1;
  --ef: 1;
  /* Tether amplitudes as % of screen (9px / 4px on the 1920x1080 stage) so
     the clash rig scales with the viewport. --sAmp is a unitless scale. */
  --tH: 0.469vw;
  --tV: 0.37vh;
  --sAmp: 0.021;
}
.anim-shake {
  position: absolute;
  inset: 0;
  animation: ab-shake 10s steps(1, end) infinite;
  will-change: transform;
}
/* Front cats overlay — a separate layer ABOVE the auth panel (z-index:2) so
   the cats fly over the login form. pointer-events:none keeps the form
   clickable. Shares the scene's motion knobs so floats/shake match. */
.anim-cats-front {
  position: absolute;
  inset: 0;
  z-index: 3;
  overflow: hidden;
  pointer-events: none;
  --pf: 1;
  --shake: 1;
}
/* Login panel shake — same ab-shake clock as the backdrop, so the dialogue
   jolts in sync with the laser impacts. On its own wrapper so the panel's
   .animate-in entrance transform is preserved. */
.auth-panel-shake {
  --shake: 1;
  animation: ab-shake 10s steps(1, end) infinite;
  will-change: transform;
}
.anim-bloom {
  position: absolute;
  left: 52%;
  top: 42%;
  width: 860px;
  height: 860px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(255,228,150,.4), rgba(255,152,72,.12) 22%, rgba(255,96,74,.03) 40%, transparent 56%);
  filter: blur(9px);
  mix-blend-mode: screen;
  pointer-events: none;
  animation: ab-glowpulse 10s ease-in-out infinite;
  will-change: transform, opacity;
}
.anim-flash {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 56% 41%, #fff, rgba(255,240,200,.55) 30%, transparent 58%);
  mix-blend-mode: screen;
  pointer-events: none;
  animation: ab-flashburst 10s steps(1, end) infinite;
}
.anim-vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(circle at 50% 44%, transparent 40%, rgba(20,8,20,.4) 80%, rgba(15,5,18,.64) 100%);
  animation: ab-vignettepulse 6s ease-in-out infinite;
}
/* ── Lightning (see .anim-lightning in AnimatedTitleBackdrop) ──
   Cool blue flashes + jagged bolts. ab-strike (10s) fires ON the two shake
   impact beats; ab-strike-ambient (13s, off the shake clock) adds occasional
   off-beat strikes. Screen-blended so it reads as additive light. */
.anim-lightning {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  mix-blend-mode: screen;
}
.anim-lflash {
  position: absolute;
  inset: 0;
  opacity: 0;
  will-change: opacity;
}
.anim-lflash-impact {
  background: radial-gradient(circle at 46% 26%, rgba(210,232,255,.95), rgba(150,190,255,.35) 42%, transparent 72%);
  animation: ab-strike 10s linear infinite;
}
.anim-lflash-ambient {
  background: radial-gradient(circle at 62% 22%, rgba(200,224,255,.9), rgba(150,185,255,.3) 40%, transparent 70%);
  animation: ab-strike-ambient 13s linear infinite;
}
.anim-bolt {
  position: absolute;
  top: -2%;
  height: 62%;
  opacity: 0;
  will-change: opacity;
  filter: drop-shadow(0 0 5px #cfe9ff) drop-shadow(0 0 13px #7fb4ff);
}
.anim-bolt polyline {
  fill: none;
  stroke: #eaf6ff;
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.anim-bolt-a { left: 15%; width: 9%;  animation: ab-strike 10s linear infinite; }
.anim-bolt-b { right: 19%; width: 8%; animation: ab-strike 10s linear infinite; }
.anim-bolt-c { left: 53%; width: 8%;  animation: ab-strike-ambient 13s linear infinite; }
@keyframes ab-strike {
  0%, 100% { opacity: 0; }
  0.6%  { opacity: 0; }
  1%    { opacity: 1; }
  1.8%  { opacity: .12; }
  2.6%  { opacity: .85; }
  4%    { opacity: 0; }
  49.5% { opacity: 0; }
  50%   { opacity: 1; }
  50.8% { opacity: .18; }
  51.6% { opacity: .8; }
  53%   { opacity: 0; }
}
@keyframes ab-strike-ambient {
  0%, 100% { opacity: 0; }
  29.6% { opacity: 0; }
  30%   { opacity: .9; }
  30.7% { opacity: .12; }
  31.5% { opacity: .72; }
  32.6% { opacity: 0; }
  70.8% { opacity: 0; }
  71.2% { opacity: .68; }
  72.2% { opacity: 0; }
}
@keyframes ab-floaty {
  0%   { transform: translate(0,0) rotate(0deg); }
  50%  { transform: translate(calc(var(--mx,0vw) * var(--pf,1)), calc(var(--my,-0.926vh) * var(--pf,1))) rotate(calc(var(--mr,0deg) * var(--pf,1))); }
  100% { transform: translate(0,0) rotate(0deg); }
}
@keyframes ab-jiggleTether {
  0%, 100% { transform: translate(0, 0); }
  50%  { transform: translate(calc(var(--tH,0.469vw) * var(--pf,1)), calc(var(--tV,0.37vh) * var(--pf,1))); }
}
@keyframes ab-broghanTether {
  0%, 100% { transform: translate(0, 0); }
  50%  { transform: translate(calc(var(--tH,0.469vw) * -1 * var(--pf,1)), calc(var(--tV,0.37vh) * var(--pf,1))); }
}
@keyframes ab-coreTether {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(0, calc(var(--tV,0.37vh) * var(--pf,1))) scale(calc(1 - var(--sAmp,0.021) * var(--pf,1))); }
}
@keyframes ab-shake {
  0%, 6%, 49%, 56%, 100% { transform: translate(0, 0); }
  1%  { transform: translate(calc(-0.417vw * var(--shake,1)), calc(0.463vh * var(--shake,1))); }
  2%  { transform: translate(calc(0.365vw * var(--shake,1)), calc(-0.556vh * var(--shake,1))); }
  3%  { transform: translate(calc(-0.26vw * var(--shake,1)), calc(0.37vh * var(--shake,1))); }
  4%  { transform: translate(calc(0.156vw * var(--shake,1)), calc(-0.278vh * var(--shake,1))); }
  50% { transform: translate(calc(0.469vw * var(--shake,1)), calc(-0.556vh * var(--shake,1))); }
  51% { transform: translate(calc(-0.417vw * var(--shake,1)), calc(0.556vh * var(--shake,1))); }
  52% { transform: translate(calc(0.313vw * var(--shake,1)), calc(-0.37vh * var(--shake,1))); }
  53% { transform: translate(calc(-0.208vw * var(--shake,1)), calc(0.278vh * var(--shake,1))); }
  54% { transform: translate(calc(0.104vw * var(--shake,1)), calc(-0.185vh * var(--shake,1))); }
}
@keyframes ab-glowpulse {
  0%   { opacity: .6;  transform: translate(-50%,-50%) scale(1.02); }
  4%   { opacity: 1;   transform: translate(-50%,-50%) scale(1.18); }
  16%  { opacity: .4;  transform: translate(-50%,-50%) scale(.98); }
  42%  { opacity: .52; transform: translate(-50%,-50%) scale(1.02); }
  50%  { opacity: 1;   transform: translate(-50%,-50%) scale(1.2); }
  62%  { opacity: .4;  transform: translate(-50%,-50%) scale(.98); }
  100% { opacity: .6;  transform: translate(-50%,-50%) scale(1.02); }
}
@keyframes ab-flashburst {
  0%, 49%, 56%, 100% { opacity: 0; }
  1%  { opacity: calc(.45 * var(--shake,1)); }
  5%  { opacity: 0; }
  50% { opacity: calc(.4 * var(--shake,1)); }
  54% { opacity: 0; }
}
@keyframes ab-vignettepulse {
  0%, 100% { opacity: .5; }
  50%      { opacity: .72; }
}
@keyframes ab-emberrise {
  0%   { transform: translateY(1.852vh) translateX(0); opacity: 0; }
  8%   { opacity: .95; }
  78%  { opacity: .7; }
  100% { transform: translateY(-112vh) translateX(var(--drift, 0vw)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .anim-shake, .anim-shake > *, .anim-shake > * > *,
  .anim-bloom, .anim-flash, .anim-vignette, .auth-panel-shake,
  .anim-lflash, .anim-bolt { animation: none !important; }
}
.auth-screen .panel {
  background: rgba(10, 10, 20, .82);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 0 40px rgba(0, 0, 0, .75), 0 0 60px rgba(170, 136, 255, .15);
  border: 1px solid rgba(170, 136, 255, .35);
}

/* ── Password field with show/hide toggle ── */
/* Block (not flex): the eye toggle is absolutely positioned, so no flex is
   needed — and as a plain block the input lays out at a true width:100%,
   matching the other (unwrapped) inputs exactly. */
.pw-field { position: relative; display: block; }
.pw-field .input { width: 100%; padding-right: 38px; }
.pw-toggle {
  position: absolute; right: 1px; top: 1px; bottom: 1px; width: 34px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: none; cursor: pointer;
  color: var(--text2); transition: color .15s;
}
.pw-toggle:hover { color: var(--accent); }

/* ── Auth screen messages, links & steps ── */
.auth-msg { font-size: 12px; line-height: 1.4; text-align: left; }
.auth-err { color: var(--danger); }
.auth-ok { color: var(--success); }
.auth-link {
  font-size: 12px; color: var(--accent); cursor: pointer;
  letter-spacing: .5px; user-select: none; transition: opacity .15s, text-shadow .15s;
}
.auth-link:hover { text-shadow: 0 0 8px var(--accent); }
.auth-link.disabled { color: var(--text2); cursor: default; text-shadow: none; opacity: .7; }
.auth-row { display: flex; justify-content: space-between; align-items: center; margin-top: 2px; }
.auth-fine { font-size: 11.5px; color: var(--text2); line-height: 1.5; text-align: left; }

/* Native "Sign in with Google" button for the desktop shell — mirrors the
   GIS filled_black pill the browser renders, since GIS's own button can't be
   used inside Electron. */
.google-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  width: 280px; height: 44px; padding: 0 16px;
  background: #131314; color: #e3e3e3;
  border: 1px solid #5f6368; border-radius: 22px;
  font-family: 'Roboto', system-ui, sans-serif; font-size: 14px; font-weight: 500;
  cursor: pointer; transition: background .15s, border-color .15s;
}
.google-btn:hover:not(:disabled) { background: #1f2023; border-color: #8a8f93; }
.google-btn:disabled { opacity: .6; cursor: default; }
.google-btn svg { flex: 0 0 auto; }
.auth-step-title {
  font-family: var(--font-display), sans-serif; font-weight: 700; font-size: 15px;
  color: var(--text); letter-spacing: 1px; text-transform: uppercase; margin-bottom: 2px;
}
.auth-code {
  text-align: center; font-size: 22px !important; letter-spacing: 10px;
  font-weight: 700; padding-left: 12px;
}

.screen-full {
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.top-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
  background: var(--bg2);
  border-bottom: 1px solid var(--bg4);
  flex-shrink: 0;
  position: relative; /* anchor for the centered title */
}
/* Submenu/screen title is always centered at the top, independent of the
   back button on the left and any controls on the right. (The in-game board
   header centers its own title via inline styles, which still take priority.) */
.top-bar > h2 {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  white-space: nowrap;
  pointer-events: none; /* never block the buttons/controls underneath */
}
/* Black outline on screen/submenu titles (and the menu brand + modal
   titles via .title-outline). The stroke is painted behind the fill so the
   glyphs stay full-weight instead of thinning. */
.top-bar > h2,
.title-outline {
  -webkit-text-stroke: 2px #000;
  paint-order: stroke fill;
}
/* Underline the screen/submenu/pop-up titles (but not the PIXEL PARTIES
   brand logo). Drawn as a border in the player's colour rather than
   text-decoration, because the black -webkit-text-stroke can bleed onto a
   text-decoration underline and make it look black. */
.top-bar > h2,
.title-outline:not(.menu-title) {
  text-decoration: none;
  border-bottom: 3px solid var(--player-color);
  padding-bottom: 4px;
}

/* ===== PIXEL PARTIES brand logo (main menu) =====
   Uses data/logo.png (a pixel-art wordmark). Pixelated scaling keeps the chunky
   pixels crisp; the drop-shadow lifts it off the scrolling card-wall behind. */
.pp-logo-stack {
  position: relative;
  display: inline-block;
  line-height: 0;
  /* Contain the multiply blend to this stack (image only), not the menu bg. */
  isolation: isolate;
  filter: drop-shadow(0 4px 12px rgba(0,0,0,.7));
}
.pp-logo-img {
  display: block;
  height: 92px;
  width: auto;
  /* Shrink to fit short windows; width re-derives to keep the aspect ratio. */
  max-height: 24vh;
  image-rendering: pixelated;
}
/* Player-colour gradient applied ONLY to the white outline of the wordmark.
   data/logo-mask-white.png is a derived mask (only the logo's near-white pixels
   are opaque), so the gradient lands solely on the letter border — the gray
   letter faces and dark pixels keep their original colour. */
.pp-logo-tint {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(120deg,
    color-mix(in srgb, var(--player-color, #00f0ff) 35%, #ffffff) 0%,
    var(--player-color, #00f0ff) 50%,
    color-mix(in srgb, var(--player-color, #00f0ff) 55%, #000000) 100%);
  -webkit-mask: url(/data/logo-mask-white.png) center / 100% 100% no-repeat;
  mask: url(/data/logo-mask-white.png) center / 100% 100% no-repeat;
}
/* Decorative pixel particles drifting AROUND the logo. The layer extends
   past the wordmark (negative inset) and sits BEHIND the logo image
   (z-index:-1, contained by the stack's `isolation:isolate`), so over-logo
   particles tuck behind the crisp letters while the rest haloes the edges.
   Cosmetic only — pointer-events:none. */
.pp-logo-particles {
  position: absolute;
  inset: -60px -92px;
  z-index: -1;
  pointer-events: none;
}
.pp-logo-particle {
  position: absolute;
  width: var(--p-size, 4px);
  height: var(--p-size, 4px);
  background: var(--p-color, var(--player-color, #00f0ff));
  box-shadow:
    0 0 8px var(--p-color, var(--player-color, #00f0ff)),
    0 0 16px var(--p-color, var(--player-color, #00f0ff));
  image-rendering: pixelated;
  opacity: 0;
  transform: translate(0, 0) scale(.6);
  animation: pp-particle-float var(--p-dur, 3.5s) ease-in-out var(--p-delay, 0s) infinite;
  will-change: transform, opacity;
}
@keyframes pp-particle-float {
  0%   { opacity: 0; transform: translate(0, 0) scale(.55); }
  20%  { opacity: var(--p-max, .85); }
  50%  { transform: translate(var(--p-dx, 0), var(--p-dy, -12px)) scale(1); }
  80%  { opacity: var(--p-max, .85); }
  100% { opacity: 0; transform: translate(calc(var(--p-dx, 0) * 1.7), calc(var(--p-dy, -12px) * 1.9)) scale(.55); }
}
@media (prefers-reduced-motion: reduce) {
  .pp-logo-particle { animation: none; opacity: calc(var(--p-max, .85) * .6); transform: none; }
}

@media (max-width: 900px) {
  .pp-logo-img { height: 46px; }
  .pp-logo-particles { inset: -34px -50px; }
}

/* ===== DECK LIST ===== */
.deck-list-item {
  padding: 8px 10px;
  cursor: pointer;
  border-left: 3px solid transparent;
  font-size: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: background .15s;
}
.deck-list-item:hover { background: rgba(255,255,255,.03); }
.deck-list-item.active {
  border-left-color: var(--accent);
  background: rgba(0,240,255,.06);
}
.deck-list-item.deck-list-sample {
  opacity: .45;
  font-style: italic;
}
.deck-list-item.deck-list-sample:hover { opacity: .65; }
.deck-list-item.deck-list-sample.active {
  opacity: .8;
  border-left-color: var(--accent3);
  background: rgba(0,240,255,.04);
}

/* ===== ROOM CARD ===== */
.room-card {
  padding: 10px 12px;
  background: var(--bg3);
  border: 1px solid var(--bg4);
  margin-bottom: 6px;
  cursor: pointer;
  transition: all .2s;
}
.room-card:hover { border-color: var(--accent); }

/* ===== LEADERBOARD ===== */
/* Top-10 ranked players panel on the multiplayer screen. Each row is a
   tight three-column flex layout: rank • [optional crown] • name (grows
   to fill) • ELO. Tier classes (gold/silver/bronze) decorate the row
   for the medal positions; rank 4–10 render their selected profile
   color via inline style only. Names are inline-styled (server returns
   the player's color) so the tier color override naturally wins via
   later-CSS-rule precedence on the medal tiers. */
.leaderboard-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  margin-bottom: 4px;
  background: var(--bg3);
  border: 1px solid var(--bg4);
  font-size: 11px;
  transition: border-color .15s ease, background .15s ease;
}
.leaderboard-row:hover { border-color: var(--accent4); }
.leaderboard-rank {
  font-family: var(--font-display), monospace;
  font-weight: 700;
  font-size: 10px;
  color: var(--text2);
  min-width: 26px;
}
.leaderboard-name {
  flex: 1;
  font-family: var(--font-body), sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: .3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.leaderboard-elo {
  font-family: var(--font-display), monospace;
  font-weight: 700;
  font-size: 12px;
  color: var(--text);
  min-width: 44px;
  text-align: right;
}
.leaderboard-crown {
  font-size: 16px;
  line-height: 1;
}
/* Smaller silver / bronze crowns vs the rank-1 gold crown to make the
   visual hierarchy obvious. The gold crown is full-size; silver
   shrinks to 13px; bronze omits the crown by spec but the medal
   emoji `🥉` doubles as a crown-like marker, kept at 12px. */
.leaderboard-crown-gold   { font-size: 18px; filter: drop-shadow(0 0 4px rgba(255,210,74,.6)); }
.leaderboard-crown-silver { font-size: 13px; filter: drop-shadow(0 0 3px rgba(216,216,230,.5)); }
.leaderboard-crown-bronze { font-size: 12px; filter: drop-shadow(0 0 3px rgba(205,127,50,.5)); }

/* Tier styling. Gold (rank 1) gets a subtle golden gradient line under
   the name plus a faint glow on the row; silver (rank 2) gets a softer
   silver glow; bronze (rank 3) gets a copper-tinted glow. Ranks 4–10
   reuse the default row styling and rely on the player's own profile
   color for the name. */
.leaderboard-row-gold {
  background: linear-gradient(90deg, rgba(255,210,74,.16) 0%, rgba(255,210,74,.04) 60%, transparent 100%);
  border-color: rgba(255,210,74,.55);
  box-shadow: 0 0 12px rgba(255,210,74,.18), inset 0 0 8px rgba(255,210,74,.06);
}
.leaderboard-row-gold .leaderboard-name {
  text-shadow: 0 0 4px rgba(255,210,74,.5), 0 0 8px rgba(255,210,74,.25);
  background-image: linear-gradient(90deg, #ffd24a 0%, #ffae3b 50%, #ffd24a 100%);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent !important;
}
.leaderboard-row-silver {
  background: linear-gradient(90deg, rgba(216,216,230,.14) 0%, rgba(216,216,230,.03) 60%, transparent 100%);
  border-color: rgba(216,216,230,.45);
  box-shadow: 0 0 8px rgba(216,216,230,.14);
}
.leaderboard-row-silver .leaderboard-name {
  text-shadow: 0 0 3px rgba(216,216,230,.45);
}
.leaderboard-row-bronze {
  background: linear-gradient(90deg, rgba(205,127,50,.14) 0%, rgba(205,127,50,.03) 60%, transparent 100%);
  border-color: rgba(205,127,50,.45);
  box-shadow: 0 0 6px rgba(205,127,50,.14);
}
.leaderboard-row-bronze .leaderboard-name {
  text-shadow: 0 0 3px rgba(205,127,50,.4);
}

/* ===== HERO SLOT ===== */
.hero-column {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
}
.ability-slot {
  width: 80px;
  padding: 4px;
  border: 1px solid var(--bg4);
  background: var(--bg);
  font-size: 8px;
  text-align: center;
  min-height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ===== PROFILE CARD ===== */
@keyframes profileGlow {
  0%, 100% { box-shadow: 0 0 30px var(--rank-glow, rgba(0,240,255,.2)), 0 2px 40px rgba(0,0,0,.6); }
  50% { box-shadow: 0 0 50px var(--rank-glow, rgba(0,240,255,.3)), 0 2px 40px rgba(0,0,0,.6); }
}
@keyframes avatarPulse {
  0%, 100% { box-shadow: 0 0 20px var(--rank-glow), 0 0 40px var(--rank-glow), inset 0 0 15px var(--rank-glow); }
  50% { box-shadow: 0 0 30px var(--rank-glow), 0 0 60px var(--rank-glow), inset 0 0 20px var(--rank-glow); }
}
@keyframes badgeShine {
  0% { background-position: -100% 0; }
  100% { background-position: 200% 0; }
}

/* Fullscreen two-column layout */
.profile-layout {
  flex: 1;
  display: flex;
  gap: 0;
  overflow: hidden;
}

/* Left column — player identity */
.profile-identity-col {
  width: 340px;
  min-width: 300px;
  flex-shrink: 0;
  border-right: 1px solid var(--bg4);
  background: linear-gradient(180deg, var(--bg2) 0%, var(--bg3) 50%, var(--bg2) 100%);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

.profile-identity-panel {
  padding: 28px 24px 20px;
  display: flex;
  flex-direction: column;
  flex: 1;
  position: relative;
}
.profile-identity-panel::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 160px;
  background: linear-gradient(180deg, rgba(255,255,255,.02) 0%, transparent 100%);
  pointer-events: none;
}

/* Right column — stats & customization */
.profile-right-col {
  flex: 1;
  overflow-y: auto;
  padding: 20px 28px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.profile-hero-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  position: relative;
  z-index: 1;
}

.profile-avatar-frame {
  width: 130px;
  height: 130px;
  border: 3px solid var(--rank-color, var(--accent));
  position: relative;
  animation: avatarPulse 3s ease-in-out infinite;
  background: var(--bg);
  border-radius: 0;            /* square, matching the main-menu avatar */
  clip-path: none;
}
.profile-avatar-inner {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.profile-avatar-upload-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,.6);
  opacity: 0;
  transition: opacity .2s;
  cursor: pointer;
  clip-path: none;
  font-size: 28px;
  color: #fff;
}
.profile-avatar-frame:hover .profile-avatar-upload-overlay { opacity: 1; }

.profile-rank-badge {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 4px 18px;
  font-weight: 800;
  letter-spacing: 2px;
  position: relative;
  overflow: hidden;
}
.profile-rank-badge::after {
  content: '';
  position: absolute;
  top: 0; left: -100%; right: -100%; bottom: 0;
  background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,.3) 50%, transparent 100%);
  background-size: 50% 100%;
  animation: badgeShine 3s ease-in-out infinite;
}

.profile-section {
  background: rgba(0,0,0,.2);
  border: 1px solid var(--bg4);
  padding: 14px 16px;
}
.profile-section-wide {
  /* extra breathing room in right column */
  padding: 18px 22px;
}
.profile-section-label {
  font-family: var(--font-display), sans-serif;
  font-size: 10px;
  font-weight: 700;
  color: var(--text2);
  letter-spacing: 2px;
  margin-bottom: 12px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--bg4);
}

.profile-stats-grid {
  display: grid;
  gap: 10px;
}
.profile-stats-grid-4 { grid-template-columns: repeat(4, 1fr); }
.profile-stats-grid-3 { grid-template-columns: repeat(3, 1fr); }

.profile-stat {
  text-align: center;
  padding: 6px 4px;
  background: rgba(255,255,255,.02);
  border: 1px solid rgba(255,255,255,.04);
}
.profile-stat-big {
  padding: 16px 8px;
}
.profile-stat-value {
  font-family: var(--font-display), sans-serif;
  font-size: 20px;
  font-weight: 800;
  color: var(--text);
  line-height: 1.2;
}
.profile-stat-big .profile-stat-value {
  font-size: 28px;
}
.profile-stat-label {
  font-family: var(--font-display), sans-serif;
  font-size: 8px;
  font-weight: 600;
  color: var(--text2);
  letter-spacing: 1px;
  margin-top: 4px;
}
.profile-stat-big .profile-stat-label {
  font-size: 10px;
  margin-top: 6px;
}

.profile-bio-input {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--text);
  padding: 8px 10px;
  font-family: var(--font-body), sans-serif;
  font-size: 14px;
  outline: none;
  resize: none;
  line-height: 1.4;
}
.profile-bio-input:focus { border-color: var(--accent); box-shadow: 0 0 5px rgba(0,240,255,.2); }
.profile-bio-input::placeholder { color: var(--text2); font-style: italic; }

.profile-elo-bar {
  height: 6px;
  background: var(--bg);
  border: 1px solid var(--bg4);
  overflow: hidden;
}
.profile-elo-fill {
  height: 100%;
  transition: width .5s ease-out;
}

.profile-cardback-preview {
  width: 60px;
  height: 84px;
  border: 2px solid var(--bg4);
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.profile-cardback-big {
  width: 90px;
  height: 126px;
}
.profile-cardback-large {
  width: 150px;
  height: 210px;
  border: 2px solid var(--bg4);
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
}
.profile-cardback-xl {
  width: 225px;
  height: 315px;
  border: 2px solid var(--bg4);
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
}
.profile-cardback-clickable {
  cursor: pointer;
  position: relative;
  transition: border-color .2s, box-shadow .2s;
}
.profile-cardback-clickable:hover {
  border-color: var(--accent);
  box-shadow: 0 0 16px rgba(0,240,255,.2);
}
.profile-cardback-hover-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,.6);
  opacity: 0;
  transition: opacity .2s;
  font-family: var(--font-display), sans-serif;
  font-size: 13px;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 2px;
}
.profile-cardback-clickable:hover .profile-cardback-hover-overlay { opacity: 1; }

/* Cardback Gallery Modal */
.profile-cb-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: 14px;
  padding: 4px;
}
.profile-cb-gallery-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  padding: 6px;
  border: 2px solid transparent;
  transition: all .2s;
}
.profile-cb-gallery-item:hover {
  border-color: var(--accent);
  background: rgba(0,240,255,.04);
}
.profile-cb-gallery-item.active {
  border-color: var(--accent);
  background: rgba(0,240,255,.08);
  box-shadow: 0 0 12px rgba(0,240,255,.2);
}
.profile-cb-gallery-card {
  width: 90px;
  height: 126px;
  border: 1px solid var(--bg4);
  background: var(--bg);
  overflow: hidden;
}
.profile-cb-gallery-label {
  font-size: 10px;
  font-weight: 600;
  color: var(--text2);
  text-align: center;
}
.profile-cb-gallery-item.active .profile-cb-gallery-label {
  color: var(--accent);
}
.profile-cb-gallery-upload {
  border: 2px dashed var(--bg4);
}
.profile-cb-gallery-upload:hover {
  border-color: var(--accent);
  border-style: dashed;
}

/* ===== TOP HEROES ===== */
.profile-top-hero {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
  background: rgba(255,255,255,.02);
  border: 1px solid rgba(255,255,255,.04);
  flex: 1;
  min-height: 0;
}
.profile-top-hero-rank {
  font-size: 18px;
  flex-shrink: 0;
  width: 24px;
  text-align: center;
}
.profile-top-hero-card {
  width: 62px;
  height: 88px;
  border: 1px solid var(--bg4);
  background: var(--bg);
  overflow: hidden;
  flex-shrink: 0;
}
.profile-top-hero-name {
  font-weight: 700;
  font-size: 12px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-top-hero-bar {
  height: 3px;
  background: var(--bg);
  border: 1px solid rgba(255,255,255,.06);
  margin-top: 4px;
  overflow: hidden;
}
.profile-top-hero-bar-fill {
  height: 100%;
  transition: width .5s ease-out;
}

/* ===== DECK WALL ===== */
.profile-deck-wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  gap: 12px;
  padding: 4px 0;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  align-content: start;
}
.profile-deck-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  transition: transform .15s;
}
.profile-deck-tile:hover { transform: translateY(-3px); }
.profile-deck-tile-card {
  width: 86px;
  height: 120px;
  border: 2px solid var(--bg4);
  background: var(--bg);
  overflow: hidden;
  position: relative;
  transition: border-color .2s, box-shadow .2s;
}
.profile-deck-tile:hover .profile-deck-tile-card {
  border-color: var(--accent);
  box-shadow: 0 4px 16px rgba(0,0,0,.5), 0 0 10px rgba(0,240,255,.15);
}
.profile-deck-tile-star {
  position: absolute;
  top: 2px;
  left: 4px;
  font-size: 12px;
  color: var(--accent4);
  text-shadow: 0 0 4px rgba(0,0,0,.8);
}
.profile-deck-tile-status {
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: 11px;
  font-weight: 700;
  text-shadow: 0 0 4px rgba(0,0,0,.8);
}
.profile-deck-tile-name {
  font-size: 10px;
  font-weight: 700;
  color: var(--text);
  text-align: center;
  max-width: 90px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.profile-deck-tile-count {
  font-size: 9px;
  color: var(--text2);
  font-family: var(--font-display), sans-serif;
}

/* ===== GAME BOARD ===== */
.game-layout {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
}
.game-hand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(8px * var(--board-scale));
  padding: calc(4px * var(--board-scale)) calc(12px * var(--board-scale));
  background: var(--bg2);
  flex-shrink: 0;
  position: relative;
  min-height: var(--hand-min-h);
}
.game-hand-opp { border-bottom: 1px solid var(--bg4); padding: calc(6px * var(--board-scale)) calc(16px * var(--board-scale)); }
.game-hand-opp .game-hand-info {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
}
.game-hand-opp .game-gold-display { z-index: 2; }
.game-hand-me { border-top: 1px solid var(--bg4); position: relative; z-index: 10000; }
.hand-slot {
  cursor: grab;
  transition: transform .15s;
  position: relative;
}
.hand-slot:active { cursor: grabbing; }
.hand-dragging {
  opacity: 0;
  width: 0;
  overflow: hidden;
  padding: 0;
  margin: 0;
}
.hand-drop-gap {
  width: calc(64px * var(--board-scale));
  height: calc(90px * var(--board-scale));
  flex-shrink: 0;
  border: 2px dashed var(--accent);
  border-radius: 4px;
  background: rgba(0,240,255,.06);
}
/* Card draw animation — floating card slides diagonally from deck to hand */
.draw-anim-card {
  position: fixed;
  z-index: 10050;
  pointer-events: none;
  width: calc(64px * var(--board-scale));
  height: calc(90px * var(--board-scale));
  animation: cardDrawFly .4s ease-out forwards;
}
@keyframes cardDrawFly {
  0% { transform: translate(0, 0) scale(0.7); opacity: .8; filter: brightness(1.6); }
  60% { opacity: 1; filter: brightness(1.2); }
  100% { transform: translate(var(--dx), var(--dy)) scale(1); opacity: 1; filter: brightness(1); }
}
.discard-anim-card {
  position: fixed;
  z-index: 10050;
  pointer-events: none;
  width: calc(64px * var(--board-scale));
  height: calc(90px * var(--board-scale));
  animation: cardDiscardFly .4s ease-in forwards;
}
@keyframes cardDiscardFly {
  0% { transform: translate(0, 0) scale(1); opacity: 1; filter: brightness(1); }
  40% { filter: brightness(1.3); }
  100% { transform: translate(var(--dx), var(--dy)) scale(0.6); opacity: .5; filter: brightness(0.7); }
}
/* Deck builder drag */
[data-touch-drag] { touch-action: none; }
.deck-drag-slot { cursor: grab; }
.deck-drag-slot:active { cursor: grabbing; }
.deck-dragging { position: absolute; width: 0; height: 0; opacity: 0; overflow: hidden; }
.deck-drag-gap {
  width: var(--card-w);
  aspect-ratio: 5 / 7;
  border: 2px dashed var(--accent3);
  background: rgba(170,255,0,.06);
}
.deck-section-body .deck-drag-gap {
  width: 100%;
}
.deck-empty-slot {
  opacity: .3;
}
.deck-empty-slot .card-slot {
  border-style: dashed;
}
/* Simulated starting-hand highlight: strongly glow the first 6 main-deck
   cards after Shuffle, and veil the rest so the "hand" pops. */
.deck-hand-highlight {
  position: relative;
  z-index: 3;
  border-radius: 6px;
  outline: 3px solid #ffe34d;
  outline-offset: 1px;
  box-shadow: 0 0 0 3px #ffd700, 0 0 18px 6px rgba(255,215,0,.8), 0 0 36px 12px rgba(255,200,0,.4);
  animation: handHighlightPulse 2.4s ease-in-out infinite;
}
@keyframes handHighlightPulse {
  0%, 100% {
    box-shadow: 0 0 0 3px #ffd700, 0 0 14px 4px rgba(255,215,0,.6), 0 0 30px 9px rgba(255,200,0,.3);
  }
  50% {
    box-shadow: 0 0 0 3px #ffd700, 0 0 22px 8px rgba(255,215,0,.85), 0 0 42px 15px rgba(255,200,0,.5);
  }
}
/* Dim/desaturate the non-hand cards while the highlight is active. The
   filter/opacity is applied to the card visual only (not the slot) so the
   hover tooltip — a sibling of .card-mini inside the slot — renders normally. */
.deck-section-body.hand-veil .deck-drag-slot:not(.deck-hand-highlight) .card-mini,
.deck-section-body.hand-veil .deck-empty-slot .card-slot {
  opacity: .28;
  filter: grayscale(.6) brightness(.7);
  transition: opacity .2s ease, filter .2s ease;
}

.hand-floating-card {
  position: fixed;
  z-index: 10000;
  pointer-events: none;
  opacity: .9;
  transform: rotate(3deg);
  filter: drop-shadow(0 4px 12px rgba(0,0,0,.6));
}
.game-hand-info {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--board-scale));
  min-width: calc(120px * var(--board-scale));
}
/* Player/opponent name in the hand bar: usernames are capped at 10 chars,
   but CPU opponents render a full Hero name (incl. title), so clamp + ellipsis
   keeps the label from overrunning the divider / cards. */
.game-hand-name {
  display: inline-block;
  max-width: calc(150px * var(--board-scale));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
.game-hand-avatar {
  width: calc(28px * var(--board-scale)); height: calc(28px * var(--board-scale));
  object-fit: cover;
  border: 1px solid var(--bg4);
}
.game-hand-avatar-big {
  /* Almost the full height of the hand container (--hand-min-h), kept square. */
  width: calc(var(--hand-min-h) * 0.86); height: calc(var(--hand-min-h) * 0.86);
  object-fit: cover;
  border: 2px solid var(--bg4);
}
/* CPU / avatar-less player portrait. Wraps a `<HeroArtCrop>` (the same
   cropped hero art used on the singleplayer opponent-picker tiles) in a
   bordered SQUARE frame. HeroArtCrop outputs a 594:398 rectangle at
   the requested width (72x48 here); this wrapper clips it to a square
   by fixing width == height and centering the inner div, so equal
   amounts are cropped from the left and right edges while the vertical
   slice stays the same. Matches .game-hand-avatar-big's chrome. */
.game-hand-avatar-crop {
  display: inline-flex;
  flex-shrink: 0;
  /* Almost the full height of the hand container (--hand-min-h), kept square —
     matches .game-hand-avatar-big. The inner <HeroArtCrop> is rendered at a
     fixed width (135px in app-board.jsx) whose cropped height (135*398/594 ≈
     90px) is >= this square at every --board-scale (capped at 1.0), so it
     always fills the frame with no gaps; excess is center-clipped. */
  width: calc(var(--hand-min-h) * 0.86);
  height: calc(var(--hand-min-h) * 0.86);
  border: 2px solid var(--bg4);
  line-height: 0;
  overflow: hidden;
  justify-content: center;  /* horizontally center the wider inner crop */
  align-items: center;
}
.game-hand-avatar-crop > div {
  display: block;
  flex-shrink: 0;           /* don't let the inner crop compress — let it overflow and be clipped */
}
/* Mobile: scale the whole wrapper so it stays in proportion on narrow
   screens without crowding the player-name next to it. */
@media (max-width: 900px) {
  .game-hand-avatar-crop { transform: scale(0.75); transform-origin: left center; }
}
@media (max-width: 600px) {
  .game-hand-avatar-crop { transform: scale(0.55); transform-origin: left center; }
}
/* Game start announcement */
@keyframes announceFadeIn {
  0% { opacity: 0; transform: scale(1.5); }
  20% { opacity: 1; transform: scale(1); }
  80% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.9); }
}
.game-announcement {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9500;
  background: rgba(0,0,0,.5);
  animation: announceFadeIn 3.5s ease-out forwards;
  cursor: pointer;
}
.game-announcement-text {
  font-family: 'Press Start 2P', monospace;
  font-size: 32px;
  text-shadow: 0 0 30px currentColor, 0 0 60px currentColor;
  letter-spacing: 3px;
}
@keyframes announceShort {
  0% { opacity: 0; transform: scale(1.3); }
  15% { opacity: 1; transform: scale(1); }
  75% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.95); }
}
.game-announcement-short {
  animation: announceShort 2s ease-out forwards;
}

/* ═══ Status Effect Visuals ═══ */

/* Frozen — ice overlay */
.status-frozen-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(150,220,255,.35) 0%,
    rgba(100,180,240,.25) 30%,
    rgba(80,160,230,.4) 60%,
    rgba(120,200,255,.3) 100%
  );
  border: 2px solid rgba(130,210,255,.6);
  box-shadow: inset 0 0 15px rgba(100,200,255,.4), 0 0 10px rgba(100,200,255,.3);
  animation: frozenShimmer 3s ease-in-out infinite;
  overflow: hidden;
}
@keyframes frozenShimmer {
  0%, 100% { box-shadow: inset 0 0 15px rgba(100,200,255,.4), 0 0 10px rgba(100,200,255,.3); }
  50% { box-shadow: inset 0 0 25px rgba(100,200,255,.6), 0 0 18px rgba(100,200,255,.5); }
}
.status-frozen-overlay::before {
  content: '❄';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 28px;
  opacity: .7;
  filter: drop-shadow(0 0 6px rgba(100,200,255,.8));
  animation: frozenSnowflake 4s ease-in-out infinite;
}
@keyframes frozenSnowflake {
  0%, 100% { transform: translate(-50%, -50%) rotate(0deg) scale(1); opacity: .7; }
  50% { transform: translate(-50%, -50%) rotate(30deg) scale(1.1); opacity: .9; }
}

/* Frozen snowflake particles */
.frozen-particle {
  position: absolute;
  color: rgba(200,230,255,.7);
  pointer-events: none;
  filter: drop-shadow(0 0 3px rgba(150,220,255,.8));
  animation: frozenParticleDrift ease-in-out infinite alternate;
}
@keyframes frozenParticleDrift {
  0% { transform: translateY(0) rotate(0deg); opacity: .3; }
  50% { opacity: .9; }
  100% { transform: translateY(-8px) rotate(45deg); opacity: .4; }
}

/* Stunned — lightning bolts */
.status-stunned-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: rgba(255,255,100,.08);
  border: 2px solid rgba(255,220,50,.5);
  box-shadow: inset 0 0 10px rgba(255,220,50,.2);
}
.stun-bolt {
  position: absolute;
  width: 14px;
  height: 14px;
  font-size: 14px;
  line-height: 1;
  color: #ffe033;
  filter: drop-shadow(0 0 4px rgba(255,220,50,.9));
  animation: stunFlicker 0.8s ease-in-out infinite;
}
.stun-bolt::before { content: '⚡'; }
.stun-bolt.s1 { top: 15%; left: 20%; animation-delay: 0s; }
.stun-bolt.s2 { top: 40%; right: 15%; animation-delay: 0.25s; }
.stun-bolt.s3 { bottom: 20%; left: 40%; animation-delay: 0.5s; }
@keyframes stunFlicker {
  0%, 100% { opacity: .6; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.3); }
}

/* Immune — shield icon */
.status-immune-icon {
  position: absolute;
  top: 2px;
  right: 2px;
  z-index: 8;
  font-size: calc(16px * var(--board-scale));
  filter: drop-shadow(0 0 4px rgba(100,255,100,.6));
  pointer-events: auto;
  user-select: none;
  animation: immunePulse 2s ease-in-out infinite;
}
.immune-tooltip {
  position: fixed;
  white-space: nowrap;
  background: var(--bg2);
  border: 1px solid rgba(100,255,100,.4);
  color: #aaffaa;
  font-size: 11px;
  padding: 4px 8px;
  pointer-events: none;
  z-index: 10200;
  box-shadow: 0 2px 8px rgba(0,0,0,.6);
}
@keyframes immunePulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.15); }
}

/* Shielded (first-turn protection) — golden shield, more prominent */
.status-shielded-icon {
  font-size: 20px !important;
  filter: drop-shadow(0 0 6px rgba(255,220,50,.8)) drop-shadow(0 0 12px rgba(255,200,0,.4)) !important;
  animation: shieldedPulse 1.5s ease-in-out infinite !important;
}
@keyframes shieldedPulse {
  0%, 100% { transform: scale(1); filter: drop-shadow(0 0 6px rgba(255,220,50,.8)) drop-shadow(0 0 12px rgba(255,200,0,.4)); }
  50% { transform: scale(1.25); filter: drop-shadow(0 0 10px rgba(255,220,50,1)) drop-shadow(0 0 18px rgba(255,200,0,.6)); }
}

/* Negated — persistent small electricity sparks */
.status-negated-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(255,230,50,.12) 0%,
    rgba(255,200,0,.08) 50%,
    rgba(255,230,50,.14) 100%
  );
  border: 2px solid rgba(255,220,50,.45);
  box-shadow: inset 0 0 10px rgba(255,220,50,.15), 0 0 6px rgba(255,220,50,.2);
  animation: negatedShimmer 2s ease-in-out infinite;
  overflow: hidden;
}
@keyframes negatedShimmer {
  0%, 100% { box-shadow: inset 0 0 10px rgba(255,220,50,.15), 0 0 6px rgba(255,220,50,.2); }
  50% { box-shadow: inset 0 0 18px rgba(255,220,50,.3), 0 0 12px rgba(255,220,50,.35); }
}
.negated-spark {
  position: absolute;
  color: rgba(255,230,80,.85);
  pointer-events: none;
  filter: drop-shadow(0 0 3px rgba(255,220,50,.9));
  animation: negatedSparkFlicker ease-in-out infinite alternate;
}
@keyframes negatedSparkFlicker {
  0% { transform: scale(0.6) rotate(-15deg); opacity: 0; }
  30% { opacity: 1; transform: scale(1.1) rotate(5deg); }
  60% { opacity: .3; transform: scale(0.5) rotate(10deg); }
  100% { transform: scale(0.9) rotate(-10deg); opacity: 0; }
}

/* Burned — persistent flame particles */
.status-burned-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: linear-gradient(
    0deg,
    rgba(255,80,0,.2) 0%,
    rgba(255,120,0,.1) 40%,
    rgba(255,60,0,.06) 100%
  );
  border: 2px solid rgba(255,100,20,.45);
  box-shadow: inset 0 0 10px rgba(255,80,0,.15), 0 0 6px rgba(255,60,0,.2);
  animation: burnedShimmer 1.5s ease-in-out infinite;
  overflow: hidden;
  transition: all .3s ease;
}
@keyframes burnedShimmer {
  0%, 100% { box-shadow: inset 0 0 10px rgba(255,80,0,.15), 0 0 6px rgba(255,60,0,.2); }
  50% { box-shadow: inset 0 0 16px rgba(255,100,0,.25), 0 0 10px rgba(255,80,0,.3); }
}
.burned-particle {
  position: absolute;
  pointer-events: none;
  filter: drop-shadow(0 0 2px rgba(255,80,0,.8));
  animation: burnedFlameFlicker ease-in-out infinite alternate;
  transition: transform .3s ease, filter .3s ease;
}
@keyframes burnedFlameFlicker {
  0% { transform: translateY(0) scale(0.7); opacity: .2; }
  40% { opacity: 1; transform: translateY(-3px) scale(1); }
  70% { opacity: .5; transform: translateY(-1px) scale(0.8); }
  100% { transform: translateY(-5px) scale(0.6); opacity: 0; }
}

/* Burn tick escalation — flames intensify when burn damage triggers */
.status-burned-overlay.burn-ticking {
  background: linear-gradient(
    0deg,
    rgba(255,50,0,.45) 0%,
    rgba(255,100,0,.3) 40%,
    rgba(255,60,0,.15) 100%
  );
  border-color: rgba(255,80,0,.8);
  box-shadow: inset 0 0 25px rgba(255,60,0,.5), 0 0 20px rgba(255,40,0,.5);
}
.status-burned-overlay.burn-ticking .burned-particle {
  transform: scale(1.8) !important;
  filter: drop-shadow(0 0 6px rgba(255,50,0,1)) brightness(1.3);
  animation-duration: .25s !important;
}

/* Poisoned overlay — purple bubbles + stack count */
.status-poisoned-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: linear-gradient(
    0deg,
    rgba(130,0,180,.2) 0%,
    rgba(100,0,160,.1) 40%,
    rgba(80,0,140,.06) 100%
  );
  border: 2px solid rgba(150,30,200,.45);
  box-shadow: inset 0 0 10px rgba(130,0,180,.15), 0 0 6px rgba(100,0,160,.2);
  animation: poisonedShimmer 2s ease-in-out infinite;
  overflow: hidden;
}
@keyframes poisonedShimmer {
  0%, 100% { box-shadow: inset 0 0 10px rgba(130,0,180,.15), 0 0 6px rgba(100,0,160,.2); }
  50% { box-shadow: inset 0 0 16px rgba(150,20,200,.25), 0 0 10px rgba(130,0,180,.3); }
}
.poisoned-particle {
  position: absolute;
  pointer-events: none;
  filter: drop-shadow(0 0 2px rgba(130,0,180,.8));
  animation: poisonedBubble ease-in-out infinite alternate;
}
@keyframes poisonedBubble {
  0% { transform: translateY(0) scale(0.6); opacity: .15; }
  40% { opacity: .8; transform: translateY(-4px) scale(1); }
  70% { opacity: .4; transform: translateY(-2px) scale(0.7); }
  100% { transform: translateY(-6px) scale(0.5); opacity: 0; }
}
.poison-stack-count {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 14px;
  font-weight: 900;
  color: #dd66ff;
  text-shadow: 0 0 6px rgba(150,0,220,.8), 1px 1px 0 #000, -1px -1px 0 #000;
  z-index: 6;
  pointer-events: none;
  width: 24px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  border-radius: 50%;
  background: rgba(60,0,100,.7);
  border: 2px solid rgba(180,50,255,.6);
  box-shadow: 0 0 8px rgba(150,0,220,.5), inset 0 0 4px rgba(180,50,255,.3);
}

/* Berserked overlay — persistent "dark magic + red smoke" while the
   `berserked` status is bound to a Hero. Two particle layers (smoke
   puffs rising from the bottom + dark-magic sparks drifting upward
   from the middle) plus a tinted gradient + pulsing border. Style
   mirrors BurnedOverlay / PoisonedOverlay so the visual reads as
   "negative status" at a glance. */
.status-berserked-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: linear-gradient(
    0deg,
    rgba(180,20,30,.22) 0%,
    rgba(120,10,80,.14) 45%,
    rgba(60,0,60,.08) 100%
  );
  border: 2px solid rgba(200,40,40,.55);
  box-shadow: inset 0 0 12px rgba(180,20,40,.25), 0 0 8px rgba(160,0,80,.3);
  animation: berserkedShimmer 1.4s ease-in-out infinite;
  overflow: hidden;
}
@keyframes berserkedShimmer {
  0%, 100% { box-shadow: inset 0 0 12px rgba(180,20,40,.25), 0 0 8px rgba(160,0,80,.3); }
  50%      { box-shadow: inset 0 0 22px rgba(220,40,60,.4),  0 0 14px rgba(200,20,100,.45); }
}
.berserked-particle {
  position: absolute;
  pointer-events: none;
  color: #c266ff;
  text-shadow: 0 0 4px rgba(160,40,200,.95), 0 0 8px rgba(100,0,140,.7);
  animation: berserkedSpark ease-in-out infinite;
}
@keyframes berserkedSpark {
  0%   { transform: translateY(0) scale(0.5) rotate(0deg);    opacity: 0; }
  20%  { opacity: .95; }
  50%  { transform: translateY(-14px) scale(1.1) rotate(60deg); opacity: 1; }
  100% { transform: translateY(-30px) scale(0.4) rotate(180deg); opacity: 0; }
}
.berserked-smoke {
  position: absolute;
  bottom: 2%;
  pointer-events: none;
  filter: hue-rotate(330deg) saturate(2.2) drop-shadow(0 0 4px rgba(220,40,40,.85));
  opacity: 0;
  animation: berserkedSmoke ease-out infinite;
}
@keyframes berserkedSmoke {
  0%   { transform: translateY(0) scale(0.6);  opacity: 0; }
  20%  { opacity: .8; }
  60%  { transform: translateY(-28px) scale(1.2); opacity: .55; }
  100% { transform: translateY(-50px) scale(1.6); opacity: 0; }
}

/* Overheal Shock — heal reversed overlay */
.status-heal-reversed-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background: linear-gradient(
    135deg,
    rgba(50,180,80,.12) 0%,
    rgba(100,40,160,.1) 50%,
    rgba(50,180,80,.08) 100%
  );
  border: 2px solid rgba(100,200,120,.35);
  box-shadow: inset 0 0 8px rgba(80,200,100,.1), 0 0 4px rgba(130,50,180,.15);
  overflow: hidden;
}
.heal-reversed-particle {
  position: absolute;
  pointer-events: none;
  filter: drop-shadow(0 0 3px currentColor);
  animation: healReversedFloat ease-in-out infinite alternate;
}
@keyframes healReversedFloat {
  0% { transform: translateY(0) scale(0.5); opacity: .1; }
  40% { opacity: .6; transform: translateY(-5px) scale(1); }
  70% { opacity: .3; transform: translateY(-2px) scale(0.7); }
  100% { transform: translateY(-8px) scale(0.4); opacity: 0; }
}

/* Activatable ability zone glow */
.zone-ability-activatable {
  border-color: rgba(255,215,80,1) !important;
  box-shadow: 0 0 22px rgba(255,215,80,1), 0 0 44px rgba(255,170,0,.75), 0 0 70px rgba(255,170,0,.35), inset 0 0 18px rgba(255,215,80,.45) !important;
  animation: abilityPulse 0.7s ease-in-out infinite !important;
  position: relative;
  z-index: 1;
}
.zone-ability-activatable::after {
  content: '⚡';
  position: absolute;
  top: -10px;
  right: -10px;
  font-size: 22px;
  filter: drop-shadow(0 0 8px rgba(255,215,80,1)) drop-shadow(0 0 14px rgba(255,170,0,.8));
  animation: abilityBoltBounce 0.5s ease-in-out infinite alternate;
  z-index: 10;
}
@keyframes abilityPulse {
  0%, 100% { box-shadow: 0 0 22px rgba(255,215,80,1), 0 0 44px rgba(255,170,0,.75), 0 0 70px rgba(255,170,0,.35), inset 0 0 18px rgba(255,215,80,.45); border-color: rgba(255,215,80,1); }
  50%      { box-shadow: 0 0 36px rgba(255,240,120,1), 0 0 72px rgba(255,200,40,.95), 0 0 110px rgba(255,200,40,.55), inset 0 0 28px rgba(255,240,120,.75); border-color: rgba(255,255,180,1); }
}
@keyframes abilityBoltBounce {
  0%   { transform: translateY(0) scale(1) rotate(-4deg); }
  100% { transform: translateY(-5px) scale(1.35) rotate(6deg); }
}

/* Free-activatable ability zone glow (Main Phase, no action cost) */
.zone-ability-free-activatable {
  border-color: rgba(120,255,180,1) !important;
  box-shadow: 0 0 22px rgba(120,255,180,1), 0 0 44px rgba(60,220,140,.75), 0 0 70px rgba(60,220,140,.35), inset 0 0 18px rgba(120,255,180,.45) !important;
  animation: freeAbilityPulse 0.7s ease-in-out infinite !important;
  position: relative;
  z-index: 1;
}
.zone-ability-free-activatable::after {
  content: '✦';
  position: absolute;
  top: -10px;
  right: -10px;
  font-size: 22px;
  color: #9fffcc;
  filter: drop-shadow(0 0 8px rgba(120,255,180,1)) drop-shadow(0 0 14px rgba(60,220,140,.8));
  animation: abilityBoltBounce 0.5s ease-in-out infinite alternate;
  z-index: 10;
}
@keyframes freeAbilityPulse {
  0%, 100% { box-shadow: 0 0 22px rgba(120,255,180,1), 0 0 44px rgba(60,220,140,.75), 0 0 70px rgba(60,220,140,.35), inset 0 0 18px rgba(120,255,180,.45); border-color: rgba(120,255,180,1); }
  50%      { box-shadow: 0 0 36px rgba(160,255,200,1), 0 0 72px rgba(80,255,160,.95), 0 0 110px rgba(80,255,160,.55), inset 0 0 28px rgba(160,255,200,.75); border-color: rgba(200,255,225,1); }
}

/* Active creature effect glow */
.zone-creature-activatable {
  border-color: rgba(255,190,80,1) !important;
  box-shadow: 0 0 22px rgba(255,190,80,1), 0 0 44px rgba(255,140,30,.75), 0 0 70px rgba(255,140,30,.35), inset 0 0 18px rgba(255,190,80,.45) !important;
  animation: creatureEffectPulse 0.7s ease-in-out infinite !important;
  position: relative;
  z-index: 1;
}
.zone-creature-activatable::after {
  content: '⚔';
  position: absolute;
  top: -10px;
  right: -10px;
  font-size: 22px;
  color: #ffc070;
  filter: drop-shadow(0 0 8px rgba(255,190,80,1)) drop-shadow(0 0 14px rgba(255,140,30,.8));
  animation: abilityBoltBounce 0.5s ease-in-out infinite alternate;
  z-index: 10;
}
@keyframes creatureEffectPulse {
  0%, 100% { box-shadow: 0 0 22px rgba(255,190,80,1), 0 0 44px rgba(255,140,30,.75), 0 0 70px rgba(255,140,30,.35), inset 0 0 18px rgba(255,190,80,.45); border-color: rgba(255,190,80,1); }
  50%      { box-shadow: 0 0 36px rgba(255,220,130,1), 0 0 72px rgba(255,170,40,.95), 0 0 110px rgba(255,170,40,.55), inset 0 0 28px rgba(255,220,130,.75); border-color: rgba(255,240,180,1); }
}

/* Active equip effect (Slippery Skates, etc.) */
.zone-equip-activatable {
  border-color: rgba(80,220,240,1) !important;
  box-shadow: 0 0 22px rgba(80,220,240,1), 0 0 44px rgba(30,180,200,.75), 0 0 70px rgba(30,180,200,.35), inset 0 0 18px rgba(80,220,240,.45) !important;
  animation: equipEffectPulse 0.7s ease-in-out infinite !important;
  position: relative;
  z-index: 1;
}
.zone-equip-activatable::after {
  content: '⚙';
  position: absolute;
  top: -10px;
  right: -10px;
  font-size: 22px;
  color: #5ddcf2;
  filter: drop-shadow(0 0 8px rgba(80,220,240,1)) drop-shadow(0 0 14px rgba(30,180,200,.8));
  animation: abilityBoltBounce 0.5s ease-in-out infinite alternate;
  z-index: 10;
}
@keyframes equipEffectPulse {
  0%, 100% { box-shadow: 0 0 22px rgba(80,220,240,1), 0 0 44px rgba(30,180,200,.75), 0 0 70px rgba(30,180,200,.35), inset 0 0 18px rgba(80,220,240,.45); border-color: rgba(80,220,240,1); }
  50%      { box-shadow: 0 0 36px rgba(140,240,255,1), 0 0 72px rgba(60,220,240,.95), 0 0 110px rgba(60,220,240,.55), inset 0 0 28px rgba(140,240,255,.75); border-color: rgba(180,250,255,1); }
}

/* Exhausted/unusable free-activation ability — grayed out */
.zone-ability-exhausted {
  opacity: 0.45 !important;
  filter: saturate(0.15) brightness(0.7) !important;
  border-color: rgba(120,120,120,.5) !important;
  box-shadow: none !important;
}

/* Friendship additional action available — very prominent green pulse */
.zone-friendship-active {
  border-color: rgba(80,255,140,1) !important;
  box-shadow: 0 0 18px rgba(80,255,140,.8), 0 0 36px rgba(40,200,100,.5), 0 0 54px rgba(40,200,100,.2), inset 0 0 12px rgba(80,255,140,.3) !important;
  animation: friendshipPulse 0.8s ease-in-out infinite !important;
  position: relative;
}
.zone-friendship-active::after {
  content: '💚';
  position: absolute;
  top: -8px;
  right: -8px;
  font-size: 15px;
  filter: drop-shadow(0 0 6px rgba(80,255,140,1));
  animation: friendshipBounce 0.6s ease-in-out infinite alternate;
  z-index: 10;
}
@keyframes friendshipPulse {
  0%, 100% { box-shadow: 0 0 18px rgba(80,255,140,.8), 0 0 36px rgba(40,200,100,.5), 0 0 54px rgba(40,200,100,.2), inset 0 0 12px rgba(80,255,140,.3); border-color: rgba(80,255,140,1); }
  50% { box-shadow: 0 0 28px rgba(80,255,140,1), 0 0 50px rgba(40,200,100,.7), 0 0 70px rgba(40,200,100,.3), inset 0 0 20px rgba(80,255,140,.5); border-color: rgba(140,255,200,1); }
}
@keyframes friendshipBounce {
  0% { transform: translateY(0) scale(1); }
  100% { transform: translateY(-4px) scale(1.2); }
}

/* Active hero effect glow (e.g. Alice, the Puppeteer Girl) */
.zone-hero-effect-active {
  border-color: rgba(255,120,120,1) !important;
  box-shadow: 0 0 22px rgba(255,120,120,1), 0 0 44px rgba(255,50,50,.75), 0 0 70px rgba(255,50,50,.35), inset 0 0 18px rgba(255,120,120,.45) !important;
  animation: heroEffectPulse 0.7s ease-in-out infinite !important;
  position: relative;
  z-index: 1;
}
.zone-hero-effect-active::after {
  content: '⚔';
  position: absolute;
  top: -12px;
  right: -12px;
  font-size: 26px;
  color: #ff9090;
  filter: drop-shadow(0 0 8px rgba(255,120,120,1)) drop-shadow(0 0 14px rgba(255,50,50,.8));
  animation: abilityBoltBounce 0.5s ease-in-out infinite alternate;
  z-index: 10;
}
@keyframes heroEffectPulse {
  0%, 100% { box-shadow: 0 0 22px rgba(255,120,120,1), 0 0 44px rgba(255,50,50,.75), 0 0 70px rgba(255,50,50,.35), inset 0 0 18px rgba(255,120,120,.45); border-color: rgba(255,120,120,1); }
  50%      { box-shadow: 0 0 38px rgba(255,170,170,1), 0 0 76px rgba(255,80,80,.95), 0 0 110px rgba(255,80,80,.55), inset 0 0 28px rgba(255,170,170,.75); border-color: rgba(255,200,200,1); }
}

/* Laser beam animation */
.beam-animation-container {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 10200;
}
.beam-animation-container svg {
  width: 100%;
  height: 100%;
}
/* The red literals below are wrapped in CSS vars whose fallbacks ARE
   those exact literals — so any beam that does NOT set the vars (every
   existing caller) renders byte-identical. Disruption Ray sets the
   --bg-* vars on its <g> to recolor only its own beam's halo green. */
.beam-line-core {
  stroke: #ff2222;
  stroke-width: 3;
  filter: drop-shadow(0 0 8px var(--bg-core-1, rgba(255,0,0,.9))) drop-shadow(0 0 20px var(--bg-core-2, rgba(255,50,50,.5)));
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  animation: beamDraw 0.3s ease-out forwards;
}
.beam-line-glow {
  stroke: var(--bg-line-glow, rgba(255,100,100,.4));
  stroke-width: 12;
  filter: blur(4px);
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  animation: beamDraw 0.3s ease-out forwards;
}
.beam-line-outer {
  stroke: var(--bg-line-outer, rgba(255,200,200,.15));
  stroke-width: 24;
  filter: blur(8px);
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  animation: beamDraw 0.3s ease-out forwards;
}
/* Sustained central flash at the impact point. Runs for the full */
/* beam duration (set via the inline `--beamDur` CSS variable) — */
/* throbs between bright/dim so the impact never feels static. */
.beam-flash {
  filter: drop-shadow(0 0 18px var(--bg-flash-1, rgba(255,30,30,.95))) drop-shadow(0 0 40px var(--bg-flash-2, rgba(255,80,80,.65)));
  transform-origin: center;
  transform-box: fill-box;
  opacity: 0;
  animation: beamFlashPulse var(--beamDur, 1500ms) ease-out 0.18s forwards;
}
/* Expanding shockwave rings. Three layers with staggered delays + */
/* end radii (driven by `--beamRingMax`) so the impact reads as a */
/* continuous shockwave for the entire laser. */
.beam-shockwave {
  filter: drop-shadow(0 0 8px var(--bg-shock, rgba(255,80,80,.7)));
  opacity: 0;
}
.beam-shockwave-1 { animation: beamShockwave var(--beamDur, 1500ms) ease-out 0.18s forwards; }
.beam-shockwave-2 { animation: beamShockwave var(--beamDur, 1500ms) ease-out calc(var(--beamDur, 1500ms) * 0.28) forwards; }
.beam-shockwave-3 { animation: beamShockwave var(--beamDur, 1500ms) ease-out calc(var(--beamDur, 1500ms) * 0.55) forwards; }
@keyframes beamDraw {
  to { stroke-dashoffset: 0; }
}
@keyframes beamFlashPulse {
  0%   { opacity: 0; transform: scale(0.4); }
  10%  { opacity: 1;    transform: scale(1.2); }
  35%  { opacity: 0.95; transform: scale(0.95); }
  60%  { opacity: 0.9;  transform: scale(1.15); }
  85%  { opacity: 0.7;  transform: scale(1.0);  }
  100% { opacity: 0;    transform: scale(1.5);  }
}
@keyframes beamShockwave {
  0%   { opacity: 0;   r: 0; stroke-width: inherit; }
  6%   { opacity: 0.95; }
  100% { opacity: 0;   r: var(--beamRingMax, 200px); }
}

/* Arrow rain animation for Rain of Arrows */
@keyframes arrowFall {
  0% { opacity: 1; transform: translateY(0); }
  80% { opacity: 1; }
  100% { opacity: 0; transform: translateY(180px); }
}

/* Ram animation — hero charges to target and back */
.ram-anim-card {
  position: fixed;
  z-index: 10120;
  pointer-events: none;
  animation: ramCharge var(--ram-dur, 1600ms) ease-in-out forwards;
  filter: brightness(1.3) drop-shadow(0 0 12px rgba(255,80,0,.8));
}
.ram-anim-card .board-card {
  width: var(--zone-w);
  height: var(--zone-h);
}
.ram-flame-trail {
  position: absolute;
  inset: -8px;
  border-radius: 8px;
  background: radial-gradient(ellipse, rgba(255,100,0,.5) 0%, rgba(255,40,0,.2) 40%, transparent 70%);
  animation: ramFlameTrail 200ms ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes ramFlameTrail {
  0% { opacity: 0.6; transform: scale(1); }
  100% { opacity: 1; transform: scale(1.15); }
}
.ram-fire-stars .ram-flame-trail {
  background: radial-gradient(ellipse, rgba(255,200,50,.6) 0%, rgba(255,100,0,.3) 40%, transparent 70%);
}
.ram-fire-particle {
  position: absolute;
  left: calc(50% + var(--fp-x, 0px));
  top: calc(100% + var(--fp-y, 10px));
  font-size: 14px;
  pointer-events: none;
  animation: ramFireParticleDrift 0.4s ease-out var(--fp-delay, 0s) infinite alternate;
  filter: drop-shadow(0 0 4px rgba(255,150,0,.8));
}
@keyframes ramFireParticleDrift {
  0% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0.3; transform: translateY(8px) scale(0.5); }
}

/* ─── Tharxian (Trojan) Horse charge ───────────────────────
   A large wooden Trojan Horse rolls out of the activator's
   flipped Surprise Zone and rams into the attacker. The
   outer `.tharxian-horse-anim` carries the translate-along-
   path keyframe (`tharxianHorseRoll`); the inner
   `.tharxian-horse-gallop` carries the up-down gallop bob
   (kept on a separate element so it doesn't clobber the
   outer translate). Dust puffs trail behind via
   `.tharxian-dust-puff` with staggered delays. */
.tharxian-horse-anim {
  position: fixed;
  z-index: 10130;
  pointer-events: none;
  animation: tharxianHorseRoll var(--th-dur, 1100ms) cubic-bezier(.22, .8, .38, 1) forwards;
}
.tharxian-horse-gallop {
  position: relative;
  animation: tharxianHorseGallop 220ms ease-in-out infinite alternate;
  transform-origin: 50% 80%;
}
.tharxian-dust-puff {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--puff-size, 16px);
  height: var(--puff-size, 16px);
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(180, 150, 110, 0.65) 0%,
    rgba(150, 120, 80, 0.45) 40%,
    rgba(120, 95, 55, 0) 70%);
  filter: blur(2px);
  opacity: 0;
  animation: tharxianDustPuff 700ms ease-out var(--puff-delay, 0s) infinite;
  pointer-events: none;
}
@keyframes tharxianHorseRoll {
  0%   { transform: translate(0, 0) scale(0.85); opacity: 0; }
  8%   { transform: translate(0, 0) scale(1); opacity: 1; }
  18%  { transform: translate(calc(var(--thDx) * 0.20), calc(var(--thDy) * 0.20)) scale(1); opacity: 1; }
  62%  { transform: translate(calc(var(--thDx) * 0.85), calc(var(--thDy) * 0.85)) scale(1); opacity: 1; }
  78%  { transform: translate(var(--thDx), var(--thDy)) scale(1.18); opacity: 1; }
  /* Impact shudder */
  84%  { transform: translate(var(--thDx), var(--thDy)) scale(1.08) rotate(-3deg); opacity: 1; }
  90%  { transform: translate(var(--thDx), var(--thDy)) scale(1.12) rotate(3deg); opacity: 1; }
  100% { transform: translate(var(--thDx), var(--thDy)) scale(1.05); opacity: 0; }
}
@keyframes tharxianHorseGallop {
  0%   { transform: translateY(0) rotate(-1deg); }
  100% { transform: translateY(-4px) rotate(1deg); }
}
@keyframes tharxianDustPuff {
  0%   { opacity: 0; transform: translate(var(--puff-x, 0px), var(--puff-y, 0px)) scale(0.4); }
  20%  { opacity: 0.85; transform: translate(var(--puff-x, 0px), var(--puff-y, 0px)) scale(1); }
  100% { opacity: 0; transform: translate(calc(var(--puff-x, 0px) * 1.6), calc(var(--puff-y, 0px) - 12px)) scale(1.6); }
}

/* ─── Tharxian Horse IMPACT burst ───────────────────────────
   Detonates at the target the moment the horse lands. Layered
   visuals: an outer + inner shockwave ring (expanding white-
   gold halos), a central radial flash (peaks bright then
   dissipates), 12 wood splinters spraying radially outward
   with random spins, 6 heavier debris chunks (square wood
   blocks) with an upward bias, and a ground dust burst at the
   impact point. Each child carries its own animation-delay
   timed off `h.dur * 0.78` so the whole sequence fires in
   sync with the horse's shudder. */
.tharxian-impact {
  position: fixed;
  z-index: 10131;
  pointer-events: none;
  width: 0;
  height: 0;
}
.tharxian-impact-ring {
  position: absolute;
  left: 0; top: 0;
  width: 20px; height: 20px;
  border: 6px solid rgba(255, 230, 140, 0.95);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  opacity: 0;
  box-shadow:
    0 0 30px rgba(255, 200, 80, 0.7),
    inset 0 0 16px rgba(255, 240, 180, 0.6);
  animation: tharxianImpactRing 750ms cubic-bezier(.15, .8, .3, 1) forwards;
}
.tharxian-impact-ring--inner {
  border-color: rgba(255, 180, 60, 0.85);
  animation-duration: 600ms;
}
.tharxian-impact-flash {
  position: absolute;
  left: 0; top: 0;
  width: 80px; height: 80px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255, 255, 240, 1) 0%,
    rgba(255, 220, 140, 0.8) 30%,
    rgba(255, 160, 60, 0.4) 60%,
    transparent 80%);
  filter: blur(2px);
  transform: translate(-50%, -50%) scale(0);
  opacity: 0;
  animation: tharxianImpactFlash 650ms ease-out forwards;
}
.tharxian-impact-splinter {
  position: absolute;
  left: 0; top: 0;
  width: 18px; height: 5px;
  background: linear-gradient(90deg, #8a5226 0%, #5a341a 50%, #2a1808 100%);
  border: 1px solid #1a0e04;
  border-radius: 1px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  transform: translate(-50%, -50%);
  opacity: 0;
  animation: tharxianImpactSplinter 900ms cubic-bezier(.3, .65, .4, 1) forwards;
}
.tharxian-impact-chunk {
  position: absolute;
  left: 0; top: 0;
  width: 12px; height: 12px;
  background: linear-gradient(135deg, #7a4520 0%, #4a2a10 60%, #2a1808 100%);
  border: 1px solid #1a0e04;
  border-radius: 2px;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.6);
  transform: translate(-50%, -50%);
  opacity: 0;
  animation: tharxianImpactChunk 1000ms cubic-bezier(.3, .55, .5, 1) forwards;
}
.tharxian-impact-dust {
  position: absolute;
  left: 0; top: 0;
  width: 140px; height: 60px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(160, 130, 90, 0.7) 0%,
    rgba(130, 100, 70, 0.4) 40%,
    transparent 75%);
  filter: blur(4px);
  transform: translate(-50%, -50%) scale(0.3);
  opacity: 0;
  animation: tharxianImpactDust 800ms ease-out forwards;
}
@keyframes tharxianImpactRing {
  0%   { width: 20px; height: 20px; opacity: 1; border-width: 8px; }
  50%  { opacity: 0.85; }
  100% { width: 280px; height: 280px; opacity: 0; border-width: 1px; }
}
@keyframes tharxianImpactFlash {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0); }
  20%  { opacity: 1; transform: translate(-50%, -50%) scale(2.6); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(3.8); }
}
@keyframes tharxianImpactSplinter {
  0%   {
    opacity: 0;
    transform: translate(-50%, -50%) translate(0, 0) rotate(0deg);
  }
  10%  { opacity: 1; }
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) translate(var(--sp-dx, 0), var(--sp-dy, 0)) rotate(var(--sp-rot-end, 360deg));
  }
}
@keyframes tharxianImpactChunk {
  0%   {
    opacity: 0;
    transform: translate(-50%, -50%) translate(0, 0) rotate(0deg);
  }
  8%   { opacity: 1; }
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) translate(var(--ck-dx, 0), var(--ck-dy, 0)) rotate(var(--ck-rot-end, 360deg));
  }
}
@keyframes tharxianImpactDust {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.3); }
  25%  { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(2); }
}

/* Projectile animation — flaming emoji flies from source to target */
.projectile-anim {
  position: fixed;
  z-index: 10120;
  pointer-events: none;
  animation: projectileFly var(--proj-dur, 600ms) ease-in forwards;
  transform: translate(-50%, -50%);
}
.projectile-emoji {
  font-size: 64px;
  filter: drop-shadow(0 0 20px rgba(255,80,0,.9)) drop-shadow(0 0 40px rgba(255,40,0,.6));
  animation: projectilePulse 100ms ease-in-out infinite alternate;
}
/* Arrow-shaft projectile (Skeleton Archer, future bow cards). The
   wrapping `.projectile-rotation-wrap` already applies the static
   src→tgt rotation, so this class only carries the visual styling
   for the SVG arrow itself. NO transforms here — adding any would
   compose with the wrapper and double-rotate. */
.projectile-arrow-shaft {
  display: inline-block;
  filter: drop-shadow(0 0 4px rgba(0,0,0,.6));
}
/* Rotation wrapper used by EVERY projectile (emoji + SVG alike). Sits
   between `.projectile-anim` (which carries the translate-along-path
   keyframe) and the actual visual element (which may itself carry a
   pulse-scale keyframe — e.g. `.projectile-emoji`). Pulling the
   rotation onto its own element keeps it from being clobbered by the
   pulse keyframe's `transform: scale(...)` (CSS transforms don't
   compose across animations on the same element). Phoenix, fireballs,
   arrows — anything with a clear "front" — naturally point at the
   target throughout the flight. */
.projectile-rotation-wrap {
  display: inline-block;
  transform-origin: 50% 50%;
  transform: rotate(var(--projAngle, 0deg));
}
.projectile-flame-trail {
  position: absolute;
  inset: -20px;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(255,100,0,.6) 0%, rgba(255,40,0,.3) 30%, transparent 60%);
  animation: projectileTrailPulse 80ms ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes projectileFly {
  0% { transform: translate(-50%, -50%) translate(0, 0) scale(0.5); opacity: 0; }
  10% { transform: translate(-50%, -50%) translate(0, 0) scale(1.2); opacity: 1; }
  85% { transform: translate(-50%, -50%) translate(var(--projDx), var(--projDy)) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -50%) translate(var(--projDx), var(--projDy)) scale(1.5); opacity: 0; }
}
@keyframes projectilePulse {
  0% { transform: scale(1); }
  100% { transform: scale(1.1); }
}
@keyframes projectileTrailPulse {
  0% { opacity: 0.5; transform: scale(1); }
}
@keyframes hammerSpin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
  100% { opacity: 0.9; transform: scale(1.2); }
}
@keyframes ramCharge {
  0% { transform: translate(0, 0) rotate(0deg) scale(1.1); opacity: 1; }
  5% { transform: translate(0, 0) rotate(var(--ramAngle, 0deg)) scale(1.15); opacity: 1; }
  12% { transform: translate(var(--ramDx), var(--ramDy)) rotate(var(--ramAngle, 0deg)) scale(1.25); opacity: 1; }
  16% { transform: translate(var(--ramDx), var(--ramDy)) rotate(var(--ramAngle, 0deg)) scale(1.1); opacity: 1; }
  22% { transform: translate(var(--ramDx), var(--ramDy)) rotate(var(--ramAngle, 0deg)) scale(1.05); opacity: 1; }
  30% { transform: translate(0, 0) rotate(0deg) scale(1); opacity: 1; }
  /* Hold full opacity until React removes the ram entry — fading
     from 1→0 inside the keyframe (the previous 90→100 ramp) caused
     a visible flicker when the placeholder un-mounted: the hero
     placeholder was still hidden (`isRamming` true), the ram card
     was fading out in front of it, and only after `setTimeout`
     fired did the hero re-render. Keeping opacity 1 through the
     end means the unmount and the hero's re-render happen in the
     same paint and the swap is invisible. */
  100% { transform: translate(0, 0) rotate(0deg) scale(1); opacity: 1; }
}

/* Gold-steal coin — flies from victim's gold counter to thief's, arcing
   upward via a midpoint jitter so the stream reads as a shower of
   coins rather than a straight line. */
@keyframes goldStealCoin {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.6) rotate(0deg); }
  20%  { opacity: 1; transform: translate(var(--gsMx), var(--gsMy)) scale(1.1) rotate(140deg); }
  70%  { opacity: 1; transform:
    translate(calc(var(--gsDx) * 0.75 + var(--gsMx) * 0.25),
              calc(var(--gsDy) * 0.75 + var(--gsMy) * 0.25))
    scale(1) rotate(320deg); }
  100% { opacity: 0; transform: translate(var(--gsDx), var(--gsDy)) scale(0.7) rotate(520deg); }
}

/* Critical Strike — huge cross-cut slash with shockwave, glint, text,
   and a fan of sparks. Front-loaded so the hit lands heavy and fast. */
@keyframes criticalSlashA {
  0%   { opacity: 0; transform: rotate(-35deg) translateX(-130%); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(-35deg) translateX(130%); }
}
@keyframes criticalSlashB {
  0%   { opacity: 0; transform: rotate(35deg) translateX(-130%); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(35deg) translateX(130%); }
}
@keyframes criticalGlint {
  0%   { opacity: 0; transform: scale(0.2); }
  40%  { opacity: 1; transform: scale(1.4); }
  100% { opacity: 0; transform: scale(2.4); }
}
@keyframes criticalShockwave {
  0%   { opacity: 0; transform: scale(0.25); }
  25%  { opacity: 1; transform: scale(1.05); }
  65%  { opacity: 0.7; transform: scale(1.35); }
  100% { opacity: 0; transform: scale(1.7); }
}
@keyframes criticalText {
  0%   { opacity: 0; transform: scale(0.3) translateY(8px); }
  35%  { opacity: 1; transform: scale(1.15) translateY(0); }
  75%  { opacity: 1; transform: scale(1) translateY(-2px); }
  100% { opacity: 0; transform: scale(0.95) translateY(-14px); }
}
@keyframes criticalSpark {
  0%   { opacity: 0; transform: translate(0, 0) scale(1); }
  20%  { opacity: 1; }
  100% { opacity: 0; transform:
    translate(calc(cos(var(--csAngle)) * var(--csDist)), calc(sin(var(--csAngle)) * var(--csDist)))
    scale(0.15); }
}

/* Siphem orbital laser — charge-up, falling beam, impact flash, sparks. */
@keyframes orbitalLaserCharge {
  0%   { opacity: 0; transform: scale(0.3); }
  60%  { opacity: 1; transform: scale(1.25); }
  100% { opacity: 0.95; transform: scale(1); }
}
@keyframes orbitalLaserBeam {
  0%   { opacity: 0; transform: scaleY(0); }
  25%  { opacity: 1; transform: scaleY(1); }
  75%  { opacity: 0.95; transform: scaleY(1); }
  100% { opacity: 0; transform: scaleY(1); }
}
@keyframes orbitalLaserImpact {
  0%   { opacity: 0; transform: scale(0.35); }
  25%  { opacity: 1; transform: scale(1.2); }
  60%  { opacity: 0.75; transform: scale(1.55); }
  100% { opacity: 0; transform: scale(1.9); }
}
@keyframes orbitalLaserSpark {
  0%   { opacity: 0; transform: translate(0, 0) scale(1); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform:
    translate(calc(cos(var(--olAngle)) * var(--olDist)), calc(sin(var(--olAngle)) * var(--olDist)))
    scale(0.2); }
}

/* Blood Moon under the Sea re-trigger — red moon, beams, dripping blood. */
@keyframes bloodMoonOrb {
  0%   { opacity: 0; transform: translateY(14px) scale(0.4); }
  35%  { opacity: 1; transform: translateY(0) scale(1.15); }
  100% { opacity: 0.85; transform: translateY(-6px) scale(1); }
}
@keyframes bloodMoonHalo {
  0%   { opacity: 0; transform: scale(0.55); }
  40%  { opacity: 1; transform: scale(1.08); }
  100% { opacity: 0; transform: scale(1.25); }
}
@keyframes bloodMoonRay {
  0%   { opacity: 0; transform: rotate(var(--bmrAngle, 0deg)) scaleY(0); }
  35%  { opacity: 1; transform: rotate(var(--bmrAngle, 0deg)) scaleY(1); }
  80%  { opacity: 0.85; }
  100% { opacity: 0; transform: rotate(var(--bmrAngle, 0deg)) scaleY(1); }
}
@keyframes bloodMoonDrop {
  0%   { opacity: 0; transform: translateY(0) scaleY(1); }
  20%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(var(--bmEndY, 80px)) scaleY(1.2); }
}

/* Deepsea Spores — board-wide rain + per-creature anemone growth. */
@keyframes deepseaSporeFall {
  0%   { opacity: 0; transform: translate(0, 0); }
  10%  { opacity: var(--dsOpacity, .8); }
  50%  { transform: translate(calc(var(--dsEndX) * 0.5 + var(--dsWobble)), calc(var(--dsEndY) * 0.5)); }
  90%  { opacity: var(--dsOpacity, .6); }
  100% { opacity: 0; transform: translate(var(--dsEndX), var(--dsEndY)); }
}
@keyframes deepseaSporesWash {
  0%   { opacity: 0; }
  30%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes deepseaSporeRedPulse {
  0%   { opacity: 0; transform: scale(0.6); }
  30%  { opacity: 1; transform: scale(1.05); }
  70%  { opacity: 0.8; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.08); }
}
@keyframes deepseaTendrilGrow {
  0%   { transform: rotate(var(--dtAngle, 0deg)) scaleY(0); }
  60%  { transform: rotate(var(--dtAngle, 0deg)) scaleY(1.08); }
  100% { transform: rotate(var(--dtAngle, 0deg)) scaleY(1); }
}
@keyframes deepseaFrondPop {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.2) rotate(-12deg); }
  30%  { opacity: 1; transform: translate(0, -6px) scale(1.1) rotate(6deg); }
  100% { opacity: 0.9; transform: translate(0, -18px) scale(1) rotate(-4deg); }
}

/* Sporified creature — dark-red tint while Deepsea Spores is active. */
.creature-sporified {
  filter: brightness(0.78) sepia(0.6) hue-rotate(-50deg) saturate(1.7) contrast(1.05);
  transition: filter 300ms ease-in-out;
}

/* Deep sea bubble rise animation for Monster in a Bottle */
@keyframes bubbleRise {
  0% { opacity: 0; transform: translateY(0) translateX(0); }
  15% { opacity: var(--bubbleOpacity, 0.6); }
  50% { transform: translateY(calc(var(--endY) * 0.5)) translateX(var(--wobble)); }
  85% { opacity: var(--bubbleOpacity, 0.4); }
  100% { opacity: 0; transform: translateY(var(--endY)) translateX(0); }
}

/* Claw maul — Deepsea Werewolf's damage signature. Four parallel
   claw streaks rake across the target, each leaving a crimson wound;
   gore drops splatter beneath the cut cluster. */
@keyframes clawSlash {
  0%   { transform: rotate(-32deg) scale(var(--cmScale, 1)) translateX(-120%); opacity: 0; }
  25%  { opacity: 1; }
  100% { transform: rotate(-32deg) scale(var(--cmScale, 1)) translateX(120%); opacity: 0; }
}
@keyframes clawWound {
  0%   { opacity: 0; transform: rotate(-32deg) scaleX(0); }
  22%  { opacity: 1; transform: rotate(-32deg) scaleX(1); }
  75%  { opacity: 0.75; }
  100% { opacity: 0; transform: rotate(-32deg) scaleX(1); }
}
@keyframes clawGore {
  0%   { opacity: 0; transform: translate(0, 0); }
  18%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-4px, var(--cmGy)); }
}

/* Scythe cut — Deepsea Reaper's creature-defeat signature. The blade
   streaks across the slot, a white glint pops at the impact point, a
   crimson wound lingers along the cut path, and droplets fall away. */
@keyframes scytheSlash {
  0%   { transform: rotate(-42deg) translateX(-120%); opacity: 0; }
  25%  { opacity: 1; }
  100% { transform: rotate(-42deg) translateX(120%); opacity: 0; }
}
@keyframes scytheGlint {
  0%   { opacity: 0; transform: scale(0.2); }
  30%  { opacity: 1; transform: scale(1.4); }
  100% { opacity: 0; transform: scale(2.2); }
}
@keyframes scytheWound {
  0%   { opacity: 0; transform: rotate(-42deg) scaleX(0); }
  20%  { opacity: 1; transform: rotate(-42deg) scaleX(1); }
  75%  { opacity: 0.85; }
  100% { opacity: 0; transform: rotate(-42deg) scaleX(1); }
}
@keyframes scytheDrop {
  0%   { opacity: 0; transform: translate(0, 0); }
  20%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-6px, var(--scDropY)); }
}

/* Flame slash — Thaw Blader's blade strike. Diagonal sword slash
   wreathed in fire: a bright blade streak sweeps across the target,
   a hot white-orange glint pops at the midpoint, a lingering flame
   trail glows along the cut path, embers scatter outward, and a
   heat-shimmer wash dilates the slot briefly. */
@keyframes flameSlashBlade {
  0%   { opacity: 0; transform: rotate(-42deg) translateX(-130%); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(-42deg) translateX(130%); }
}
@keyframes flameSlashGlint {
  0%   { opacity: 0; transform: scale(0.25); }
  35%  { opacity: 1; transform: scale(1.5); }
  100% { opacity: 0; transform: scale(2.6); }
}
@keyframes flameSlashTrail {
  0%   { opacity: 0; transform: rotate(-42deg) scaleX(0); }
  22%  { opacity: 1; transform: rotate(-42deg) scaleX(1); }
  70%  { opacity: 0.95; transform: rotate(-42deg) scaleX(1); }
  100% { opacity: 0; transform: rotate(-42deg) scaleX(1); }
}
@keyframes flameSlashHeat {
  0%   { opacity: 0; transform: scale(0.45); filter: blur(0px); }
  35%  { opacity: 0.85; transform: scale(1.05); filter: blur(1.5px); }
  100% { opacity: 0; transform: scale(1.45); filter: blur(2.5px); }
}
@keyframes flameSlashEmber {
  0%   { opacity: 0; transform: translate(0, 0) scale(1); }
  20%  { opacity: 1; }
  100% { opacity: 0; transform:
    translate(calc(cos(var(--fsAngle)) * var(--fsDist)), calc(sin(var(--fsAngle)) * var(--fsDist)))
    scale(0.2); }
}
@keyframes flameSlashFlame {
  0%   { opacity: 0; transform: rotate(-42deg) translate(var(--fsfStartX, 0), 0) scale(0.4); }
  30%  { opacity: 1; transform: rotate(-42deg) translate(var(--fsfStartX, 0), -6px) scale(1.15); }
  100% { opacity: 0; transform: rotate(-42deg) translate(var(--fsfStartX, 0), -22px) scale(0.8); }
}

/* Crescent reap — Skeleton Reaper's signature curved blade-arc cut.
   Path uses pathLength="1" so dashoffset 1→0 is a normalized "draw"
   regardless of the actual SVG path length (which scales with target
   size + lethal flag). Two variants: a quick light cut for non-lethal
   hits, and a slow heavy lethal cut tuned to feel weighty. */
@keyframes crescentReapNormal {
  0%   { stroke-dashoffset: 1; opacity: 0; }
  15%  { opacity: 1; }
  85%  { opacity: 0.95; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes crescentReapLethal {
  0%   { stroke-dashoffset: 1; opacity: 0; }
  18%  { opacity: 1; }
  72%  { opacity: 1; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}
/* Aura that swells beneath a lethal cut — black-violet gloom with a
   crimson core tint. Fades through scale to leave a lingering wash
   right when the blade vanishes. */
@keyframes crescentReapAura {
  0%   { opacity: 0; transform: scale(0.5); }
  35%  { opacity: 0.95; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.45); }
}
/* Skull rising through the aura on a lethal cut. Floats up + slight
   sway, fades out as the cut completes. */
@keyframes crescentReapSkull {
  0%   { opacity: 0; transform: translate(0, 28px) rotate(-12deg) scale(0.8); }
  30%  { opacity: 1; transform: translate(0, 0) rotate(0deg) scale(1); }
  70%  { opacity: 1; transform: translate(0, -10px) rotate(8deg) scale(1.05); }
  100% { opacity: 0; transform: translate(0, -36px) rotate(14deg) scale(1.1); }
}

/* Spooky ghost float — Deepsea Poltergeister artifact-destroy signature.
   Ghost fades in, wobbles side-to-side while rising, fades out. */
@keyframes spookyGhostFloat {
  0%   { opacity: 0; transform: translate(0, 10px) scale(0.6); }
  15%  { opacity: 1; transform: translate(-6px, 2px) scale(1.05); }
  35%  { opacity: 1; transform: translate(8px, -18px) scale(1); }
  55%  { opacity: 0.95; transform: translate(-6px, -36px) scale(1.02); }
  75%  { opacity: 0.8; transform: translate(6px, -54px) scale(1); }
  100% { opacity: 0; transform: translate(0, -78px) scale(0.75); }
}
@keyframes spookyWispRise {
  0%   { opacity: 0; transform: translate(0, 0); }
  20%  { opacity: 0.9; }
  60%  { transform: translate(var(--sgWobble), calc(var(--sgEndY) * 0.55)); opacity: 0.6; }
  100% { opacity: 0; transform: translate(0, var(--sgEndY)); }
}

/* Holy revival animation for Resuscitation Potion */
@keyframes holyRayGrow {
  0% { opacity: 0; transform: scaleY(0); }
  30% { opacity: .9; }
  100% { opacity: 0; transform: scaleY(1); }
}
@keyframes holySparkleRise {
  0% { opacity: 0; transform: translateY(0) scale(0.3); }
  20% { opacity: 1; transform: translateY(-10px) scale(1); }
  100% { opacity: 0; transform: translateY(-100px) scale(0.2); }
}

@keyframes musicNoteFloat {
  0% { opacity: 0; transform: translate(0, 0) scale(0.3) rotate(0deg); }
  15% { opacity: var(--noteOpacity, 0.8); transform: translate(calc(var(--endX) * 0.15), calc(var(--endY) * 0.1)) scale(1.1) rotate(-5deg); }
  50% { opacity: var(--noteOpacity, 0.8); transform: translate(calc(var(--endX) * 0.5), calc(var(--endY) * 0.5)) scale(1) rotate(5deg); }
  100% { opacity: 0; transform: translate(var(--endX), var(--endY)) scale(0.5) rotate(-10deg); }
}

/* abilityAttach prompt: hand card highlight for attachable abilities */
.hand-card-attach-eligible {
  border: 2px solid rgba(100,220,150,.9) !important;
  box-shadow: 0 0 12px rgba(100,220,150,.6) !important;
  cursor: grab !important;
}
.hand-card-attach-dimmed {
  opacity: 0.3 !important;
  filter: saturate(0.1) brightness(0.6) !important;
  pointer-events: none !important;
}

/* Ability activation flash — very visible to both players */
.zone-ability-activated {
  border-color: #fff !important;
  box-shadow: 0 0 30px rgba(255,230,100,1), 0 0 60px rgba(255,200,50,.7), 0 0 90px rgba(255,170,0,.4), inset 0 0 20px rgba(255,255,255,.5) !important;
  animation: abilityActivateFlash 0.4s ease-out 3 !important;
  z-index: 20 !important;
}
.zone-ability-activated::before {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 6px;
  border: 3px solid rgba(255,255,255,.8);
  animation: abilityActivateRing 0.6s ease-out 2;
  pointer-events: none;
}
@keyframes abilityActivateFlash {
  0% { background: rgba(255,230,100,.6); transform: scale(1.15); }
  50% { background: rgba(255,255,200,.3); transform: scale(1.05); }
  100% { background: transparent; transform: scale(1); }
}
@keyframes abilityActivateRing {
  0% { transform: scale(0.8); opacity: 1; border-color: #fff; }
  100% { transform: scale(1.4); opacity: 0; border-color: rgba(255,200,50,0); }
}

/* Ability BLOCK flash — same shape as activation flash, but pure white
   so the player can tell at a glance that an effect was just absorbed
   (Resistance, etc.) rather than activated. */
/* Boris, the Guardian of Blackport sperrt diese Ability dauerhaft.
   Bewusst NICHT `zone-ability-blocked` — das ist eine 0,4s-Blitz-
   Animation fuer den Moment der Blockade, kein Dauerzustand. */
/* Energieblase fuer `projectileShape: 'bubble'` (Energy Drain).
   Farbe kommt per `emojiStyle` von der Karte, damit die Form fuer
   andere Effekte wiederverwendbar bleibt. */
.projectile-bubble {
  display: block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%,
    rgba(255,255,255,.95) 0 12%,
    var(--bubble-hi, #9dffbe) 14%,
    var(--bubble-mid, #3ef07a) 55%,
    var(--bubble-lo, #14a34a) 100%);
  box-shadow: 0 0 8px var(--bubble-glow, rgba(62,240,122,.85)),
              inset 0 -2px 4px rgba(0,0,0,.35);
}

/* Zonen-Badge (Doom Clock: Zaehlerstand). Sitzt oben auf der Zone,
   faengt keine Klicks ab. */
/* Zusatzzeile im Karten-Tooltip (Doom Clock: Zaehlerstand). */
.tooltip-doom-counters {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid rgba(220, 70, 70, .35);
  font-size: 12px;
  line-height: 1.5;
  color: #ffdede;
}
.tooltip-doom-counters b { color: #ff8f8f; }

.board-zone-badge {
  position: absolute;
  /* Unten an der Karte, aber INNERHALB — bei -8px stand es ueber den
     Zonenrand hinaus und wurde vom Zonen-Clipping abgeschnitten
     (Als Befund 5.8.). */
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 12;
  padding: 1px 7px;
  border-radius: 9px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .3px;
  white-space: nowrap;
  color: #ffdede;
  background: rgba(30, 6, 8, .92);
  border: 1px solid rgba(220, 70, 70, .75);
  box-shadow: 0 0 8px rgba(220, 60, 60, .55);
  pointer-events: none;
}

.zone-ability-boris-blocked { cursor: not-allowed !important; }
/* Exakt wie eine unspielbare HANDKARTE: der Schleier liegt auf der
   KARTE (.board-card), nicht auf der Zone. Ein Pseudo-Element auf der
   Zone selbst erbt deren Rahmen-Animationen und rotiert mit der
   3D-Perspektive mit — das ergab einen pulsierenden, sich drehenden
   Extra-Layer ueber der Karte statt einer gedimmten Karte. */
.zone-ability-boris-blocked .board-card::before {
  content: '';
  position: absolute;
  inset: 0;
  /* Schwaecher als bei Handkarten (dort 0.7): die Ability-Zone ist
     ohnehin dunkler unterlegt, der Schleier addierte sich sonst und
     wirkte deutlich finsterer als eine gedimmte Handkarte. */
  background: rgba(12, 12, 20, 0.45);
  z-index: 3;
  pointer-events: none;
}
.zone-ability-boris-blocked .board-card-hp { opacity: .3; }

.zone-ability-blocked {
  border-color: #fff !important;
  box-shadow: 0 0 30px rgba(255,255,255,1), 0 0 60px rgba(220,235,255,.7), 0 0 90px rgba(180,210,255,.4), inset 0 0 20px rgba(255,255,255,.6) !important;
  animation: abilityBlockFlash 0.4s ease-out 3 !important;
  z-index: 20 !important;
}
.zone-ability-blocked::before {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 6px;
  border: 3px solid rgba(255,255,255,.95);
  animation: abilityBlockRing 0.6s ease-out 2;
  pointer-events: none;
}
@keyframes abilityBlockFlash {
  0% { background: rgba(255,255,255,.7); transform: scale(1.15); }
  50% { background: rgba(240,248,255,.4); transform: scale(1.05); }
  100% { background: transparent; transform: scale(1); }
}
@keyframes abilityBlockRing {
  0% { transform: scale(0.8); opacity: 1; border-color: #fff; }
  100% { transform: scale(1.4); opacity: 0; border-color: rgba(255,255,255,0); }
}

/* Deck viewer modal */
.deck-viewer-modal {
  max-width: 520px;
  width: 90vw;
  max-height: 70vh;
  overflow-y: auto;
  padding: 16px;
}
.deck-viewer-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 4px;
}

/* First-choice floating panel */
.game-board-dimmed {
  opacity: .3;
  pointer-events: none;
}
.first-choice-panel {
  position: fixed;
  top: 52.5%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 9600;
  background: var(--bg2);
  border: 2px solid var(--accent);
  box-shadow: 0 8px 40px rgba(0,0,0,.7), 0 0 20px rgba(0,240,255,.15);
  padding: 12px 24px;
  text-align: center;
  /* Als Kosmetik-Ruling: ohne Breitenbegrenzung wächst dieses fixed
     positionierte Panel mit seinem Inhalt — eine lange, einzeilige
     Beschreibung (Sandy Blobs "Choose an opponent target…", Primordiums
     Nachfrage) zog es dadurch über das komplette Kampffeld. Der Deckel
     zwingt den Text in mehrere Zeilen und hält das Panel in lesbarer
     Breite. 620px ≈ 6 Karten-Minis nebeneinander, die Galerie- und
     Zonen-Varianten dieses Panels passen also weiterhin hinein; die
     große Deck-Ansicht nutzt ohnehin ein eigenes Modal.
     Gilt für ALLE Prompt-Varianten (32 Verwendungen), nicht nur die
     beiden gemeldeten. */
  max-width: min(620px, 92vw);
  box-sizing: border-box;
  /* Sicherheitsnetz gegen sehr lange Karten-/Spielernamen ohne
     Leerzeichen, die sonst die Breite wieder aufsprengen würden. */
  overflow-wrap: anywhere;
}

/* Damage numbers */
@keyframes damageFloat {
  0% { opacity: 1; transform: translate(-50%, 0) scale(1); }
  15% { opacity: 1; transform: translate(-50%, -10px) scale(1.3); }
  50% { opacity: .9; transform: translate(-50%, -35px) scale(1.5); }
  100% { opacity: 0; transform: translate(-50%, -70px) scale(1.8); }
}
.damage-number {
  position: fixed;
  z-index: 10200;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 20px;
  color: #ff2233;
  text-shadow: 0 0 8px rgba(255,0,0,.7), 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
  pointer-events: none;
  animation: damageFloat 1.6s ease-out forwards;
}
.damage-number.heal-number {
  color: #33dd55;
  text-shadow: 0 0 8px rgba(50,220,80,.7), 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
}

/* Red flash overlay when a hero or creature takes damage. Uses an
   absolutely positioned ::after so we don't perturb the existing
   board-zone background, border, or any inner card layout. */
@keyframes damageFlashRed {
  0%   { opacity: 0; }
  15%  { opacity: 1; }
  100% { opacity: 0; }
}
.board-zone.damage-flashing::after {
  content: '';
  position: absolute;
  inset: 0;
  background-color: rgba(255, 40, 40, 0.55);
  pointer-events: none;
  z-index: 30;
  animation: damageFlashRed 0.45s ease-out forwards;
}

/* Gold gain numbers */
@keyframes goldFloat {
  0% { opacity: 1; transform: translate(-50%, 0) scale(1); }
  15% { opacity: 1; transform: translate(-50%, -20px) scale(1.3); }
  50% { opacity: .9; transform: translate(-50%, -60px) scale(1.5); }
  100% { opacity: 0; transform: translate(-50%, -110px) scale(1.3); }
}
.gold-gain-number {
  position: fixed;
  z-index: 10001;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 38px;
  color: #ffd700;
  text-shadow: 0 0 12px rgba(255,215,0,.8), 3px 3px 0 #000, -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000;
  pointer-events: none;
  animation: goldFloat 1.6s ease-out forwards;
}
@keyframes goldFloatDown {
  0% { opacity: 1; transform: translate(-50%, 0) scale(1); }
  15% { opacity: 1; transform: translate(-50%, 10px) scale(1.3); }
  50% { opacity: .9; transform: translate(-50%, 40px) scale(1.5); }
  100% { opacity: 0; transform: translate(-50%, 80px) scale(1.3); }
}
.gold-gain-down { animation: goldFloatDown 1.6s ease-out forwards; }
.gold-loss-number {
  position: fixed;
  z-index: 10001;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 38px;
  color: #ff3344;
  text-shadow: 0 0 12px rgba(255,50,50,.8), 3px 3px 0 #000, -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000;
  pointer-events: none;
  animation: goldFloat 1.6s ease-out forwards;
}
.gold-loss-down { animation: goldFloatDown 1.6s ease-out forwards; }

/* Level change floating numbers */
.level-change-number {
  position: fixed;
  z-index: 10001;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 14px;
  color: #ffffff;
  text-shadow: 0 0 8px rgba(255,255,255,.6), 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
  pointer-events: none;
  animation: levelFloat 1.5s ease-out forwards;
}
.level-change-negative {
  color: #ff4444;
  text-shadow: 0 0 8px rgba(255,50,50,.6), 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
}
@keyframes levelFloat {
  0% { opacity: 1; transform: translate(-50%, 0) scale(1); }
  15% { opacity: 1; transform: translate(-50%, -8px) scale(1.4); }
  50% { opacity: .9; transform: translate(-50%, -25px) scale(1.2); }
  100% { opacity: 0; transform: translate(-50%, -50px) scale(1); }
}

/* Toughness HP change number above hero */
.toughness-hp-number {
  position: fixed;
  z-index: 10001;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 22px;
  font-weight: 900;
  pointer-events: none;
  animation: toughnessHpFloat 1.6s ease-out forwards;
}
.toughness-hp-up {
  color: #1b7a2e;
  text-shadow: 0 0 12px rgba(30,140,50,.7), 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
}
.toughness-hp-down {
  color: #8b1a1a;
  text-shadow: 0 0 12px rgba(160,30,30,.7), 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
}
@keyframes toughnessHpFloat {
  0% { opacity: 1; transform: translate(-50%, 0) scale(0.8); }
  12% { opacity: 1; transform: translate(-50%, -6px) scale(1.5); }
  40% { opacity: 1; transform: translate(-50%, -20px) scale(1.2); }
  100% { opacity: 0; transform: translate(-50%, -55px) scale(1); }
}

/* Fighting ATK change number above hero */
.fighting-atk-number {
  position: fixed;
  z-index: 10001;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 22px;
  font-weight: 900;
  pointer-events: none;
  animation: toughnessHpFloat 1.6s ease-out forwards;
}
.fighting-atk-up {
  color: #a0a0a0;
  text-shadow: 0 0 10px rgba(160,160,160,.6), 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
}
.fighting-atk-down {
  color: #9a4444;
  text-shadow: 0 0 10px rgba(160,60,60,.5), 2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
}

.game-hand-cards {
  display: flex;
  flex: 1;
  justify-content: center;
  /* Allow vertical overflow so highlight outlines / glow / lift
     transforms (Saint Nicolas escrow glow, hand-pick-selected lift,
     urgent pick pulse, etc.) can extend above the container without
     getting clipped at its top edge. Horizontal compression is
     already handled by the per-slot `flex-shrink: 1` + `min-width`
     below, so the previous `overflow: hidden` blanket clip was
     overzealous — it cut off legitimate visual feedback. */
  overflow: visible;
  padding: 2px 0;
  margin-left: calc(180px * var(--board-scale)); /* Match right margin for symmetric centering */
  margin-right: calc(180px * var(--board-scale)); /* Reserve space for gold display + sort/shuffle buttons */
}
/* Hand cards compress (overlap) when there are too many */
.game-hand-cards .hand-slot {
  flex: 0 1 calc(68px * var(--board-scale)); /* 64px card + 4px spacing, shrinks when needed */
  min-width: calc(18px * var(--board-scale));
}
.game-hand-cards .hand-slot:last-child {
  flex: 0 0 auto; /* Last card never compressed — always fully visible */
}
/* Opponent face-down cards also compress */
.game-hand-cards > .board-card.hand-card {
  flex-shrink: 1;
  min-width: calc(18px * var(--board-scale));
}
.game-hand-cards > .board-card.hand-card:last-child {
  flex-shrink: 0;
}
.game-board {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow: hidden;
  position: relative;
}

/* ═══ Global pixel cursor (menus + battle) ═══
   A chunky, low-res arrow rendered as a real PNG (24x30, 2px "pixels") in
   public/cursor-arrow*.png — generated by scripts/mkcursor.js.
   IMPORTANT: these are PNGs, NOT SVGs — Blink (Chrome/Edge) does NOT support
   SVG data-URI cursors and silently falls back to the keyword. Kept ≤32x32
   (Firefox ignores larger cursors); hotspot = the arrow tip (2,2).
     • body        → default white arrow, cascades by inheritance everywhere.
     • text fields → keep the native I-beam.
     • clickables  → the cyan variant here is only the pre-JS FALLBACK. At
                     runtime applyPixelHoverCursor() (app-main.jsx) recolours
                     the arrow to a light, shining tone of the player's OWN
                     colour and injects <style id="pp-hover-cursor"> that
                     overrides this rule. (!important beats the many base
                     `cursor: pointer` rules regardless of source order;
                     :not(:disabled) preserves the not-allowed cursor.)
     • draggable hand/deck cards keep their grab cursor (not matched here). */
body {
  cursor: url("/cursor-arrow.png?v=4") 2 2, default;
}
input[type="text"], input[type="password"], input[type="email"],
input[type="number"], input[type="search"], input[type="tel"],
input[type="url"], input:not([type]), textarea {
  cursor: url("/cursor-text.png?v=4") 5 9, text;
}
.btn:not(:disabled):not([disabled]),
button:not(:disabled):not([disabled]),
a[href], select, summary,
.board-zone-clickable, .ctx-menu-item:not(.disabled), [class*="clickable"],
[style*="cursor: pointer"], [style*="cursor:pointer"], .pp-hover-pointer {
  cursor: url("/cursor-arrow-cyan.png?v=4") 2 2, pointer !important;
}
/* Pixel-styled versions of the remaining keyword cursors. !important so they
   beat the base rules (one of which — .hand-card-attach-eligible — is itself
   !important) regardless of source order. Hotspots from scripts/mkcursor.js. */
.hand-slot, .deck-drag-slot, .pz-hand-card, .hand-card-attach-eligible {
  cursor: url("/cursor-grab.png?v=4") 10 10, grab !important;
}
.hand-slot:active, .deck-drag-slot:active, .pz-hand-card:active {
  cursor: url("/cursor-grabbing.png?v=4") 10 8, grabbing !important;
}
.btn:disabled, button:disabled, button[disabled],
.ctx-menu-item.disabled, .shop-buy-btn:disabled,
.shop-random-btn:disabled, .menu-deck-card.is-illegal,
[style*="cursor: not-allowed"], [style*="cursor:not-allowed"] {
  cursor: url("/cursor-no.png?v=4") 9 9, not-allowed !important;
}
/* While a card is being dragged (custom JS drag, toggled by JS via the
   pp-dragging-card body class), force the grabbing fist EVERYWHERE so it never
   flickers back to the arrow once the pointer leaves the source slot. The
   class is repeated 3x purely to raise specificity above the (0,3,0) hover
   rule so this always wins. */
body.pp-dragging-card.pp-dragging-card.pp-dragging-card,
body.pp-dragging-card.pp-dragging-card.pp-dragging-card * {
  cursor: url("/cursor-grabbing.png?v=4") 10 8, grabbing !important;
}
.board-util {
  width: var(--sidebar-w);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  padding: 4px;
  gap: 0;
  background: rgba(0,0,0,.2);
  border-right: 1px solid var(--bg4);
}
.board-util-side {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;
  gap: 3px;
  min-height: 0;
}
.board-util-mid {
  height: calc(28px * var(--board-scale));
  flex-shrink: 0;
}
.board-util-spacer {
  flex: 1;
}
.board-util-right {
  border-right: none;
  border-left: 1px solid var(--bg4);
}
.board-center {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: calc(4px * var(--board-scale)) calc(8px * var(--board-scale));
}

/* ═══ Player-hand ambiance: bloom + pixel particles (see HandAmbiance) ═══ */
.hand-ambiance {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* No z-index here on purpose: this wrapper must NOT create a stacking
     context, so .hand-particles' z-index and .hand-bloom's negative
     z-index both resolve against .game-hand-me. */
}
/* BLOOM — player-coloured light rising from the bottom edge of the hand,
   screen-blended over its dark background with a slow breathing pulse.
   z-index:-1 keeps it BEHIND the (statically-positioned) hand cards while
   still sitting above the hand's own background fill. */
.hand-bloom {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120% 150% at 50% 100%,
      color-mix(in srgb, var(--amb-me, #00f0ff) 42%, transparent) 0%, transparent 68%);
  mix-blend-mode: screen;
  opacity: .55;
  animation: hand-bloom-pulse 6s ease-in-out infinite;
}
@keyframes hand-bloom-pulse {
  0%, 100% { opacity: .45; }
  50%      { opacity: .7; }
}
/* PIXEL PARTICLES — pixel motes floating just above the hand cards. No
   overflow clip, so they're free to drift up out of the hand and into the
   board above (the hand row sits at z-index 10000, so they ride over it). */
.hand-particles {
  position: absolute;
  inset: 0;
  z-index: 7;          /* above the hand cards */
  pointer-events: none;
}
.hand-mote {
  position: absolute;
  width: var(--p-size, 3px);
  height: var(--p-size, 3px);
  background: var(--p-color, var(--amb-me, #00f0ff));
  box-shadow: 0 0 6px var(--p-color, var(--amb-me, #00f0ff));
  image-rendering: pixelated;
  opacity: 0;
  transform: translate(0, 0) scale(.6);
  animation: hand-mote-float var(--p-dur, 8s) ease-in-out var(--p-delay, 0s) infinite;
  will-change: transform, opacity;
}
@keyframes hand-mote-float {
  0%   { opacity: 0; transform: translate(0, 0) scale(.5); }
  18%  { opacity: var(--p-max, .4); }
  50%  { transform: translate(var(--p-dx, 0), var(--p-dy, -30px)) scale(1); }
  82%  { opacity: var(--p-max, .4); }
  100% { opacity: 0; transform: translate(calc(var(--p-dx, 0) * 1.6), calc(var(--p-dy, -30px) * 1.8)) scale(.7); }
}
@media (prefers-reduced-motion: reduce) {
  .hand-bloom { animation: none; opacity: .55; }
  .hand-mote  { animation: none; opacity: calc(var(--p-max, .4) * .5); transform: none; }
}

/* ═══ Battlefield ambiance: rising pixel motes behind the zones ═══════
   Same pixel-mote language as the hand (shares the hand-mote-float
   keyframes), mounted as the FIRST child of .board-center at z-index 0
   so all zones / cards / area overlays paint above it. Deliberately
   dimmer + slower than the hand layer — it is a background, not an
   effect. See BoardAmbiance in app-board.jsx. */
.board-ambiance {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.board-mote {
  position: absolute;
  width: var(--p-size, 3px);
  height: var(--p-size, 3px);
  background: var(--p-color, #ffffff);
  box-shadow: 0 0 6px var(--p-color, #ffffff);
  image-rendering: pixelated;
  opacity: 0;
  transform: translate(0, 0) scale(.6);
  animation: hand-mote-float var(--p-dur, 8s) ease-in-out var(--p-delay, 0s) infinite;
  will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
  .board-mote { animation: none; opacity: calc(var(--p-max, .3) * .5); transform: none; }
}
.board-player-side {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  min-height: 0;
}
.board-permanents {
  position: absolute;
  /* Pull the column closer to the playfield — was 8px, but with the */
  /* Coolness Stack now also living in this column the visual gap to  */
  /* the rest of the board read as too wide. */
  right: 26px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 50;
  pointer-events: auto;
}
.board-permanents-opp { bottom: 52%; }
.board-permanents-me { top: 52%; }

/* Coolness Stack slot — sits below the last permanent in the same column. */
/* Distinct cyan/blue glow vs. the permanents' gold to read at a glance. */
.board-coolness-slot {
  position: relative;
  border: 2px solid rgba(120,210,255,.55);
  border-radius: 6px;
  box-shadow: 0 0 12px rgba(120,210,255,.3), inset 0 0 8px rgba(120,210,255,.15);
  padding: 2px;
  cursor: pointer;
  animation: coolnessPulse 3.2s ease-in-out infinite;
}
@keyframes coolnessPulse {
  0%, 100% { box-shadow: 0 0 12px rgba(120,210,255,.3), inset 0 0 8px rgba(120,210,255,.15); }
  50%      { box-shadow: 0 0 22px rgba(150,225,255,.5), inset 0 0 12px rgba(150,225,255,.2); }
}
.board-coolness-slot.potion-target-valid {
  border-color: rgba(255,80,80,.95) !important;
  box-shadow: 0 0 16px rgba(255,80,80,.8), inset 0 0 12px rgba(255,80,80,.3) !important;
}
.board-coolness-slot.potion-target-selected {
  border-color: rgba(255,200,80,1) !important;
  box-shadow: 0 0 18px rgba(255,200,80,.9), inset 0 0 14px rgba(255,200,80,.4) !important;
}
/* Top of the Stack is currently playable — a stronger gold pulse to */
/* draw the eye away from the regular cyan idle state. */
.board-coolness-slot.coolness-playable {
  border-color: rgba(255,215,0,.95) !important;
  animation: coolnessPlayablePulse .9s ease-in-out infinite !important;
}
.board-coolness-slot.coolness-playable::after {
  content: '🆒';
  position: absolute;
  top: -10px; right: -8px;
  font-size: 18px;
  filter: drop-shadow(0 0 4px rgba(255,215,0,.9));
  pointer-events: none;
}
@keyframes coolnessPlayablePulse {
  0%,100% { box-shadow: 0 0 16px rgba(255,215,0,.7), inset 0 0 10px rgba(255,215,0,.3); }
  50%     { box-shadow: 0 0 26px rgba(255,235,80,.9), inset 0 0 14px rgba(255,235,80,.4); }
}
.board-permanent-slot {
  position: relative;
  border: 2px solid rgba(255,215,0,.4);
  border-radius: 6px;
  box-shadow: 0 0 12px rgba(255,215,0,.2), inset 0 0 8px rgba(255,215,0,.1);
  padding: 2px;
  animation: permanentPulse 3s ease-in-out infinite;
}
@keyframes permanentPulse {
  0%, 100% { box-shadow: 0 0 12px rgba(255,215,0,.2), inset 0 0 8px rgba(255,215,0,.1); }
  50% { box-shadow: 0 0 20px rgba(255,215,0,.35), inset 0 0 12px rgba(255,215,0,.15); }
}
.zone-permanent-activatable {
  border-color: rgba(255,215,0,.9) !important;
  box-shadow: 0 0 16px rgba(255,215,0,.8), 0 0 32px rgba(255,180,0,.4), inset 0 0 12px rgba(255,215,0,.3) !important;
  animation: permanentActivatablePulse 1s ease-in-out infinite !important;
}
.zone-permanent-activatable::after {
  content: '⚖';
  position: absolute;
  top: -8px;
  right: -8px;
  font-size: 14px;
  color: #ffd700;
  filter: drop-shadow(0 0 4px rgba(255,215,0,.9));
  animation: abilityBoltBounce 0.8s ease-in-out infinite alternate;
  z-index: 10;
}
@keyframes permanentActivatablePulse {
  0%, 100% { box-shadow: 0 0 16px rgba(255,215,0,.8), 0 0 32px rgba(255,180,0,.4), inset 0 0 12px rgba(255,215,0,.3); border-color: rgba(255,215,0,.9); }
  50% { box-shadow: 0 0 24px rgba(255,215,0,1), 0 0 48px rgba(255,180,0,.6), inset 0 0 18px rgba(255,215,0,.5); border-color: rgba(255,230,100,1); }
}
.board-row {
  display: flex;
  justify-content: center;
  gap: var(--zone-gap);
  align-items: center;
}
.board-area-between {
  display: flex;
  align-items: center;
  margin: 0 calc(-8px * var(--board-scale));
}
.board-area-zones-center {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 100%;
  height: 0;
  pointer-events: none;
  /* z-index: 10 lifts the area zones above sibling board rows (static
     flex children of .board-center that otherwise paint over the area
     zones' lower half, eating hover events on the bottom of the card).
     The Deepsea Castle mood-lighting split now lives at z-index auto
     (darkness) + z-index 1000 (rays), so area zones at 10 correctly
     sit BETWEEN the darkness and the rays and are no longer trapped
     above the Castle effect. */
  z-index: 10;
}
.board-area-zones-center .board-zone {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: auto;
}
.board-area-spacer {
  width: var(--spacer-w);
  margin: 0 calc(-8px * var(--board-scale));
}
.board-hero-group {
  display: flex;
  gap: calc(4px * var(--board-scale));
}
.board-zone-spacer {
  width: var(--spacer-w);
  flex-shrink: 0;
}
.board-mid-row {
  display: flex;
  align-items: center;
  gap: calc(12px * var(--board-scale));
  padding: calc(4px * var(--board-scale)) 0;
  flex-shrink: 0;
}
.board-phase-tracker {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}
.board-phase-item {
  font-family: var(--font-display), sans-serif;
  font-size: var(--board-font);
  font-weight: 700;
  color: rgba(255,255,255,.4);
  padding: calc(5px * var(--board-scale)) calc(10px * var(--board-scale));
  background: rgba(255,255,255,.03);
  border: 1px solid rgba(255,255,255,.08);
  white-space: nowrap;
  transition: all .2s;
}
.board-phase-item.active {
  color: #fff;
  background: rgba(255,255,255,.08);
  text-shadow: 0 0 8px rgba(255,255,255,.4);
}
.board-phase-item.clickable {
  cursor: pointer;
  color: rgba(255,255,255,.7);
  border-style: dashed;
}
.board-phase-item.clickable:hover {
  color: #fff;
  background: rgba(255,255,255,.06);
  border-style: solid;
}

/* Gold display — fixed position, same x for both players */
.game-gold-display {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 3px;
  z-index: 10002;
}
.game-gold-icon { font-size: calc(32px * var(--board-scale)); }
.game-gold-value {
  font-size: calc(30px * var(--board-scale));
  font-weight: 800;
  color: #ffd700;
  text-shadow: 0 0 10px rgba(255,215,0,.5);
}

/* Hand action buttons (Sort / Shuffle) — fixed position, left of gold */
.hand-actions {
  position: absolute;
  right: calc(116px * var(--board-scale));
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: calc(6px * var(--board-scale));
  z-index: 10002;
}
.hand-action-btn {
  padding: calc(6px * var(--board-scale)) calc(16px * var(--board-scale)) !important;
  font-size: calc(12px * var(--board-scale)) !important;
  letter-spacing: 0.5px;
  opacity: 0.8;
  transition: opacity .15s;
}
.hand-action-btn:hover { opacity: 1; }
/* Vertical divider separating the hand cards from the Sort/Shuffle column. */
.hand-actions::before {
  content: '';
  position: absolute;
  left: calc(-12px * var(--board-scale));
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: calc(var(--hand-min-h) * 0.78);
  background: linear-gradient(to bottom,
    transparent 0%,
    color-mix(in srgb, var(--hand-accent, #00f0ff) 60%, transparent) 16%,
    color-mix(in srgb, var(--hand-accent, #00f0ff) 60%, transparent) 84%,
    transparent 100%);
}

/* Card dimming — uses overlay, not opacity, so tooltip is unaffected */
.hand-card-dimmed .board-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(12, 12, 20, 0.7);
  z-index: 3;
  pointer-events: none;
}
.hand-card-dimmed .board-card {
  /* Unplayable cards are still draggable (reorder / attempt), so keep the
     grab hand like any hand card — inherit the slot's grab cursor. */
  cursor: inherit;
}
.hand-card-dimmed .board-card-hp { opacity: .3; }

/* Hand-lock blocked indicator (Kazena draw lock) */
.hand-lock-indicator {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 48px;
  color: #ff4444;
  z-index: 5;
  pointer-events: none;
  text-shadow: 0 0 12px rgba(255, 0, 0, .6), 0 0 24px rgba(255, 0, 0, .3);
  filter: drop-shadow(0 2px 4px rgba(0,0,0,.8));
  line-height: 1;
}

/* Hand card revealed (Luna Kiai's reveal-to-Burn, etc.) — the owner
   sees the copy they spent semi-transparent so it's clear which card
   already triggered its activation this turn. Opponent sees the reveal
   through the normal face-up `revealedHandCards` flow. */
.hand-slot.hand-card-revealed .board-card {
  opacity: 0.55;
  filter: saturate(0.75);
}
.hand-slot.hand-card-revealed::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 2px dashed rgba(255, 215, 70, .85);
  border-radius: 4px;
  pointer-events: none;
}

/* Permanently revealed hand card — opp-side cards mechanically
   revealed by the rules (Treacherous Crystal in hand, Crystal-Well
   transfers routed through revealOnEnterHand, Bamboo Shield, etc.).
   In normal play the face-up vs face-down contrast already signals
   "revealed", but puzzle mode flattens that by exposing every slot —
   this golden glow is the surviving distinguisher. Server tags
   eligible slots with `permanent: true` on the revealedHandCards
   entry; the client paints this class on top of `revealed-hand-card`. */
.board-card.hand-card.hand-card-perma-revealed {
  box-shadow: 0 0 10px rgba(255, 215, 70, .8), 0 0 18px rgba(255, 180, 30, .4);
  border: 2px solid rgba(255, 215, 70, .9);
  border-radius: 6px;
}

/* Treacherous Crystal click affordance — paints the Crystal as
   clickable while it's the active player's turn AND at least one
   opp Creature is still stealable (un-stolen + not Cardinal-immune).
   Pink-magenta pulse matches the cross-side charmed/control visual
   (charmed hero outline, spellHeroPick panel border). Click emits
   `trigger_treacherous_crystal` server-side, which flips `stolenBy`
   on every eligible opp Creature for the rest of this turn. The
   pulse fades after the trigger since no further opp Creatures are
   stealable, giving a natural "click used" cue. */
.board-card.hand-card.hand-card-treacherous-click {
  cursor: pointer;
  animation: treacherousCrystalPulse 1.4s ease-in-out infinite;
}
@keyframes treacherousCrystalPulse {
  0%, 100% { box-shadow: 0 0 10px rgba(255, 105, 180, .60), 0 0 20px rgba(255, 60, 140, .30); }
  50%      { box-shadow: 0 0 18px rgba(255, 105, 180, .95), 0 0 34px rgba(255, 60, 140, .60); }
}


/* Force discard target — thick red border on hand cards, hover dims */
.hand-discard-target {
  cursor: pointer !important;
}
.hand-discard-target .board-card {
  box-shadow: 0 0 10px rgba(255,60,60,.6), 0 0 20px rgba(255,40,40,.3) !important;
  border: 3px solid rgba(255,60,60,.85) !important;
  animation: discardPulse 0.8s ease-in-out infinite;
  transition: filter .15s ease, transform .15s ease;
}
.hand-discard-target:hover .board-card {
  filter: brightness(0.4) grayscale(0.5);
  transform: scale(0.96);
  box-shadow: 0 0 16px rgba(255,40,40,.9), 0 0 30px rgba(255,20,20,.5) !important;
}
@keyframes discardPulse {
  0%, 100% { box-shadow: 0 0 10px rgba(255,60,60,.6), 0 0 20px rgba(255,40,40,.3); }
  50% { box-shadow: 0 0 16px rgba(255,60,60,.8), 0 0 28px rgba(255,40,40,.5); }
}
.board-area-line {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  opacity: .4;
}
.board-zone {
  width: var(--zone-w);
  height: var(--zone-h);
  border: 2px solid var(--bg4);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  flex-shrink: 0;
  overflow: hidden;
  background-color: rgba(0,0,0,.3);
}
.board-zone-hero { border-color: rgba(140,80,220,.5); background-color: rgba(140,80,220,.08); }
.board-zone-surprise { border-color: rgba(200,50,50,.5); background-color: rgba(200,50,50,.08); }

/* Surprise flip animation */
.surprise-flipping {
  animation: surprise-flip-anim 0.8s ease-out;
}
@keyframes surprise-flip-anim {
  0% { transform: rotateY(0deg) scale(1); }
  30% { transform: rotateY(90deg) scale(1.2); filter: brightness(2); }
  60% { transform: rotateY(180deg) scale(1.3); filter: brightness(2.5); box-shadow: 0 0 30px rgba(255,100,50,.8); }
  100% { transform: rotateY(360deg) scale(1); filter: brightness(1); box-shadow: none; }
}

/* Own face-down surprise: semi-transparent to indicate hidden from opponent */
.surprise-own-facedown .board-card,
.surprise-own-facedown .board-zone-empty { opacity: 0.45; }

/* ── Kassaran reveal flip ─────────────────────────────────────────────
   Top card of the deck flies to screen center, flips face-up, holds,
   then continues to the hand (match) or back to the deck top (miss).
   Coordinates are passed in as CSS variables on the wrapper:
     --sx/--sy = start (deck pile center)
     --cx/--cy = stage 2 (viewport center)
     --ex/--ey = end (hand slot or deck pile)
   The wrapper drives flight via `left/top` keyframes; the inner
   element flips via a separate rotateY animation at the same total
   duration so the flip lands during the center-hold phase. */
.kassaran-flip-card {
  position: fixed;
  width: 64px;
  height: 90px;
  pointer-events: none;
  z-index: 10100;
  perspective: 800px;
  /* --kfDur is set inline by KassaranFlipCard when a caller wants a
     faster reveal (mill_center_reveal); everyone else keeps 2000ms. */
  animation: kassaran-flight var(--kfDur, 2000ms) cubic-bezier(.4,.1,.3,1) forwards;
}
.kassaran-flip-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  animation: kassaran-flip var(--kfDur, 2000ms) ease-in-out forwards;
}
.kassaran-flip-back,
.kassaran-flip-front {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 8px 32px rgba(0,0,0,.6);
}
.kassaran-flip-front {
  transform: rotateY(180deg);
}
.kassaran-flip-back img,
.kassaran-flip-front img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
@keyframes kassaran-flight {
  0%   { left: var(--sx); top: var(--sy); transform: scale(1); }
  25%  { left: var(--cx); top: var(--cy); transform: scale(2.6); }
  35%  { left: var(--cx); top: var(--cy); transform: scale(2.6); }
  75%  { left: var(--cx); top: var(--cy); transform: scale(2.6); }
  100% { left: var(--ex); top: var(--ey); transform: scale(1); }
}
@keyframes kassaran-flip {
  0%   { transform: rotateY(0deg); }
  25%  { transform: rotateY(0deg); }
  35%  { transform: rotateY(180deg); }
  100% { transform: rotateY(180deg); }
}

/* Birthday Present three-card reveal & pick — phased visual:
   Phase 1: slide-in from above (staggered ~250ms per card).
   Phase 2: flip face-up (delay-chained off slide-in end).
   Phase 3: hold at viewport centre waiting for opp's click.
   Phase 4a: chosen card pulses with a 200ms gold glow.
   Phase 4b: chosen + others fly to their target hand slots
             (server provides `--ex` / `--ey` per card).
   The outer `.bday-present-reveal-card` element handles position +
   slide-in + fly-out keyframes; the inner element owns the flip and
   the highlight glow so the two transform stacks don't fight. */
.bday-present-reveal-card {
  position: fixed;
  width: 110px;
  height: 154px;
  z-index: 10100;
  perspective: 1000px;
  pointer-events: none;
  /* `--idx` is -1 / 0 / 1 — horizontal slot around viewport centre.
     `--delay` staggers each card's slide-in. The CSS variables `--ex`
     / `--ey` are populated by the server's `pick_resolved` event when
     the fly-out class is added. */
  --cx: calc(50vw - 55px + var(--idx) * 130px);
  --cy: calc(50vh - 77px);
  left: var(--cx);
  top: var(--cy);
  animation: bday-present-slide-in 600ms cubic-bezier(.2,.7,.3,1) var(--delay, 0ms) backwards;
}
.bday-present-reveal-card.clickable {
  pointer-events: auto;
  cursor: pointer;
}
/* Hover affordance for the opponent's pick. MUST NOT touch
   `.bday-present-reveal-inner` — it is `transform-style: preserve-3d`
   and applying `filter`/`transform` there flattens the 3D flip,
   exposing the back face (the card appears to flip face-DOWN on
   hover). Instead: grow the outer (non-3D) card and glow the visible
   front face only, so the offered Spell stays face-up. */
.bday-present-reveal-card.clickable:hover {
  transform: scale(1.06);
  transition: transform 180ms ease-out;
  z-index: 10110;
}
.bday-present-reveal-card.clickable:hover .bday-present-reveal-front {
  filter: brightness(1.18) drop-shadow(0 0 14px rgba(255,230,140,.85));
  transition: filter 180ms ease-out;
}
.bday-present-reveal-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  /* Flip after slide-in completes (delay = slide-in duration 600ms +
     100ms breath). End state stays at rotateY(180deg) showing the
     front face. */
  animation: bday-present-flip 600ms cubic-bezier(.3,.1,.3,1) calc(var(--delay, 0ms) + 700ms) forwards;
  transform: rotateY(0deg);
}
.bday-present-reveal-back,
.bday-present-reveal-front {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 16px 44px rgba(0,0,0,.75);
}
.bday-present-reveal-front {
  transform: rotateY(180deg);
}
.bday-present-reveal-back img,
.bday-present-reveal-front img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Phase 4a: chosen card highlight. The inner stack runs the flip's
   forwards-state PLUS a layered 200ms glow keyframe. `chosen` lands
   long after the flip's delay has elapsed, so chaining the original
   flip animation is no-op cosmetically (it stays at rotateY(180)). */
.bday-present-reveal-card.chosen .bday-present-reveal-inner {
  animation:
    bday-present-flip 600ms cubic-bezier(.3,.1,.3,1) calc(var(--delay, 0ms) + 700ms) forwards,
    bday-present-highlight 200ms ease-in-out forwards;
}
@keyframes bday-present-highlight {
  0%   { filter: brightness(1) drop-shadow(0 0 0 transparent);
         transform: rotateY(180deg) scale(1); }
  50%  { filter: brightness(1.45) drop-shadow(0 0 28px rgba(255,210,80,1));
         transform: rotateY(180deg) scale(1.18); }
  100% { filter: brightness(1.25) drop-shadow(0 0 18px rgba(255,210,80,.9));
         transform: rotateY(180deg) scale(1.12); }
}
/* Phase 4b: fly-out. Outer element animates left/top toward (--ex, --ey).
   Overrides the slide-in animation on the outer (the inner's flip
   anim is separate and stays). The per-flight `--flight-delay` lets
   the second activator-bound card wait its turn (so the two
   user-bound cards land "one by one" instead of together). */
.bday-present-reveal-card.flying-out {
  animation: bday-present-fly-out 700ms cubic-bezier(.4,.1,.3,1) var(--flight-delay, 0ms) forwards;
}
/* Slide-in originates at the OWNER's deck pile (--sx, --sy captured
   client-side at event time) and lands at the centre slot (--cx, --cy).
   Animating `left` / `top` between two var() values is well-supported
   in Chromium/Firefox/Safari. The `backwards` fill mode on the outer
   keeps the element parked at the deck during the per-card delay so
   each subsequent card starts at the deck position (not whatever the
   first card's mid-flight position happened to be). */
@keyframes bday-present-slide-in {
  0%   { opacity: 0; left: var(--sx); top: var(--sy); transform: scale(0.35); }
  60%  { opacity: 1; }
  100% { opacity: 1; left: var(--cx); top: var(--cy); transform: scale(1); }
}
@keyframes bday-present-flip {
  0%   { transform: rotateY(0deg); }
  100% { transform: rotateY(180deg); }
}
@keyframes bday-present-fly-out {
  0%   { left: var(--cx); top: var(--cy); transform: scale(1); opacity: 1; }
  100% { left: var(--ex); top: var(--ey); transform: scale(0.55); opacity: 0.7; }
}

/* Timeless King Zi — the opponent-chosen Spell grows large and slowly
   vanishes as the Hero casts it. Outer card scales + fades in place
   (stays at its --cx/--cy slot); the inner keeps its face-up flip. A
   warm time-magic glow sells the "being cast" beat. `tk-casting` is
   set only by Zi (castMode) — Birthday Present is unaffected. */
.bday-present-reveal-card.tk-casting {
  animation: tk-zi-cast 1400ms cubic-bezier(.3,.05,.4,1) forwards;
  z-index: 10120;
}
.bday-present-reveal-card.tk-casting .bday-present-reveal-inner {
  box-shadow: 0 0 26px rgba(120,210,255,.75), 0 0 60px rgba(90,160,255,.5);
  border-radius: 8px;
}
@keyframes tk-zi-cast {
  0%   { transform: scale(1);    opacity: 1;   filter: brightness(1); }
  35%  { transform: scale(1.55); opacity: 1;   filter: brightness(1.35); }
  100% { transform: scale(2.6);  opacity: 0;   filter: brightness(1.7); }
}

/* Brackle's catapult — five-phase flying Creature card. THREE chained
   CSS animations cover the full visual; the implicit `forwards`-filled
   gaps between them are the HOLD (creature parked on Brackle) and
   IMPACT (creature parked at target while the explosion plays) phases:
     1. `brackle-catapult-load` (duration `--load-ms`) interpolates
        source slot (--sx,--sy) → Brackle's hero zone (--mx,--my).
        `forwards` fill parks the card on Brackle.
     2. `brackle-catapult-fire` (duration `--fire-ms`, delay
        `--fire-delay` = load + hold) interpolates Brackle → target
        (--ex,--ey). `forwards` fill parks the card on the target for
        the IMPACT phase.
     3. `brackle-catapult-discard` (duration `--discard-ms`, delay
        `--discard-delay` = load + hold + fire + impact) interpolates
        target → the discard pile (--dx,--dy). The corpse's actual
        flight to discard — replaces the diff-detector's auto-flight
        (which is pre-suppressed client-side so it doesn't double-fire).
   `pointer-events: none` + `z-index: 10100` match the other flying-
   card animations (Kassaran, Colored Snow, Birthday Present). */
.brackle-catapult-card {
  position: fixed;
  width: 64px;
  height: 90px;
  pointer-events: none;
  z-index: 10100;
  left: var(--sx);
  top:  var(--sy);
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .65);
  animation:
    brackle-catapult-load    var(--load-ms,    1100ms) cubic-bezier(.2, .65, .35, 1) forwards,
    /* Fire phase intentionally snappy: steep cubic-bezier accelerates
       hard off Brackle, fast tail-end, no settling — the projectile
       just leaves. Default fire-ms matches the JS constant (300ms). */
    brackle-catapult-fire    var(--fire-ms,    300ms)  cubic-bezier(.7, 0, .85, .35) var(--fire-delay,    1500ms) forwards,
    /* Discard phase — the corpse curves down to the discard pile,
       shrinking and tumbling. Slower / softer easing than fire so it
       reads as "thrown away" rather than "second catapult". */
    brackle-catapult-discard var(--discard-ms, 500ms)  cubic-bezier(.4, 0, .3, 1)    var(--discard-delay, 2100ms) forwards;
}
.brackle-catapult-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
@keyframes brackle-catapult-load {
  0%   { left: var(--sx); top: var(--sy); transform: scale(1)    rotate(0deg); }
  100% { left: var(--mx); top: var(--my); transform: scale(1.15) rotate(-4deg); }
}
@keyframes brackle-catapult-fire {
  0%   { left: var(--mx); top: var(--my); transform: scale(1.15) rotate(-4deg); opacity: 1; }
  20%  {                                  transform: scale(1.22) rotate(-2deg); }
  100% { left: var(--ex); top: var(--ey); transform: scale(0.95) rotate(8deg);  opacity: 1; }
}
@keyframes brackle-catapult-discard {
  0%   { left: var(--ex); top: var(--ey); transform: scale(0.95) rotate(8deg);   opacity: 1; }
  100% { left: var(--dx); top: var(--dy); transform: scale(0.45) rotate(180deg); opacity: 0.2; }
}

/* Colored Snow's Potion reveal — two-phase animation. Phase 1 flies
   the Potion from the controller's Potion Deck to the center of the
   screen face-down, then dramatically flips face-up and HOLDS at the
   center for the duration of the Potion's resolve (which may include
   targeting prompts). Phase 2, triggered when the script adds the
   `.exiting` class to the same DOM element, flies the card from the
   center to its post-resolve destination (deleted pile, Support Zone
   for Biomancy, opp hand for Saint Nicolas, or discard). */
.colored-snow-reveal-card {
  position: fixed;
  width: 64px;
  height: 90px;
  pointer-events: none;
  z-index: 10100;
  perspective: 800px;
  animation: colored-snow-reveal-in 1500ms cubic-bezier(.4,.1,.3,1) forwards;
}
.colored-snow-reveal-card.exiting {
  animation: colored-snow-reveal-out 500ms ease-in forwards;
}
/* Handoff exit — used when another animation (Saint Nicolas's
   play_pile_transfer) owns the actual flight to the destination.
   The reveal card just fades + shrinks slightly in place so the
   pile-transfer flight is the only thing the player tracks. */
.colored-snow-reveal-card.handoff {
  animation: colored-snow-reveal-handoff 350ms ease-out forwards;
}
@keyframes colored-snow-reveal-handoff {
  0%   { left: var(--cx); top: var(--cy); transform: scale(2.6); opacity: 1; }
  100% { left: var(--cx); top: var(--cy); transform: scale(2.2); opacity: 0; }
}
.colored-snow-reveal-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  animation: colored-snow-reveal-flip 1500ms ease-in-out forwards;
}
.colored-snow-reveal-back,
.colored-snow-reveal-front {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 8px 32px rgba(0,0,0,.6);
}
.colored-snow-reveal-front {
  transform: rotateY(180deg);
}
.colored-snow-reveal-back img,
.colored-snow-reveal-front img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
@keyframes colored-snow-reveal-in {
  0%   { left: var(--sx); top: var(--sy); transform: scale(1); }
  40%  { left: var(--cx); top: var(--cy); transform: scale(2.6); }
  100% { left: var(--cx); top: var(--cy); transform: scale(2.6); }
}
@keyframes colored-snow-reveal-flip {
  0%   { transform: rotateY(0deg); }
  60%  { transform: rotateY(0deg); }
  80%  { transform: rotateY(180deg); }
  100% { transform: rotateY(180deg); }
}
@keyframes colored-snow-reveal-out {
  0%   { left: var(--cx); top: var(--cy); transform: scale(2.6); opacity: 1; }
  100% { left: var(--ex); top: var(--ey); transform: scale(1); opacity: .4; }
}

/* Opponent's known re-set surprise: semi-transparent face-up card */
.surprise-known-facedown .board-card { opacity: 0.45; filter: sepia(0.3); }

/* Surprise re-setting animation: flash + glow */
.surprise-resetting {
  animation: surprise-reset-anim 1.2s ease-out;
}
@keyframes surprise-reset-anim {
  0% { filter: brightness(1); transform: scale(1); }
  20% { filter: brightness(2.5) sepia(0.5); transform: scale(1.15); box-shadow: 0 0 25px rgba(210,170,80,.8); }
  50% { filter: brightness(1.8) sepia(0.3); transform: scale(1.05); box-shadow: 0 0 15px rgba(210,170,80,.5); }
  100% { filter: brightness(1); transform: scale(1); box-shadow: none; }
}

/* Surprise zone drag highlights — prominent colored borders + glow.
   Uses `outline` (offset inward) instead of `border` so the surrounding
   wrapper element doesn't grow by the border width during drag, which
   would shift the zone a few pixels in its row. Uses `background-color`
   (not the `background` shorthand) so inline backgroundImage skins set
   on per-board zone sprites survive the highlight. */
.surprise-drop-eligible {
  outline: 2px solid rgba(255,140,40,.9) !important;
  outline-offset: -2px;
  background-color: rgba(255,140,40,.15) !important;
  box-shadow: 0 0 14px 3px rgba(255,140,40,.35), inset 0 0 10px rgba(255,140,40,.15);
  animation: surprise-eligible-pulse 1s ease-in-out infinite alternate;
}
@keyframes surprise-eligible-pulse {
  from { box-shadow: 0 0 14px 3px rgba(255,140,40,.35), inset 0 0 10px rgba(255,140,40,.15); }
  to   { box-shadow: 0 0 20px 6px rgba(255,140,40,.5),  inset 0 0 14px rgba(255,140,40,.25); }
}
.surprise-drop-active {
  outline: 2px solid rgba(255,180,50,1) !important;
  outline-offset: -2px;
  background-color: rgba(255,180,50,.25) !important;
  box-shadow: 0 0 24px 6px rgba(255,180,50,.6), inset 0 0 16px rgba(255,180,50,.3);
  transform: scale(1.04);
  transition: transform .1s, box-shadow .1s;
}
.surprise-drop-ineligible {
  opacity: 0.35;
  filter: saturate(0.3);
}

/* Area drag highlight — big golden glow on own Area Zone while dragging
   an Area-subtype card that at least one hero can cast. Uses outline+glow
   so the zone doesn't shift in layout when the class toggles on/off. */
.area-drop-eligible {
  outline: 3px solid rgba(255,210,60,0.95) !important;
  outline-offset: -3px;
  background-color: rgba(255,210,60,0.2) !important;
  box-shadow: 0 0 24px 6px rgba(255,210,60,0.55), inset 0 0 18px rgba(255,220,100,0.35);
  animation: area-drop-pulse 1.1s ease-in-out infinite alternate;
}
@keyframes area-drop-pulse {
  from { box-shadow: 0 0 24px 6px rgba(255,210,60,0.45), inset 0 0 18px rgba(255,220,100,0.3); }
  to   { box-shadow: 0 0 38px 10px rgba(255,220,100,0.7), inset 0 0 24px rgba(255,230,140,0.5); }
}

/* Spider Avalanche animation — torrential downpour of tiny spiders */
.anim-spider-flash {
  position: absolute; left: -40px; top: -20px; width: 80px; height: 80px;
  background: radial-gradient(circle, rgba(80,0,120,.5) 0%, transparent 70%);
  border-radius: 50%;
  animation: spider-flash 0.3s ease-out forwards;
  pointer-events: none;
}
@keyframes spider-flash {
  0% { opacity: 0; transform: scale(0.3); }
  50% { opacity: 1; transform: scale(1.2); }
  100% { opacity: 0; transform: scale(1.5); }
}
.anim-spider-drop {
  position: absolute; left: 0; top: 0;
  font-size: var(--size, 8px);
  opacity: 0;
  pointer-events: none;
  animation: spider-drop-fall forwards ease-in;
  transform: translate(var(--startX), -80px);
  filter: drop-shadow(0 0 2px rgba(80,0,120,.6));
}
@keyframes spider-drop-fall {
  0%   { opacity: 0; transform: translate(var(--startX), -80px) scale(0.5); }
  15%  { opacity: 1; transform: translate(var(--startX), -40px) scale(1); }
  100% { opacity: 0; transform: translate(calc(var(--startX) + var(--drift)), 80px) scale(0.7); }
}
.board-zone-ability { border-color: rgba(50,100,220,.5); background: rgba(50,100,220,.08); }
.board-zone-support { border-color: rgba(220,200,50,.5); background: rgba(220,200,50,.08); }
.board-zone-clickable { cursor: pointer; }
.board-zone-clickable:hover { box-shadow: inset 0 0 12px rgba(255,255,255,.1); }
.board-zone-island { border-color: rgba(50,220,80,.6) !important; background: rgba(50,220,80,.1) !important; }
.board-zone-island .board-zone-empty { color: rgba(50,220,80,.4); }

/* ── Subdued empty per-Hero zones ──────────────────────────────────────
   The per-Hero Support / Ability / Surprise slots otherwise tile the board
   with rows of identical brightly-outlined labelled boxes, which reads as
   noisy clutter — especially to a newcomer. When a slot is EMPTY (it holds
   a .board-zone-empty label rather than a card) we fade its coloured frame
   to a faint neutral outline and hide the type label until hover, so played
   cards pop and the slot's purpose is still one hover away. Filled slots,
   Island Support zones (an active special state) and live play/attach
   targets keep their full styling. */
.board-zone-support:not(.board-zone-island):has(.board-zone-empty),
.board-zone-ability:has(.board-zone-empty),
.board-zone-surprise:has(.board-zone-empty) {
  border-color: rgba(255,255,255,.07);
  background-color: rgba(255,255,255,.012);
}
.board-zone-support:not(.board-zone-island) .board-zone-empty,
.board-zone-ability .board-zone-empty,
.board-zone-surprise .board-zone-empty {
  opacity: 0;
  transition: opacity .15s ease;
}
/* Reveal the slot's type + restore its colour on hover. */
.board-zone-ability:hover:has(.board-zone-empty)  { border-color: rgba(50,100,220,.6); }
.board-zone-support:not(.board-zone-island):hover:has(.board-zone-empty) { border-color: rgba(220,200,50,.6); }
.board-zone-surprise:hover:has(.board-zone-empty) { border-color: rgba(200,50,50,.6); }
.board-zone-support:hover .board-zone-empty,
.board-zone-ability:hover .board-zone-empty,
.board-zone-surprise:hover .board-zone-empty { opacity: .6; }
/* A slot the player is being asked to play/attach into must stay legible. */
.board-zone-play-target .board-zone-empty,
.attach-pick-target .board-zone-empty { opacity: .6 !important; }

/* Creature level counter */
.creature-level {
  position: absolute;
  right: 2px;
  top: 45%;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: calc(9px * var(--board-scale));
  color: #88ddff;
  text-shadow: 0 0 4px #000, 1px 1px 0 #000, -1px -1px 0 #000;
  z-index: 4;
  pointer-events: none;
  line-height: 1;
}
/* Hero-filtered level reduction (Sparkfly Queen's "corresponding Hero
   only" rebate). Dark green to distinguish from the default
   light-blue badge, which represents a reduction available to ANY of
   your Heroes. Re-enables pointer events so the hover-tooltip can
   explain which Hero unlocks the reduced level. */
.creature-level.creature-level-hero-filtered {
  color: #1f7a3a;
  text-shadow: 0 0 4px #000, 1px 1px 0 #000, -1px -1px 0 #000, 0 0 6px rgba(40,200,90,.5);
  pointer-events: auto;
  cursor: help;
}

/* Per-instance Artifact cost-override badge (Play Money). Top-left of
   the card, blue-tinted to match the gold-cost diamond used in
   tooltips. Auto-pointer so the hover-tooltip explains the discount. */
.hand-cost-override {
  position: absolute;
  left: 4px;
  top: 4px;
  padding: 2px 5px;
  background: rgba(20, 30, 60, 0.92);
  border: 1px solid #44aaff;
  border-radius: 4px;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: calc(10px * var(--board-scale));
  font-weight: bold;
  color: #88ddff;
  text-shadow: 0 0 4px #000, 1px 1px 0 #000;
  box-shadow: 0 0 6px rgba(68, 170, 255, 0.55);
  z-index: 5;
  pointer-events: auto;
  cursor: help;
  line-height: 1;
}

/* Additional Action provider icon */
.additional-action-icon {
  position: absolute;
  right: 1px;
  top: 2px;
  font-size: calc(12px * var(--board-scale));
  z-index: 5;
  cursor: default;
  filter: drop-shadow(0 0 3px rgba(255,200,0,.8));
  animation: additionalActionPulse 2s ease-in-out infinite;
  line-height: 1;
}
@keyframes additionalActionPulse {
  0%, 100% { filter: drop-shadow(0 0 3px rgba(255,200,0,.6)); transform: scale(1); }
  50% { filter: drop-shadow(0 0 6px rgba(255,200,0,1)); transform: scale(1.15); }
}

/* Head Counter badge for Cute Hydra (and any future "Head Counter"
   creature). Sits bottom-left on the support card so it doesn't clash
   with summoning-sickness (top-left), additional-action (top-right),
   creature-level (right-middle), or mummified (bottom-right). */
.head-counter-badge {
  position: absolute;
  left: 1px;
  bottom: 2px;
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 1px 3px;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: calc(9px * var(--board-scale));
  color: #ffd5b3;
  background: rgba(50, 10, 5, 0.78);
  border: 1px solid #c44a2a;
  border-radius: 3px;
  text-shadow: 0 0 3px #000, 1px 1px 0 #000;
  box-shadow: 0 0 4px rgba(220, 80, 30, 0.5);
  z-index: 5;
  pointer-events: auto;
  cursor: default;
  line-height: 1;
}
.head-counter-badge .head-counter-icon {
  font-size: calc(11px * var(--board-scale));
  filter: drop-shadow(0 0 2px rgba(255, 100, 30, 0.7));
}
.head-counter-badge .head-counter-num {
  font-weight: bold;
  color: #ffe2b3;
}

.summoning-sickness-icon {
  position: absolute;
  left: 1px;
  top: 2px;
  font-size: calc(12px * var(--board-scale));
  z-index: 5;
  cursor: default;
  opacity: 0.8;
  filter: drop-shadow(0 0 2px rgba(150,150,255,.6));
  animation: summonSickPulse 2.5s ease-in-out infinite;
  line-height: 1;
}
@keyframes summonSickPulse {
  0%, 100% { opacity: 0.6; filter: drop-shadow(0 0 2px rgba(150,150,255,.4)); }
  50% { opacity: 1; filter: drop-shadow(0 0 5px rgba(150,150,255,.8)); }
}

@keyframes terrorPulse {
  0%, 100% { transform: scale(1); opacity: 0.85; }
  50% { transform: scale(1.25); opacity: 1; filter: brightness(1.3); }
}

@keyframes nomuSparkle {
  0% { opacity: 0; transform: translate(0, 0) scale(0.5); }
  30% { opacity: 1; transform: translate(calc(var(--np-x) * 0.4), calc(var(--np-y) * 0.4)) scale(1.2); }
  100% { opacity: 0; transform: translate(var(--np-x), var(--np-y)) scale(0.3); }
}

.mummified-icon {
  position: absolute;
  right: 1px;
  bottom: 2px;
  font-size: calc(14px * var(--board-scale));
  z-index: 5;
  cursor: default;
  opacity: 0.85;
  filter: drop-shadow(0 0 3px rgba(200,180,120,.7));
  animation: mummifiedPulse 3s ease-in-out infinite;
  line-height: 1;
}
@keyframes mummifiedPulse {
  0%, 100% { opacity: 0.7; filter: drop-shadow(0 0 2px rgba(200,180,120,.5)); }
  50% { opacity: 1; filter: drop-shadow(0 0 6px rgba(200,180,120,.9)); }
}

/* ═══ Modular Animation System ═══ */

/* Card reveal — opponent's played card appears centered */
.card-reveal-stack {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: 33%;
  z-index: 10050;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.card-reveal-entry {
  position: absolute;
  pointer-events: auto;
  cursor: default;
  animation: cardRevealAnim 3.5s ease-in-out forwards;
}
.card-reveal-card {
  width: 340px;
  aspect-ratio: 750 / 1050;
  box-shadow: 0 0 50px rgba(0,0,0,.7), 0 0 100px rgba(255,255,255,.08);
  border-radius: 8px;
}
@keyframes cardRevealAnim {
  0% { transform: scale(0.8); opacity: 0; }
  8% { transform: scale(1); opacity: 1; }
  65% { transform: scale(1); opacity: 1; }
  100% { transform: scale(0.97); opacity: 0; }
}

/* ═══ REACTION CHAIN VISUALIZATION ═══ */
.reaction-chain-overlay {
  position: fixed;
  right: 82px;
  bottom: 30%;
  z-index: 10040;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  pointer-events: none;
}
.reaction-chain-label {
  font-size: 11px;
  color: var(--accent);
  letter-spacing: 2px;
  text-transform: uppercase;
  text-shadow: 0 0 8px rgba(0,240,255,.4);
}
.reaction-chain-cards {
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  padding: 10px 12px;
  background: rgba(0,0,0,.7);
  border: 1px solid var(--bg4);
  border-radius: 8px;
  backdrop-filter: blur(6px);
  pointer-events: auto;
  max-height: 50vh;
  overflow-y: auto;
}
.reaction-chain-card {
  position: relative;
  animation: chainSlideIn .35s ease-out backwards;
  transition: opacity .5s, filter .3s;
}
@keyframes chainSlideIn {
  from { transform: translateY(-40px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
.chain-badge {
  position: absolute;
  bottom: 2px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 7px;
  font-weight: 700;
  letter-spacing: 1px;
  padding: 1px 5px;
  border-radius: 3px;
  pointer-events: none;
  z-index: 5;
}
.chain-badge-initial {
  background: rgba(80,140,220,.85);
  color: #fff;
}
.chain-owner-dot {
  position: absolute;
  top: 3px;
  right: 3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,.5);
  z-index: 5;
}
/* Resolving — golden glow */
.chain-glow {
  filter: brightness(1.3);
  box-shadow: 0 0 20px rgba(255,215,0,.8), 0 0 40px rgba(255,200,0,.4);
  animation: chainGlowPulse 0.6s ease-in-out infinite;
}
@keyframes chainGlowPulse {
  0%, 100% { box-shadow: 0 0 20px rgba(255,215,0,.8), 0 0 40px rgba(255,200,0,.4); }
  50% { box-shadow: 0 0 30px rgba(255,215,0,1), 0 0 60px rgba(255,200,0,.6); }
}
/* Negated — glitch + shake + 🚫 */
.chain-negated {
  animation: chainGlitch .15s linear infinite, chainShake .1s linear infinite;
  filter: saturate(0.3) brightness(0.7);
}
@keyframes chainGlitch {
  0% { clip-path: inset(0 0 80% 0); filter: hue-rotate(90deg); }
  20% { clip-path: inset(40% 0 20% 0); filter: hue-rotate(180deg); }
  40% { clip-path: inset(70% 0 0 0); filter: hue-rotate(270deg); }
  60% { clip-path: inset(10% 0 60% 0); filter: hue-rotate(45deg); }
  80% { clip-path: inset(50% 0 10% 0); filter: hue-rotate(135deg); }
  100% { clip-path: inset(0 0 40% 0); filter: hue-rotate(0deg); }
}
@keyframes chainShake {
  0%, 100% { transform: translate(0,0); }
  25% { transform: translate(-3px, 2px); }
  50% { transform: translate(3px, -1px); }
  75% { transform: translate(-2px, -2px); }
}

/* Escape Attack — the saved Hero leans/dashes side-to-side to evade
   the incoming Attack/Spell. Larger amplitude than chainShake so the
   dodge reads clearly; paired with a subtle counter-rotation for the
   "leaning into the dodge" feel. ~700ms total to land while the
   damage call returns.
   The animation is applied DEEP — to the BoardCard children of the
   targeted Hero Zone — rather than to the zone div itself. Animating
   the zone div added a layout-affecting `position: relative` shift
   that re-measured the absolute-positioned Area Zones (which compute
   their left coordinate from the Hero Zone rects), making them drift
   along with the dodge. Animating the inner card only keeps the
   zone's layout box pinned in place. */
@keyframes escapeDodge {
  0%   { transform: translateX(0) rotate(0deg); }
  12%  { transform: translateX(-26px) rotate(-6deg); }
  25%  { transform: translateX(0)     rotate(0deg);  }
  38%  { transform: translateX(26px)  rotate(6deg);  }
  52%  { transform: translateX(0)     rotate(0deg);  }
  66%  { transform: translateX(-18px) rotate(-3deg); }
  80%  { transform: translateX(18px)  rotate(3deg);  }
  100% { transform: translateX(0)     rotate(0deg);  }
}
/* All direct visual children dodge together. Targeting the children
   rather than the [data-hero-zone] wrapper keeps the wrapper's layout
   box pinned — important because the Area Zones (rendered as siblings
   in `.board-area-zones-center`) compute their left coordinates from
   the Hero Zone rects. Animating the wrapper itself made the area
   zones appear to drift along with the dodge. */
.escape-dodging > * {
  animation: escapeDodge 700ms cubic-bezier(.4, 1.6, .6, 1);
}
.chain-negate-symbol {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 48px;
  z-index: 6;
  pointer-events: none;
  filter: drop-shadow(0 0 10px rgba(255,0,0,.8));
  animation: negateSymbolPop .3s ease-out;
}
@keyframes negateSymbolPop {
  0% { transform: scale(0); opacity: 0; }
  60% { transform: scale(1.3); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
/* Resolved — bright flash then fade */
.chain-resolved {
  animation: chainResolveFlash .5s ease-out forwards !important;
}
@keyframes chainResolveFlash {
  0% { filter: brightness(2.5) saturate(1.5); box-shadow: 0 0 30px rgba(255,255,255,.9); }
  40% { filter: brightness(1.8) saturate(1); box-shadow: 0 0 15px rgba(255,255,200,.5); }
  100% { opacity: 0.15; filter: brightness(1.5) saturate(0); box-shadow: none; }
}

/* Ice negation overlay (Tool Freezer) */
.chain-ice-overlay {
  position: absolute;
  inset: 0;
  border-radius: 4px;
  overflow: hidden;
  pointer-events: none;
  z-index: 5;
  animation: chainIceAppear .4s ease-out forwards;
}
.chain-ice-overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(150,220,255,.55) 0%, rgba(80,170,240,.35) 50%, rgba(150,220,255,.55) 100%);
}
.chain-ice-overlay::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 2px solid rgba(180,230,255,.9);
  border-radius: 4px;
  box-shadow: inset 0 0 16px rgba(100,180,255,.6), 0 0 12px rgba(100,200,255,.4);
}
.chain-ice-crystal {
  position: absolute;
  opacity: .8;
  filter: drop-shadow(0 0 3px rgba(150,220,255,.8));
  animation: chainIceCrystalPop .3s ease-out backwards;
}
@keyframes chainIceAppear {
  from { opacity: 0; } to { opacity: 1; }
}
@keyframes chainIceCrystalPop {
  from { transform: scale(0) rotate(-30deg); opacity: 0; }
  to { transform: scale(1) rotate(0deg); opacity: .8; }
}

/* ═══ CAMERA FLASH ═══ */
/* Guardian Beast Zhu skip-turn overlay. Boar zodiac → sloth theme.
   Full-screen gray-out (desaturated, dimmed) plus floating "Zzz"
   glyphs drifting upward, with a centered "fell asleep!" label.
   Designed to read at a glance — gray = no agency, drifty Zzzs =
   sleep, big label = "your turn was skipped". ~1700ms total. */
.zhu-skip-overlay {
  position: fixed;
  inset: 0;
  z-index: 10090;
  pointer-events: none;
  animation: zhuSkipFade 1700ms ease-in-out forwards;
}
@keyframes zhuSkipFade {
  0%   { opacity: 0; }
  18%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { opacity: 0; }
}
.zhu-skip-veil {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 45%,
    rgba(40,42,50,.45) 0%,
    rgba(20,22,28,.78) 55%,
    rgba(10,10,14,.9) 100%);
  backdrop-filter: grayscale(85%) brightness(.55);
  -webkit-backdrop-filter: grayscale(85%) brightness(.55);
}
.zhu-skip-label {
  position: absolute;
  left: 50%;
  top: 42%;
  transform: translate(-50%, -50%);
  text-align: center;
  font-family: var(--font-display), sans-serif;
  color: #e7e7ea;
  text-shadow: 0 0 18px rgba(0,0,0,.95), 0 0 8px rgba(120,120,140,.8);
  animation: zhuSkipLabelPop 1.3s ease-out forwards;
}
@keyframes zhuSkipLabelPop {
  0%   { transform: translate(-50%, -50%) scale(0.6); opacity: 0; }
  20%  { transform: translate(-50%, -50%) scale(1.1); opacity: 1; }
  40%  { transform: translate(-50%, -50%) scale(1.0); opacity: 1; }
  85%  { transform: translate(-50%, -50%) scale(1.0); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(0.95); opacity: 0; }
}
.zhu-skip-icon {
  font-size: 84px;
  margin-bottom: 6px;
  filter: drop-shadow(0 0 16px rgba(180,200,255,.65));
  animation: zhuSkipIconBob 2.2s ease-in-out infinite;
}
@keyframes zhuSkipIconBob {
  0%, 100% { transform: rotate(-6deg); }
  50%      { transform: rotate(6deg); }
}
.zhu-skip-title {
  font-size: 32px;
  font-weight: 800;
  letter-spacing: 2px;
  text-transform: uppercase;
}
.zhu-skip-sub {
  margin-top: 6px;
  font-size: 14px;
  font-weight: 600;
  color: #b8bac4;
  letter-spacing: 1px;
}

/* Floating "Zzz" glyphs that drift upward across the overlay. Five
   instances at staggered positions / sizes / delays so the overlay
   reads as "the air itself is sleeping". */
.zhu-skip-zzz {
  position: absolute;
  font-family: var(--font-display), sans-serif;
  font-weight: 800;
  color: #cdd0db;
  text-shadow: 0 0 12px rgba(200,210,255,.8), 0 0 4px rgba(140,160,220,.9);
  opacity: 0;
}
.zhu-skip-zzz-1 {
  left: 18%; top: 70%; font-size: 90px;
  animation: zhuSkipZzzFloat 1.7s ease-out 100ms forwards;
}
.zhu-skip-zzz-2 {
  left: 28%; top: 60%; font-size: 60px;
  animation: zhuSkipZzzFloat 1.6s ease-out 280ms forwards;
}
.zhu-skip-zzz-3 {
  left: 70%; top: 65%; font-size: 70px;
  animation: zhuSkipZzzFloat 1.7s ease-out 200ms forwards;
}
.zhu-skip-zzz-4 {
  left: 82%; top: 75%; font-size: 100px;
  animation: zhuSkipZzzFloat 1.7s ease-out 50ms forwards;
}
.zhu-skip-zzz-5 {
  left: 50%; top: 80%; font-size: 50px;
  animation: zhuSkipZzzFloat 1.6s ease-out 380ms forwards;
}
@keyframes zhuSkipZzzFloat {
  0%   { transform: translate(0, 0) rotate(-12deg) scale(0.6); opacity: 0; }
  25%  { transform: translate(8px, -40px) rotate(-4deg) scale(1.0); opacity: .95; }
  60%  { transform: translate(-6px, -120px) rotate(6deg) scale(1.0); opacity: .85; }
  100% { transform: translate(14px, -220px) rotate(14deg) scale(1.1); opacity: 0; }
}

.camera-flash-overlay {
  position: fixed;
  inset: 0;
  z-index: 10080;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  animation: cameraFlashBg .8s ease-out forwards;
}
@keyframes cameraFlashBg {
  0% { background: rgba(255,255,255,0); }
  15% { background: rgba(255,255,255,.95); }
  40% { background: rgba(255,255,255,.6); }
  100% { background: rgba(255,255,255,0); }
}
.camera-flash-icon {
  animation: cameraIconPop .8s ease-out forwards;
  filter: drop-shadow(0 0 30px rgba(255,105,180,.8));
}
@keyframes cameraIconPop {
  0% { transform: scale(0.3); opacity: 0; }
  20% { transform: scale(1.4); opacity: 1; }
  50% { transform: scale(1.1); opacity: 0.9; }
  100% { transform: scale(1.5); opacity: 0; }
}

/* Explosion flash */
.anim-explosion-flash {
  position: absolute;
  width: 90px;
  height: 90px;
  left: -45px;
  top: -45px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,220,80,.9) 0%, rgba(255,100,0,.5) 40%, transparent 70%);
  animation: animExpFlash .5s ease-out forwards;
}
@keyframes animExpFlash {
  0% { transform: scale(0.2); opacity: 1; }
  40% { transform: scale(1.6); opacity: .8; }
  100% { transform: scale(2.2); opacity: 0; }
}

/* Explosion particles */
.anim-explosion-particle {
  position: absolute;
  width: var(--size);
  height: var(--size);
  left: calc(var(--size) / -2);
  top: calc(var(--size) / -2);
  border-radius: 50%;
  background: var(--color);
  box-shadow: 0 0 6px var(--color), 0 0 2px #fff;
  animation: animExpParticle var(--dur, 500ms) ease-out forwards;
  opacity: 0;
}
@keyframes animExpParticle {
  0% { transform: translate(0, 0) scale(1.2); opacity: 1; }
  20% { opacity: 1; }
  100% { transform: translate(var(--dx), var(--dy)) scale(0); opacity: 0; }
}

/* Creature death animation — flash + rising souls + red sparks */
.anim-creature-death-flash {
  position: absolute;
  width: 80px;
  height: 80px;
  left: -40px;
  top: -40px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,.95) 0%, rgba(200,100,100,.5) 35%, transparent 70%);
  animation: animDeathFlash .7s ease-out forwards;
}
@keyframes animDeathFlash {
  0% { transform: scale(0.3); opacity: 1; }
  30% { transform: scale(1.8); opacity: .9; }
  60% { transform: scale(1.2); opacity: .4; }
  100% { transform: scale(2.5); opacity: 0; }
}
.anim-creature-death-soul {
  position: absolute;
  width: var(--size);
  height: var(--size);
  left: calc(var(--size) / -2);
  top: calc(var(--size) / -2);
  border-radius: 50%;
  background: var(--color);
  box-shadow: 0 0 8px var(--color), 0 0 16px rgba(150,200,255,.4);
  animation: animDeathSoul var(--dur, 800ms) ease-out forwards;
  opacity: 0;
}
@keyframes animDeathSoul {
  0% { transform: translate(0, 0) scale(1); opacity: .9; }
  30% { opacity: 1; }
  100% { transform: translate(var(--dx), var(--dy)) scale(0.3); opacity: 0; }
}
.anim-creature-death-spark {
  position: absolute;
  width: var(--size);
  height: var(--size);
  left: calc(var(--size) / -2);
  top: calc(var(--size) / -2);
  border-radius: 50%;
  background: var(--color);
  box-shadow: 0 0 4px var(--color);
  animation: animDeathSpark var(--dur, 500ms) ease-out forwards;
  opacity: 0;
}
@keyframes animDeathSpark {
  0% { transform: translate(0, 0) scale(1.3); opacity: 1; }
  100% { transform: translate(var(--dx), var(--dy)) scale(0); opacity: 0; }
}

/* Guardian Beast Gou: white protective bubble. Engulfs the slot in
   a translucent dome, holds for ~700ms while shielding lands, then
   fades. Three layered elements — soft inner core, sharper rim, and
   a quick rim-circling highlight that sells the "active barrier"
   read. Scaled to roughly cover a hero/creature slot. */
.anim-shield-bubble-core {
  position: absolute;
  width: 110px;
  height: 130px;
  left: -55px;
  top: -65px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%,
    rgba(255,255,255,.85) 0%,
    rgba(230,250,255,.55) 35%,
    rgba(180,220,255,.18) 65%,
    transparent 78%);
  box-shadow: 0 0 28px rgba(220,240,255,.85), 0 0 12px rgba(255,255,255,.9) inset;
  animation: animShieldBubbleCore 1.3s ease-out forwards;
  opacity: 0;
}
@keyframes animShieldBubbleCore {
  0%   { transform: scale(0.2); opacity: 0; }
  18%  { transform: scale(1.0); opacity: 0.95; }
  70%  { transform: scale(1.05); opacity: 0.85; }
  100% { transform: scale(1.15); opacity: 0; }
}

.anim-shield-bubble-rim {
  position: absolute;
  width: 110px;
  height: 130px;
  left: -55px;
  top: -65px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,.95);
  box-shadow: 0 0 18px rgba(255,255,255,.7), 0 0 6px rgba(255,255,255,.95) inset;
  animation: animShieldBubbleRim 1.3s ease-out forwards;
  opacity: 0;
}
@keyframes animShieldBubbleRim {
  0%   { transform: scale(0.4); opacity: 0; }
  22%  { transform: scale(1.02); opacity: 1; }
  85%  { transform: scale(1.08); opacity: 0.6; }
  100% { transform: scale(1.18); opacity: 0; }
}

/* Diagonal sweep highlight — a single bright crescent moving across
   the bubble's surface to imply curvature. */
.anim-shield-bubble-glint {
  position: absolute;
  width: 110px;
  height: 130px;
  left: -55px;
  top: -65px;
  border-radius: 50%;
  background: linear-gradient(115deg,
    transparent 38%,
    rgba(255,255,255,.95) 48%,
    rgba(255,255,255,.4) 53%,
    transparent 60%);
  mix-blend-mode: screen;
  animation: animShieldBubbleGlint 1.3s ease-out forwards;
  opacity: 0;
}
@keyframes animShieldBubbleGlint {
  0%   { transform: translateX(-30px) scale(0.85); opacity: 0; }
  25%  { opacity: 0.95; }
  60%  { opacity: 0.6; }
  100% { transform: translateX(30px) scale(1.1); opacity: 0; }
}

/* Guardian Beast Ji: massive cyan shock that stuns. Wider and bluer
   than the regular electric_strike. Layered visuals: a wide cyan
   flash dome, two giant zigzag bolts crashing down, a halo of inward-
   converging arc bolts (.anim-stun-bolt), and a burst of crackling
   sparks (reuses .anim-explosion-particle with cyan colors). */
.anim-stun-flash {
  position: absolute;
  width: 130px;
  height: 130px;
  left: -65px;
  top: -65px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(180,240,255,.95) 0%, rgba(80,180,255,.55) 35%, rgba(40,120,220,.25) 60%, transparent 78%);
  box-shadow: 0 0 40px rgba(140,220,255,.9), 0 0 18px rgba(255,255,255,.9) inset;
  animation: animStunFlash .7s ease-out forwards;
  animation-delay: 100ms;
  opacity: 0;
}
@keyframes animStunFlash {
  0%   { transform: scale(0.25); opacity: 1; }
  35%  { transform: scale(1.6); opacity: .9; }
  70%  { transform: scale(2.0); opacity: .55; }
  100% { transform: scale(2.4); opacity: 0; }
}

.anim-stun-bolt {
  position: absolute;
  font-size: var(--size);
  color: rgba(180,240,255,.98);
  filter: drop-shadow(0 0 8px rgba(140,220,255,.95)) drop-shadow(0 0 4px rgba(255,255,255,1));
  animation: animStunBolt ease-in forwards;
  opacity: 0;
  left: var(--startX);
  top: var(--startY);
  transform: rotate(var(--rotation));
}
@keyframes animStunBolt {
  0%   { transform: translate(0, 0) rotate(var(--rotation)) scale(1.5); opacity: 1; }
  70%  { opacity: 1; }
  100% { transform: translate(calc(var(--startX) * -1), calc(var(--startY) * -1)) rotate(var(--rotation)) scale(0.4); opacity: 0; }
}

/* Two giant vertical megabolts that crash down onto the target —
   the showpiece of the stun visual. Mirrored horizontally so the
   composition reads as TWO bolts striking from above, not one. */
.anim-stun-megabolt {
  position: absolute;
  font-size: 90px;
  color: rgba(200,245,255,1);
  filter: drop-shadow(0 0 14px rgba(140,220,255,1)) drop-shadow(0 0 6px rgba(255,255,255,1));
  pointer-events: none;
  opacity: 0;
}
.anim-stun-megabolt-1 {
  left: -30px;
  top: -100px;
  animation: animStunMegabolt1 .55s cubic-bezier(.4,0,.7,1) forwards;
}
.anim-stun-megabolt-2 {
  left: 8px;
  top: -100px;
  transform: scaleX(-1);
  animation: animStunMegabolt2 .6s cubic-bezier(.4,0,.7,1) .1s forwards;
}
@keyframes animStunMegabolt1 {
  0%   { transform: translateY(-60px) scale(0.6); opacity: 0; }
  25%  { transform: translateY(-10px) scale(1.4); opacity: 1; }
  55%  { transform: translateY(0) scale(1.2); opacity: 1; }
  100% { transform: translateY(0) scale(1.0); opacity: 0; }
}
@keyframes animStunMegabolt2 {
  0%   { transform: scaleX(-1) translateY(-60px) scale(0.6); opacity: 0; }
  25%  { transform: scaleX(-1) translateY(-10px) scale(1.5); opacity: 1; }
  60%  { transform: scaleX(-1) translateY(0) scale(1.3); opacity: 1; }
  100% { transform: scaleX(-1) translateY(0) scale(1.1); opacity: 0; }
}

/* Guardian Beast Niu: golden power-up. Warm gold radiating flash,
   expanding halo ring, upward shaft of light behind the hero, plus
   spiraling stars and sparkles. Total runtime ~750ms. */
.anim-niu-flash {
  position: absolute;
  width: 130px;
  height: 130px;
  left: -65px;
  top: -65px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,244,163,1) 0%, rgba(255,215,0,.7) 35%, rgba(255,184,74,.3) 60%, transparent 78%);
  box-shadow: 0 0 38px rgba(255,215,0,.95), 0 0 14px rgba(255,255,255,.95) inset;
  animation: animNiuFlash .65s ease-out forwards;
  opacity: 0;
}
@keyframes animNiuFlash {
  0%   { transform: scale(0.25); opacity: 1; }
  35%  { transform: scale(1.5); opacity: .9; }
  70%  { transform: scale(1.9); opacity: .55; }
  100% { transform: scale(2.2); opacity: 0; }
}

.anim-niu-halo {
  position: absolute;
  width: 140px;
  height: 140px;
  left: -70px;
  top: -70px;
  border-radius: 50%;
  border: 3px solid rgba(255,215,0,.9);
  box-shadow: 0 0 22px rgba(255,215,0,.9), 0 0 10px rgba(255,255,255,.9) inset;
  animation: animNiuHalo .8s ease-out .05s forwards;
  opacity: 0;
}
@keyframes animNiuHalo {
  0%   { transform: scale(0.35); opacity: 0; }
  25%  { transform: scale(1.05); opacity: 1; }
  70%  { transform: scale(1.4); opacity: .55; }
  100% { transform: scale(1.7); opacity: 0; }
}

/* Vertical column of light rising from below the hero, suggests a
   "power surge" feel. Behind the flash so it doesn't dominate. */
.anim-niu-shaft {
  position: absolute;
  width: 56px;
  height: 180px;
  left: -28px;
  top: -110px;
  background: linear-gradient(to top,
    rgba(255,215,0,0) 0%,
    rgba(255,215,0,.55) 25%,
    rgba(255,244,163,.85) 55%,
    rgba(255,255,255,.95) 75%,
    rgba(255,244,163,.4) 95%,
    rgba(255,215,0,0) 100%);
  filter: blur(6px);
  animation: animNiuShaft .75s ease-out forwards;
  opacity: 0;
  transform-origin: bottom center;
}
@keyframes animNiuShaft {
  0%   { transform: scaleY(0.2); opacity: 0; }
  20%  { transform: scaleY(1.0); opacity: 1; }
  60%  { transform: scaleY(1.05); opacity: .8; }
  100% { transform: scaleY(1.15); opacity: 0; }
}

.anim-niu-star {
  position: absolute;
  left: 0;
  top: 0;
  color: #fff4a3;
  text-shadow: 0 0 8px #ffd700, 0 0 4px #fff, 0 0 12px rgba(255,184,74,.85);
  animation: animNiuStar ease-out forwards;
  opacity: 0;
  transform-origin: center;
}
@keyframes animNiuStar {
  0%   { transform: translate(0, 0) scale(0.3) rotate(0deg); opacity: 0; }
  20%  { transform: translate(calc(var(--tx) * 0.3), calc(var(--ty) * 0.3)) scale(1.2) rotate(120deg); opacity: 1; }
  70%  { transform: translate(var(--tx), var(--ty)) scale(1.05) rotate(360deg); opacity: 1; }
  100% { transform: translate(calc(var(--tx) * 1.25), calc(var(--ty) * 1.25 - 12px)) scale(0.5) rotate(540deg); opacity: 0; }
}

/* Guardian Beast She: snake fangs biting down on the target. Two
   tall ivory triangles descend from above, converge inward, snap
   shut at the target's center, hold briefly, then withdraw upward.
   A sickly green flash and venom-drip particles round out the
   envenomation read. Total runtime ~700ms. */
.anim-snake-fang {
  position: absolute;
  width: 18px;
  height: 56px;
  /* Pointed fang shape — wider top tapering to a sharp tip. */
  clip-path: polygon(0% 0%, 100% 0%, 88% 60%, 50% 100%, 12% 60%);
  background: linear-gradient(to bottom,
    #fff 0%,
    #f8f4e0 50%,
    #e8dfb8 80%,
    #d7c9a0 100%);
  filter: drop-shadow(0 0 8px rgba(255,255,210,.85)) drop-shadow(0 0 3px rgba(255,255,255,.95));
  opacity: 0;
  transform-origin: top center;
}
.anim-snake-fang-left {
  left: -34px;
  top: -78px;
  animation: animSnakeFangLeft .65s cubic-bezier(.4,0,.6,1) forwards;
}
.anim-snake-fang-right {
  left: 16px;
  top: -78px;
  animation: animSnakeFangRight .65s cubic-bezier(.4,0,.6,1) forwards;
}
@keyframes animSnakeFangLeft {
  0%   { transform: translate(-22px, -28px) rotate(-22deg) scale(0.7); opacity: 0; }
  35%  { transform: translate(0, 0) rotate(8deg) scale(1.05); opacity: 1; }
  50%  { transform: translate(2px, 4px) rotate(12deg) scale(1.0); opacity: 1; }   /* bite peak */
  62%  { transform: translate(0, 0) rotate(8deg) scale(1.0); opacity: 1; }
  100% { transform: translate(-26px, -38px) rotate(-25deg) scale(0.6); opacity: 0; }
}
@keyframes animSnakeFangRight {
  0%   { transform: translate(22px, -28px) rotate(22deg) scale(0.7); opacity: 0; }
  35%  { transform: translate(0, 0) rotate(-8deg) scale(1.05); opacity: 1; }
  50%  { transform: translate(-2px, 4px) rotate(-12deg) scale(1.0); opacity: 1; } /* bite peak */
  62%  { transform: translate(0, 0) rotate(-8deg) scale(1.0); opacity: 1; }
  100% { transform: translate(26px, -38px) rotate(25deg) scale(0.6); opacity: 0; }
}

/* Sickly-green venom flash on impact. Times to the bite peak ~310ms. */
.anim-snake-bite-flash {
  position: absolute;
  width: 110px;
  height: 110px;
  left: -55px;
  top: -55px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(200,255,90,.85) 0%,
    rgba(140,220,40,.45) 40%,
    rgba(80,180,20,.18) 65%,
    transparent 80%);
  box-shadow: 0 0 24px rgba(155,232,74,.85);
  animation: animSnakeBiteFlash .55s ease-out .25s forwards;
  opacity: 0;
}
@keyframes animSnakeBiteFlash {
  0%   { transform: scale(0.4); opacity: 0; }
  30%  { transform: scale(1.4); opacity: 1; }
  70%  { transform: scale(1.7); opacity: .55; }
  100% { transform: scale(2.0); opacity: 0; }
}

/* Small green venom drips falling from the bite point. Spawn after
   the fangs retract, fall briefly, fade. */
.anim-snake-venom-drip {
  position: absolute;
  left: var(--dx);
  top: 0;
  width: var(--size);
  height: calc(var(--size) * 1.4);
  border-radius: 50% 50% 50% 50% / 35% 35% 65% 65%;
  background: linear-gradient(to bottom, #c8ff5a, #7bcc2a, #498a0e);
  box-shadow: 0 0 6px rgba(155,232,74,.85);
  animation: animSnakeVenomDrip ease-in forwards;
  opacity: 0;
}
@keyframes animSnakeVenomDrip {
  0%   { transform: translateY(-4px) scale(0.6); opacity: 0; }
  20%  { transform: translateY(2px) scale(1.0); opacity: 1; }
  100% { transform: translateY(var(--fall)) scale(0.8); opacity: 0; }
}

/* Cannibalism (Mao's starting Ability): meat drumstick "chomp" plus
   green healing particles rising from the eating Hero. Tuned to
   ~900ms total. */
.anim-cannibalism-flash {
  position: absolute;
  width: 110px;
  height: 110px;
  left: -55px;
  top: -55px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(180,255,170,.7) 0%,
    rgba(110,220,90,.35) 40%,
    rgba(60,160,40,.15) 65%,
    transparent 80%);
  box-shadow: 0 0 22px rgba(140,235,120,.85);
  animation: animCannibalismFlash .65s ease-out forwards;
  opacity: 0;
}
@keyframes animCannibalismFlash {
  0%   { transform: scale(0.4); opacity: 0; }
  30%  { transform: scale(1.15); opacity: .9; }
  70%  { transform: scale(1.4); opacity: .55; }
  100% { transform: scale(1.7); opacity: 0; }
}

/* The drumstick — wobbles in, then "shrinks in bites" through
   three discrete scale-down keyframes that read as chomps. */
.anim-cannibalism-meat {
  position: absolute;
  left: 0;
  top: 0;
  font-size: 64px;
  line-height: 1;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 0 10px rgba(255,200,120,.85)) drop-shadow(0 0 4px rgba(0,0,0,.6));
  animation: animCannibalismMeat .9s cubic-bezier(.4,0,.55,1) forwards;
  opacity: 0;
}
@keyframes animCannibalismMeat {
  0%   { transform: translate(-50%, -50%) scale(0.3) rotate(-18deg); opacity: 0; }
  15%  { transform: translate(-50%, -50%) scale(1.2) rotate(-8deg); opacity: 1; }
  /* Bite 1 */
  28%  { transform: translate(-50%, -50%) scale(1.0) rotate(6deg); opacity: 1; }
  35%  { transform: translate(-50%, -52%) scale(0.85) rotate(-4deg); opacity: 1; }
  /* Bite 2 */
  48%  { transform: translate(-50%, -50%) scale(0.85) rotate(-4deg); opacity: 1; }
  55%  { transform: translate(-50%, -52%) scale(0.65) rotate(8deg); opacity: 1; }
  /* Bite 3 — gone */
  72%  { transform: translate(-50%, -50%) scale(0.65) rotate(8deg); opacity: 1; }
  82%  { transform: translate(-50%, -54%) scale(0.4) rotate(-6deg); opacity: .85; }
  100% { transform: translate(-50%, -60%) scale(0.15) rotate(0deg); opacity: 0; }
}

/* Two chomp-impact sparks that flash on each bite, faked by re-using
   one element with two animation delays on independent keyframes. */
.anim-cannibalism-bite {
  position: absolute;
  font-size: 26px;
  color: #fff;
  text-shadow: 0 0 8px #ffe8b0, 0 0 4px #fff;
  opacity: 0;
}
.anim-cannibalism-bite-1 {
  left: -28px; top: -10px;
  animation: animCannibalismBite .25s ease-out 270ms forwards;
}
.anim-cannibalism-bite-2 {
  left: 14px; top: -6px;
  animation: animCannibalismBite .25s ease-out 490ms forwards;
}
@keyframes animCannibalismBite {
  0%   { transform: scale(0.3); opacity: 0; }
  40%  { transform: scale(1.4); opacity: 1; }
  100% { transform: scale(0.8); opacity: 0; }
}

/* Green healing particles drifting upward from around the Hero. */
.anim-cannibalism-particle {
  position: absolute;
  font-weight: 800;
  pointer-events: none;
  animation: animCannibalismParticle ease-out forwards;
  opacity: 0;
}
@keyframes animCannibalismParticle {
  0%   { transform: translateY(0) scale(0.4); opacity: 0; }
  20%  { transform: translateY(-6px) scale(1.0); opacity: 1; }
  85%  { transform: translateY(calc(var(--endY) * 0.85)) scale(1.0); opacity: .9; }
  100% { transform: translateY(var(--endY)) scale(0.7); opacity: 0; }
}

/* Freeze animation — ice flash + snowballs */
.anim-freeze-flash {
  position: absolute;
  width: 100px;
  height: 120px;
  left: -50px;
  top: -50px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(150,220,255,.9) 0%, rgba(100,180,240,.4) 40%, transparent 70%);
  animation: animFreezeFlash .6s ease-out .4s forwards;
  opacity: 0;
}
@keyframes animFreezeFlash {
  0% { transform: scale(0.2); opacity: 1; }
  50% { transform: scale(1.8); opacity: .7; }
  100% { transform: scale(2.2); opacity: 0; }
}

.anim-snowball {
  position: absolute;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, #fff, #ccddff);
  box-shadow: 0 0 4px rgba(200,230,255,.8), 0 0 2px #fff;
  animation: animSnowball var(--dur, 300ms) ease-in forwards;
  opacity: 0;
  left: var(--startX);
  top: var(--startY);
}
@keyframes animSnowball {
  0% { transform: translate(0, 0) scale(1); opacity: 1; }
  80% { opacity: 1; }
  100% { transform: translate(calc(var(--startX) * -1), 0) scale(0.6); opacity: 0; }
}

/* Venom fog — thick sickly green cloud */
.anim-venom-fog-particle {
  position: absolute;
  width: var(--vsize);
  height: var(--vsize);
  border-radius: 50%;
  background: radial-gradient(circle at 40% 40%, rgba(80,200,40,.7), rgba(40,120,20,.4), transparent);
  box-shadow: 0 0 20px rgba(80,200,40,.5), 0 0 40px rgba(40,160,20,.3);
  animation: animVenomFog var(--dur, 800ms) ease-in-out forwards;
  opacity: 0;
  filter: blur(6px);
  left: calc(50% - var(--vsize) / 2);
  top: calc(50% - var(--vsize) / 2);
}
@keyframes animVenomFog {
  0% { transform: translate(0, 10px) scale(0.3); opacity: 0; }
  20% { opacity: var(--vopacity, 0.6); }
  50% { transform: translate(var(--vdx), var(--vdy)) scale(1); opacity: var(--vopacity, 0.6); }
  100% { transform: translate(calc(var(--vdx) * 1.5), calc(var(--vdy) - 20px)) scale(1.3); opacity: 0; }
}

/* Ice encase — shards converging from all sides */

/* Poisoned Well — purple water bubbles + steam */

/* Plague Smoke — thick black gas */

/* Biomancy Bloom — sickly jungle flowers overgrowing */

/* Biomancy Vines — vines enveloping a target */
.anim-biomancy-vine {
  position: absolute;
  animation: animBiomancyVine var(--dur, 600ms) ease-in-out forwards;
  opacity: 0;
  left: 0; top: 0;
  filter: drop-shadow(0 0 3px rgba(40,120,20,.7));
}
@keyframes animBiomancyVine {
  0% { transform: translate(var(--vdx), var(--vdy)) scale(0.3) rotate(0deg); opacity: 0; }
  30% { opacity: 1; }
  70% { transform: translate(0, 0) scale(1.1) rotate(var(--vrot)); opacity: 1; }
  100% { transform: translate(0, 0) scale(0.9) rotate(var(--vrot)); opacity: 0; }
}
.anim-biomancy-flower {
  position: absolute;
  animation: animBiomancyBloom var(--dur, 700ms) ease-out forwards;
  opacity: 0;
  left: 0; top: 0;
  filter: drop-shadow(0 0 3px rgba(80,160,40,.6));
}
@keyframes animBiomancyBloom {
  0% { transform: translate(0, 0) scale(0.2) rotate(0deg); opacity: 0; }
  20% { opacity: 1; }
  60% { transform: translate(var(--bdx), var(--bdy)) scale(1.1) rotate(20deg); opacity: 1; }
  100% { transform: translate(calc(var(--bdx) * 1.2), calc(var(--bdy) * 1.2)) scale(0.8) rotate(40deg); opacity: 0; }
}
/* Druid's nature-storm summon: leaves spawn at centre and fan outwards,
   spinning as they go. Fade-out happens past the outer radius so the leaves
   feel "blown past" the slot rather than absorbed by it. */
.anim-druid-leaf {
  position: absolute;
  animation: animDruidLeaf var(--dur, 1000ms) ease-out forwards;
  opacity: 0;
  left: 0; top: 0;
  filter: drop-shadow(0 0 4px rgba(60,170,50,.75));
}
@keyframes animDruidLeaf {
  0%   { transform: translate(0, 0) scale(0.35) rotate(0deg); opacity: 0; }
  15%  { opacity: 1; }
  70%  { transform: translate(var(--ldx), var(--ldy)) scale(1.05) rotate(var(--lspin)); opacity: 1; }
  100% { transform: translate(calc(var(--ldx) * 1.35), calc(var(--ldy) * 1.35)) scale(0.8) rotate(var(--lspin)); opacity: 0; }
}

/* Plague Smoke styles */
.anim-plague-smoke {
  position: absolute;
  width: var(--psize);
  height: var(--psize);
  border-radius: 50%;
  background: radial-gradient(circle at 40% 40%, rgba(30,30,30,.8), rgba(15,15,15,.5), transparent);
  box-shadow: 0 0 15px rgba(0,0,0,.6), 0 0 30px rgba(20,20,20,.3);
  animation: animPlagueSmoke var(--dur, 800ms) ease-in-out forwards;
  opacity: 0;
  filter: blur(5px);
  left: calc(50% - var(--psize) / 2);
  top: calc(50% - var(--psize) / 2);
}
@keyframes animPlagueSmoke {
  0% { transform: translate(0, 5px) scale(0.4); opacity: 0; }
  20% { opacity: var(--popacity, 0.6); }
  50% { transform: translate(var(--pdx), var(--pdy)) scale(1); opacity: var(--popacity, 0.6); }
  100% { transform: translate(calc(var(--pdx) * 1.3), calc(var(--pdy) - 15px)) scale(1.2); opacity: 0; }
}
.anim-well-bubble {
  position: absolute;
  width: var(--wsize);
  height: var(--wsize);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, rgba(160,60,220,.8), rgba(100,20,160,.5));
  box-shadow: 0 0 8px rgba(140,40,200,.6);
  animation: animWellBubble var(--dur, 700ms) ease-out forwards;
  opacity: 0;
  left: calc(50% - var(--wsize) / 2);
  bottom: 0;
}
@keyframes animWellBubble {
  0% { transform: translate(0, 0) scale(0.5); opacity: 0; }
  15% { opacity: 0.8; }
  50% { transform: translate(var(--wdx), -30px) scale(1); opacity: 0.7; }
  100% { transform: translate(var(--wdx), -50px) scale(0.4); opacity: 0; }
}
.anim-well-steam {
  position: absolute;
  width: var(--ssize);
  height: var(--ssize);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(180,120,220,.5), rgba(140,80,200,.2), transparent);
  filter: blur(5px);
  animation: animWellSteam var(--dur, 800ms) ease-out forwards;
  opacity: 0;
  left: calc(50% - var(--ssize) / 2);
  bottom: 10px;
}
@keyframes animWellSteam {
  0% { transform: translate(0, 0) scale(0.6); opacity: 0; }
  20% { opacity: 0.6; }
  60% { transform: translate(var(--sdx), -40px) scale(1.2); opacity: 0.4; }
  100% { transform: translate(var(--sdx), -70px) scale(1.6); opacity: 0; }
}
.anim-ice-shard {
  position: absolute;
  font-size: var(--size);
  color: rgba(180,220,255,.9);
  filter: drop-shadow(0 0 4px rgba(100,200,255,.8));
  animation: animIceShard var(--dur, 300ms) ease-in forwards;
  opacity: 0;
  left: var(--startX);
  top: var(--startY);
}
@keyframes animIceShard {
  0% { transform: translate(0, 0) scale(1.2); opacity: 1; }
  80% { opacity: 1; }
  100% { transform: translate(calc(var(--startX) * -1), calc(var(--startY) * -1)) scale(0.4); opacity: 0; }
}

/* Thaw animation — melting flash + drips falling */
.anim-thaw-flash {
  position: absolute;
  width: 80px;
  height: 80px;
  left: -40px;
  top: -40px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(200,240,255,.7) 0%, rgba(150,200,230,.3) 50%, transparent 70%);
  animation: animThawFlash .7s ease-out forwards;
}
@keyframes animThawFlash {
  0% { transform: scale(0.5); opacity: 1; }
  40% { transform: scale(1.3); opacity: .6; }
  100% { transform: scale(1.8); opacity: 0; }
}

.anim-thaw-drip {
  position: absolute;
  width: var(--size);
  height: calc(var(--size) * 1.5);
  left: var(--xOff);
  top: -5px;
  border-radius: 0 0 50% 50%;
  background: var(--color);
  box-shadow: 0 0 3px var(--color);
  animation: animThawDrip var(--dur, 600ms) ease-in forwards;
  opacity: 0;
}
@keyframes animThawDrip {
  0% { transform: translateY(0) scaleY(0.5); opacity: .8; }
  30% { opacity: 1; transform: translateY(5px) scaleY(1); }
  100% { transform: translateY(var(--speed)) scaleY(1.5); opacity: 0; }
}

/* Electric strike animation — lightning bolts converging from all sides */
.anim-electric-flash {
  position: absolute;
  width: 70px;
  height: 70px;
  left: -35px;
  top: -35px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,240,100,.8) 0%, rgba(255,220,50,.4) 40%, transparent 70%);
  animation: animElectricFlash .5s ease-out forwards;
  animation-delay: 200ms;
  opacity: 0;
}
@keyframes animElectricFlash {
  0% { transform: scale(0.3); opacity: 1; }
  50% { transform: scale(1.4); opacity: .7; }
  100% { transform: scale(1.8); opacity: 0; }
}
.anim-electric-bolt {
  position: absolute;
  font-size: var(--size);
  color: rgba(255,230,50,.95);
  filter: drop-shadow(0 0 5px rgba(255,220,50,.9));
  animation: animElectricBolt ease-in forwards;
  opacity: 0;
  left: var(--startX);
  top: var(--startY);
  transform: rotate(var(--rotation));
}
@keyframes animElectricBolt {
  0% { transform: translate(0, 0) rotate(var(--rotation)) scale(1.3); opacity: 1; }
  70% { opacity: 1; }
  100% { transform: translate(calc(var(--startX) * -1), calc(var(--startY) * -1)) rotate(var(--rotation)) scale(0.3); opacity: 0; }
}

/* Flame strike animation — fire converging from all sides */
.anim-flame-flash {
  position: absolute;
  width: 70px;
  height: 70px;
  left: -35px;
  top: -35px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,120,0,.8) 0%, rgba(255,60,0,.4) 40%, transparent 70%);
  animation: animFlameFlash .6s ease-out forwards;
  animation-delay: 250ms;
  opacity: 0;
}
@keyframes animFlameFlash {
  0% { transform: scale(0.3); opacity: 1; }
  50% { transform: scale(1.5); opacity: .7; }
  100% { transform: scale(2); opacity: 0; }
}
.anim-flame-shard {
  position: absolute;
  font-size: var(--size);
  filter: drop-shadow(0 0 4px rgba(255,80,0,.9));
  animation: animFlameShard ease-in forwards;
  opacity: 0;
  left: var(--startX);
  top: var(--startY);
}
@keyframes animFlameShard {
  0% { transform: translate(0, 0) scale(1.2); opacity: 1; }
  70% { opacity: 1; }
  100% { transform: translate(calc(var(--startX) * -1), calc(var(--startY) * -1)) scale(0.3); opacity: 0; }
}

/* Black flame strike — Skeleton Demon's signature attack. Same
   structural animation as flame_strike (radial converge + central
   flash) but with a dark-purple/black palette and a per-broadcast
   `--bfScale` CSS variable that scales the flash size with damage. */
.anim-black-flame-flash {
  position: absolute;
  width: calc(80px * var(--bfScale, 1));
  height: calc(80px * var(--bfScale, 1));
  left: calc(-40px * var(--bfScale, 1));
  top: calc(-40px * var(--bfScale, 1));
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(40,0,60,.9) 0%,
    rgba(20,0,30,.55) 40%,
    rgba(60,0,80,.25) 70%,
    transparent 80%);
  animation: animBlackFlameFlash .65s ease-out forwards;
  animation-delay: 220ms;
  opacity: 0;
}
@keyframes animBlackFlameFlash {
  0% { transform: scale(0.3); opacity: 1; }
  50% { transform: scale(1.5); opacity: .85; }
  100% { transform: scale(2.3); opacity: 0; }
}
/* `brightness(0)` flattens the emoji's color channels to pure black
   while preserving alpha — turning 🔥 into a flame-shaped silhouette.
   The dual drop-shadows then layer a deep-violet outer glow + a
   tighter inner haze so the black flames read as menacing rather
   than just "dead pixels". */
.anim-black-flame-shard {
  position: absolute;
  font-size: var(--size);
  filter:
    brightness(0)
    drop-shadow(0 0 7px rgba(80,0,120,.95))
    drop-shadow(0 0 14px rgba(30,0,55,.75));
  animation: animBlackFlameShard ease-in forwards;
  opacity: 0;
  left: var(--startX);
  top: var(--startY);
}
@keyframes animBlackFlameShard {
  0% { transform: translate(0, 0) scale(1.3); opacity: 1; }
  70% { opacity: 1; }
  100% { transform: translate(calc(var(--startX) * -1), calc(var(--startY) * -1)) scale(0.25); opacity: 0; }
}

/* Wind swirl animation — gentle spiral around target */
.anim-wind-flash {
  position: absolute;
  width: 60px;
  height: 60px;
  left: -30px;
  top: -30px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(200,240,255,.5) 0%, rgba(180,220,240,.2) 50%, transparent 70%);
  animation: animWindFlash .6s ease-out forwards;
  opacity: 0;
}
@keyframes animWindFlash {
  0% { transform: scale(0.3) rotate(0deg); opacity: .8; }
  50% { transform: scale(1.2) rotate(90deg); opacity: .5; }
  100% { transform: scale(1.6) rotate(180deg); opacity: 0; }
}
.anim-wind-particle {
  position: absolute;
  font-size: var(--size);
  color: rgba(180,220,240,.9);
  filter: drop-shadow(0 0 3px rgba(150,200,230,.7));
  animation: animWindSwirl ease-out forwards;
  opacity: 0;
}
@keyframes animWindSwirl {
  0% { transform: translate(calc(cos(var(--startAngle)) * var(--radius)), calc(sin(var(--startAngle)) * var(--radius))) scale(1); opacity: .9; }
  50% { transform: translate(calc(cos(var(--startAngle) + 2rad) * var(--radius) * 0.6), calc(sin(var(--startAngle) + 2rad) * var(--radius) * 0.6)) scale(1.2); opacity: 1; }
  100% { transform: translate(0, -15px) scale(0.3); opacity: 0; }
}

/* Shadow summon animation */
.anim-shadow-flash {
  position: absolute;
  width: 70px;
  height: 70px;
  left: -35px;
  top: -35px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(80,30,140,.7) 0%, rgba(50,20,100,.3) 40%, transparent 70%);
  animation: animShadowFlash .6s ease-out forwards;
  animation-delay: 250ms;
  opacity: 0;
}
@keyframes animShadowFlash {
  0% { transform: scale(0.3); opacity: 1; }
  50% { transform: scale(1.4); opacity: .6; }
  100% { transform: scale(1.8); opacity: 0; }
}
.anim-shadow-tendril {
  position: absolute;
  font-size: var(--size);
  color: rgba(80,30,140,.85);
  filter: drop-shadow(0 0 4px rgba(60,20,120,.8));
  animation: animShadowTendril ease-out forwards;
  opacity: 0;
  left: var(--xOff);
  bottom: -40px;
}
@keyframes animShadowTendril {
  0% { transform: translateY(0) scale(0.5); opacity: 0; }
  30% { opacity: .9; transform: translateY(-20px) scale(1.1); }
  70% { opacity: .7; transform: translateY(-40px) scale(0.9); }
  100% { transform: translateY(-60px) scale(0.3); opacity: 0; }
}

/* Summon lock warning */
.summon-lock-warning {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 100;
  font-size: calc(11px * var(--board-scale));
  font-weight: 700;
  color: #ff4444;
  text-shadow: 0 0 6px rgba(255,0,0,.4), 1px 1px 0 #000;
  pointer-events: none;
  animation: summonLockPulse 2s ease-in-out infinite;
  max-width: calc(200px * var(--board-scale));
  line-height: 1.3;
}
@keyframes summonLockPulse {
  0%, 100% { opacity: .8; }
  50% { opacity: 1; }
}

/* Gold sparkle flash */
/* Logan, the Investment Monkee — goldener Bananenregen.
   Faellt von oberhalb der Karte herab, dreht sich dabei und driftet
   seitlich; die Werte kommen als CSS-Variablen aus der Komponente,
   damit jede Banane eine eigene Bahn hat. */
@keyframes pp-banana-fall {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  12%  { opacity: 1; }
  80%  { opacity: 1; }
  100% { transform: translate(var(--pp-banana-tilt, 0px), 132px) rotate(var(--pp-banana-spin, 180deg)); opacity: 0; }
}

.anim-gold-flash {
  position: absolute;
  width: 40px;
  height: 40px;
  left: -20px;
  top: -20px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,215,0,.8) 0%, rgba(255,200,0,.3) 40%, transparent 70%);
  animation: animGoldFlash .5s ease-out forwards;
}
@keyframes animGoldFlash {
  0% { transform: scale(0.3); opacity: 1; }
  50% { transform: scale(1.3); opacity: .6; }
  100% { transform: scale(1.6); opacity: 0; }
}

/* Diamond sparkle flash — icy white/cyan radial used by
   DiamondSparkleEffect. Same scale curve as the gold flash so
   stagger-emitting works visually identically. */
.anim-diamond-flash {
  position: absolute;
  width: 44px;
  height: 44px;
  left: -22px;
  top: -22px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255,255,255,.95) 0%,
    rgba(190,235,255,.65) 30%,
    rgba(120,200,255,.30) 55%,
    transparent 75%);
  filter: drop-shadow(0 0 6px rgba(180,230,255,.7));
  animation: animDiamondFlash .55s ease-out forwards;
}
@keyframes animDiamondFlash {
  0%   { transform: scale(0.3); opacity: 1; }
  50%  { transform: scale(1.4); opacity: .7; }
  100% { transform: scale(1.7); opacity: 0; }
}

/* Beer bubbles animation */
.anim-beer-bubble {
  position: absolute;
  width: var(--size);
  height: var(--size);
  left: var(--xOff);
  bottom: -10px;
  border-radius: 50%;
  background: var(--color);
  opacity: 0;
  animation: animBeerBubble ease-out forwards;
  box-shadow: 0 0 4px var(--color), inset 0 0 2px rgba(255,255,255,.5);
}
@keyframes animBeerBubble {
  0% { transform: translateY(0) translateX(0) scale(0.5); opacity: 0; }
  20% { opacity: .9; transform: translateY(-15px) translateX(var(--wobble)) scale(1); }
  60% { opacity: .7; transform: translateY(-45px) translateX(calc(var(--wobble) * -0.5)) scale(1.1); }
  100% { transform: translateY(-80px) translateX(var(--wobble)) scale(0.4); opacity: 0; }
}

/* Potion targeting */
.potion-target-valid {
  box-shadow: 0 0 16px rgba(255,180,50,.8), 0 0 30px rgba(255,140,0,.4), inset 0 0 8px rgba(255,180,50,.3);
  border-color: rgba(255,180,50,.9) !important;
  animation: targetValidPulse 1s ease-in-out infinite;
}
@keyframes targetValidPulse {
  0%, 100% { box-shadow: 0 0 16px rgba(255,180,50,.8), 0 0 30px rgba(255,140,0,.4), inset 0 0 8px rgba(255,180,50,.3); }
  50% { box-shadow: 0 0 24px rgba(255,180,50,1), 0 0 45px rgba(255,140,0,.6), inset 0 0 14px rgba(255,180,50,.5); }
}
/* Direct-click borrow picker highlight (Charme Lv1, Singing). Layered
   ON TOP of `.potion-target-valid` for prompts that auto-commit on
   first click — distinctly flashier so players immediately notice the
   "click here" affordance. Bright magenta-cyan double glow, faster
   pulse than the standard targeting yellow. */
.borrow-pick-target {
  box-shadow:
    0 0 22px rgba(255,80,255,1),
    0 0 44px rgba(80,220,255,.85),
    0 0 70px rgba(255,80,255,.6),
    inset 0 0 16px rgba(255,140,255,.7) !important;
  border-color: #ff66ff !important;
  border-width: 3px !important;
  animation: borrowPickPulse .55s ease-in-out infinite !important;
  z-index: 4;
  position: relative;
}
@keyframes borrowPickPulse {
  0%, 100% {
    box-shadow:
      0 0 22px rgba(255,80,255,1),
      0 0 44px rgba(80,220,255,.85),
      0 0 70px rgba(255,80,255,.6),
      inset 0 0 16px rgba(255,140,255,.7);
    transform: scale(1);
  }
  50% {
    box-shadow:
      0 0 36px rgba(255,140,255,1),
      0 0 70px rgba(120,240,255,1),
      0 0 110px rgba(255,80,255,.8),
      inset 0 0 26px rgba(255,180,255,.9);
    transform: scale(1.06);
  }
}
.potion-target-selected {
  box-shadow: 0 0 20px rgba(255,50,50,.9), 0 0 40px rgba(255,30,30,.5), inset 0 0 12px rgba(255,50,50,.4) !important;
  border-color: #ff3333 !important;
  animation: potionPulse .8s ease-in-out infinite;
}
/* Ineligible targets: visually present but grayed out so the player
   sees at a glance which of their own board Creatures fail the current
   effect's filter (e.g. Dragon Pilot's ≤Lv1 inherent-path requirement).
   Pointer-events off so clicks fall through to anything below. */
.potion-target-ineligible {
  filter: grayscale(0.9) brightness(0.55);
  opacity: 0.65;
  pointer-events: none;
}
@keyframes potionPulse {
  0%, 100% { box-shadow: 0 0 20px rgba(255,50,50,.9), 0 0 40px rgba(255,30,30,.5), inset 0 0 12px rgba(255,50,50,.4); }
  50% { box-shadow: 0 0 30px rgba(255,50,50,1), 0 0 55px rgba(255,30,30,.7), inset 0 0 18px rgba(255,50,50,.6); }
}

/* Beer targeting — green selected targets */
.beer-targeting .potion-target-selected {
  box-shadow: 0 0 20px rgba(50,220,80,.9), 0 0 40px rgba(30,200,50,.5), inset 0 0 12px rgba(50,220,80,.4) !important;
  border-color: #33dd55 !important;
  animation: beerPulse .8s ease-in-out infinite;
}
@keyframes beerPulse {
  0%, 100% { box-shadow: 0 0 20px rgba(50,220,80,.9), 0 0 40px rgba(30,200,50,.5), inset 0 0 12px rgba(50,220,80,.4); }
  50% { box-shadow: 0 0 30px rgba(50,220,80,1), 0 0 55px rgba(30,200,50,.7), inset 0 0 18px rgba(50,220,80,.6); }
}

/* Sacrifice targeting — bright red eligible-target highlight (Occultism).
   Default valid-target highlight is yellow/orange; sacrifice flavor flips
   it to a deeper, faster-pulsing red so the player can immediately see
   which Creatures are about to die. Layered ON TOP of `.potion-target-valid`
   via the `.sacrifice-targeting` board wrapper (added when the prompt
   config carries `redSelect: true`). */
/* WAEHLBAR vs. GEWAEHLT (Ueberarbeitung 8.8.)
   Vorher unterschieden sich beide Zustaende nur in Nuancen — beide rot,
   beide pulsierend, 2px gegen 3px Rand. Auf einem Brett voller roter
   Kandidaten war praktisch nicht zu sehen, welche man schon angeklickt
   hatte. Jetzt trennen sie DREI Merkmale statt eines:
     · Waehlbar  = ruhiger, duenner roter Rahmen, langsames Atmen.
     · Gewaehlt  = WEISSER dicker Ring mit rotem Kern, deutlich groesser,
                   plus roter Schleier ueber der ganzen Karte und ein
                   grosses ✖ in der Mitte.
   Das Abzeichen liegt INNEN (die Zone hat overflow: hidden, ein Badge
   am Rand waere abgeschnitten). */
.sacrifice-targeting .potion-target-valid {
  box-shadow:
    0 0 14px rgba(255,70,70,.6),
    inset 0 0 10px rgba(255,80,80,.22) !important;
  border-color: rgba(255,95,95,.85) !important;
  border-width: 2px !important;
  animation: sacrificeTargetPulse 1.5s ease-in-out infinite !important;
}
@keyframes sacrificeTargetPulse {
  0%, 100% {
    box-shadow:
      0 0 14px rgba(255,70,70,.6),
      inset 0 0 10px rgba(255,80,80,.22);
  }
  50% {
    box-shadow:
      0 0 24px rgba(255,95,95,.85),
      inset 0 0 15px rgba(255,95,95,.35);
  }
}

.sacrifice-targeting .potion-target-selected {
  position: relative;
  z-index: 5;
  border-color: #ffffff !important;
  border-width: 4px !important;
  transform: scale(1.09);
  box-shadow:
    0 0 0 3px rgba(220,0,0,.95),
    0 0 34px rgba(255,40,40,1),
    0 0 72px rgba(190,0,0,.8),
    inset 0 0 26px rgba(255,20,20,.6) !important;
  animation: sacrificeSelectedPulse 1.1s ease-in-out infinite !important;
}
@keyframes sacrificeSelectedPulse {
  0%, 100% {
    box-shadow:
      0 0 0 3px rgba(220,0,0,.95),
      0 0 34px rgba(255,40,40,1),
      0 0 72px rgba(190,0,0,.8),
      inset 0 0 26px rgba(255,20,20,.6);
  }
  50% {
    box-shadow:
      0 0 0 4px rgba(255,60,60,1),
      0 0 52px rgba(255,90,90,1),
      0 0 100px rgba(220,0,0,1),
      inset 0 0 36px rgba(255,45,45,.85);
  }
}
/* Roter Schleier + Kreuz ueber der gewaehlten Karte — das eigentliche
   Erkennungsmerkmal. Deckend genug, um sofort ins Auge zu fallen, aber
   durchsichtig genug, dass Werte und Bild lesbar bleiben. */
.sacrifice-targeting .potion-target-selected::after {
  content: '✖';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 34px;
  font-weight: 700;
  color: rgba(255,255,255,.95);
  text-shadow:
    0 0 10px rgba(255,0,0,1),
    0 2px 4px rgba(0,0,0,.9);
  background: rgba(180,0,0,.28);
  pointer-events: none;
  z-index: 6;
}

/* Golden targeting — Divine Zeal and any radiant-flavor effect. The
   valid-target highlight is a soft amber outline so the eligible set
   stays legible without competing for attention; the selected highlight
   blows up into a dramatic blazing white-gold halo at a noticeably
   larger scale + fast pulse, so picked targets are unmistakable next
   to the merely-eligible ones. Layered ON TOP of `.potion-target-*`
   via the `.golden-targeting` board wrapper (added when the prompt
   config carries `goldSelect: true`). */
.golden-targeting .potion-target-valid {
  box-shadow:
    0 0 12px rgba(255,200,40,.55),
    inset 0 0 6px rgba(255,220,80,.25) !important;
  border-color: rgba(255,200,40,.7) !important;
  border-width: 2px !important;
  animation: goldenTargetValidPulse 1.4s ease-in-out infinite !important;
  filter: none !important;
}
@keyframes goldenTargetValidPulse {
  0%, 100% {
    box-shadow:
      0 0 12px rgba(255,200,40,.55),
      inset 0 0 6px rgba(255,220,80,.25);
  }
  50% {
    box-shadow:
      0 0 20px rgba(255,210,60,.75),
      inset 0 0 10px rgba(255,230,100,.35);
  }
}
.golden-targeting .potion-target-selected {
  box-shadow:
    0 0 48px rgba(255,255,220,1),
    0 0 92px rgba(255,220,40,1),
    0 0 160px rgba(255,160,0,.9),
    0 0 240px rgba(255,120,0,.55),
    inset 0 0 36px rgba(255,255,200,1) !important;
  border-color: #ffffff !important;
  border-width: 5px !important;
  animation: goldenTargetSelectedPulse .5s ease-in-out infinite !important;
  /* Promote to its own compositor layer so the big box-shadow + scale
     animation doesn't repaint through the parent on every cursor
     move (which produced visible black flicker on the selected slot
     during pointermove events). `translateZ(0)` forces GPU compositing;
     `backface-visibility: hidden` stops the rasterizer from briefly
     showing a black backface during sub-pixel transitions. */
  will-change: transform, box-shadow;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  transform: translateZ(0);
}
@keyframes goldenTargetSelectedPulse {
  0%, 100% {
    box-shadow:
      0 0 48px rgba(255,255,220,1),
      0 0 92px rgba(255,220,40,1),
      0 0 160px rgba(255,160,0,.9),
      0 0 240px rgba(255,120,0,.55),
      inset 0 0 36px rgba(255,255,200,1);
    transform: translateZ(0) scale(1.06);
  }
  50% {
    box-shadow:
      0 0 70px rgba(255,255,240,1),
      0 0 130px rgba(255,240,120,1),
      0 0 210px rgba(255,190,20,1),
      0 0 300px rgba(255,140,0,.85),
      inset 0 0 50px rgba(255,255,230,1);
    transform: translateZ(0) scale(1.14);
  }
}

/* Summon effect glow */
@keyframes summonGlow {
  0% { box-shadow: 0 0 0 transparent; }
  10% { box-shadow: 0 0 40px rgba(255,255,100,1), 0 0 80px rgba(255,220,50,.6), inset 0 0 20px rgba(255,255,100,.5); border-color: rgba(255,255,100,.9); }
  30% { box-shadow: 0 0 50px rgba(255,255,100,.9), 0 0 100px rgba(255,220,50,.5), inset 0 0 25px rgba(255,255,100,.4); }
  60% { box-shadow: 0 0 30px rgba(255,255,100,.5), inset 0 0 12px rgba(255,255,100,.2); }
  100% { box-shadow: 0 0 0 transparent; }
}
.zone-summon-glow {
  animation: summonGlow 1.4s ease-out forwards !important;
  z-index: 20;
  position: relative;
}

/* Zone picker — highlighted zones during effect zone selection */
.zone-pick-target {
  box-shadow: 0 0 18px rgba(100,255,100,.7), 0 0 35px rgba(80,220,80,.4), inset 0 0 8px rgba(100,255,100,.3) !important;
  border-color: rgba(100,255,100,.8) !important;
  animation: zonePickPulse .9s ease-in-out infinite !important;
  cursor: pointer !important;
}
@keyframes zonePickPulse {
  0%, 100% { box-shadow: 0 0 18px rgba(100,255,100,.7), 0 0 35px rgba(80,220,80,.4), inset 0 0 8px rgba(100,255,100,.3); }
  50% { box-shadow: 0 0 28px rgba(100,255,100,1), 0 0 50px rgba(80,220,80,.6), inset 0 0 14px rgba(100,255,100,.5); }
}

/* Creature drag — valid/invalid zone feedback */
.zone-drag-valid {
  box-shadow: 0 0 10px rgba(100,255,100,.4), inset 0 0 5px rgba(100,255,100,.15);
  border-color: rgba(100,255,100,.5) !important;
}
.zone-drag-invalid {
  opacity: 0.35;
  filter: saturate(0.3);
}
/* Bounce-place target (Deepsea swap gesture) — cyan/teal pulse on
   occupied slots where the dragged Creature can swap with the
   occupant. Overrides the invalid-zone dimming. */
/* Verstaerkt 8.8.: der alte Schein lag als Rahmen UM die Zone — auf
   einem BELEGTEN Platz sitzt aber ein Kartenbild drin, gegen das ein
   Randleuchten kaum ankommt (Als Befund beim Klick auf Blue-Ice
   Dragon: "zwar gehighlightet, aber nur schwach"). Jetzt liegt der
   Hinweis AUF der Karte: dicker Innenring plus ein cyanfarbener
   Schleier ueber die ganze Zone, dazu ein schnellerer Puls. */
.zone-bounce-place-target {
  box-shadow:
    inset 0 0 0 3px rgba(170,245,255,.95),
    0 0 20px rgba(80,210,255,.95),
    0 0 40px rgba(40,170,230,.7) !important;
  border-color: #8ce8ff !important;
  border-width: 3px !important;
  animation: bouncePlacePulse .9s ease-in-out infinite !important;
  cursor: pointer !important;
  opacity: 1 !important;
  filter: none !important;
  z-index: 4;
}
.zone-bounce-place-target::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0,150,220,.26);
  pointer-events: none;
  z-index: 5;
  animation: bouncePlaceVeil .9s ease-in-out infinite;
}
@keyframes bouncePlacePulse {
  0%, 100% {
    box-shadow:
      inset 0 0 0 3px rgba(170,245,255,.95),
      0 0 20px rgba(80,210,255,.95),
      0 0 40px rgba(40,170,230,.7);
  }
  50% {
    box-shadow:
      inset 0 0 0 4px rgba(220,252,255,1),
      0 0 32px rgba(150,240,255,1),
      0 0 60px rgba(70,200,255,.85);
  }
}
@keyframes bouncePlaceVeil {
  0%, 100% { background: rgba(0,150,220,.22); }
  50%      { background: rgba(60,200,255,.36); }
}

/* Bounce-outgoing hide — the destination Support Zone during a Deepsea
   swap. Keeps the zone's background/border visible (so the zone itself
   doesn't look like it moved) while hiding just the card contents, so
   the inbound flying card can visually "arrive" into an empty-looking
   slot. */
[data-bounce-hiding="true"] > * {
  visibility: hidden !important;
}

/* Creature-originated ram (Bear Rider's dash, etc.) — hide the inert
   copy of the Creature in its Support Zone for the duration of the
   flight so only the airborne ram card is visible. Mirrors the bounce-
   hiding rule: the slot's background / border stay rendered so the
   layout doesn't shift, just the card contents disappear. */
[data-support-ramming="true"] > * {
  visibility: hidden !important;
}

/* Additional Action provider selection */
.zone-provider-highlight {
  box-shadow: 0 0 16px rgba(255,200,0,.8), 0 0 30px rgba(255,180,0,.4), inset 0 0 8px rgba(255,200,0,.3) !important;
  border-color: rgba(255,200,0,.9) !important;
  animation: providerPulse 1s ease-in-out infinite !important;
  cursor: pointer !important;
}
@keyframes providerPulse {
  0%, 100% { box-shadow: 0 0 16px rgba(255,200,0,.8), 0 0 30px rgba(255,180,0,.4), inset 0 0 8px rgba(255,200,0,.3); }
  50% { box-shadow: 0 0 24px rgba(255,200,0,1), 0 0 45px rgba(255,180,0,.6), inset 0 0 14px rgba(255,200,0,.5); }
}
.zone-provider-dimmed {
  opacity: 0.3;
  filter: saturate(0.2);
}

/* Card gallery source badge (HAND / DECK) */
.gallery-source-badge {
  position: absolute;
  bottom: 3px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 1px;
  color: #fff;
  padding: 1px 6px;
  border-radius: 3px;
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(0,0,0,.6);
  z-index: 5;
}

/* Opponent targeting highlight — red glow on cards opponent is selecting */
.opp-target-highlight {
  box-shadow: 0 0 20px rgba(255,50,50,.8), 0 0 40px rgba(255,30,30,.4), inset 0 0 10px rgba(255,50,50,.3) !important;
  border-color: rgba(255,60,60,.8) !important;
  animation: oppTargetPulse .8s ease-in-out infinite !important;
}
@keyframes oppTargetPulse {
  0%, 100% { box-shadow: 0 0 20px rgba(255,50,50,.8), 0 0 40px rgba(255,30,30,.4), inset 0 0 10px rgba(255,50,50,.3); }
  50% { box-shadow: 0 0 30px rgba(255,50,50,1), 0 0 55px rgba(255,30,30,.6), inset 0 0 16px rgba(255,50,50,.5); }
}

/* Explosion animation on destroyed cards */
@keyframes zoneExplode {
  0% { transform: scale(1); filter: brightness(1); box-shadow: 0 0 0 transparent; }
  15% { transform: scale(1.15); filter: brightness(2.5); box-shadow: 0 0 30px rgba(255,120,0,.9), 0 0 60px rgba(255,60,0,.5); }
  40% { transform: scale(1.1); filter: brightness(1.8); box-shadow: 0 0 40px rgba(255,80,0,.8), 0 0 80px rgba(255,40,0,.4); }
  70% { transform: scale(0.9); filter: brightness(0.5); opacity: 0.4; }
  100% { transform: scale(0.8); filter: brightness(0); opacity: 0; }
}
.zone-exploding {
  animation: zoneExplode .7s ease-out forwards !important;
  z-index: 20;
  position: relative;
}
.board-zone-area { border-color: rgba(50,180,80,.5); background: rgba(50,180,80,.08); }
.board-zone-discard { border-color: rgba(120,120,120,.4); background: rgba(80,80,80,.08); }
.board-zone-deleted { border-color: rgba(100,60,60,.4); background: rgba(100,40,40,.08); }
.board-zone-deck { border-color: rgba(100,80,50,.5); background: rgba(100,80,50,.08); }
.board-zone-potion { border-color: rgba(80,140,100,.5); background: rgba(80,140,100,.08); }
.board-zone-empty {
  font-size: calc(8px * var(--board-scale));
  color: var(--text2);
  opacity: .5;
  text-transform: uppercase;
  letter-spacing: .5px;
  font-family: var(--font-display), sans-serif;
  text-align: center;
  line-height: 1.3;
}
.board-card {
  width: calc(64px * var(--board-scale));
  height: calc(90px * var(--board-scale));
  position: relative;
  overflow: hidden;
  /* `inherit` (not `default`) so a card picks up its context's pixel cursor:
     a hand card inherits the slot's grab, a clickable-zone card inherits the
     player-colour hover, and a plain card inherits the body arrow. `default`
     here forced the NATIVE cursor on every card, hiding the pixel set. */
  cursor: inherit;
  flex-shrink: 0;
  /* Container query context for the responsive HP/ATK strip below.
     `inline-size` containment means children can use @container (max-
     width: …) to react to THIS card's rendered width — not the
     viewport width. Lets the hero stats strip switch from row to
     stacked layout when the card itself is too narrow for both
     numbers, without any JS measurement. */
  container-type: inline-size;
}
.board-card.flipped { transform: rotate(180deg); }

/* Dark Deepsea God manifest — huge /DDG.png above the battlefield:
   starts small + transparent, swells to full size + full opacity,
   then fades out while continuing to grow past the original bounds.
   Total duration 2500ms; JS cleans the node up at 2700ms. */
@keyframes ddgManifest {
  0%   { opacity: 0;    transform: scale(0.35); filter: drop-shadow(0 0 40px rgba(255,40,60,0.85)); }
  20%  { opacity: 0.45; transform: scale(0.65); }
  50%  { opacity: 1;    transform: scale(1.00); }
  72%  { opacity: 1;    transform: scale(1.10); filter: drop-shadow(0 0 55px rgba(255,60,80,1)); }
  100% { opacity: 0;    transform: scale(1.45); filter: drop-shadow(0 0 70px rgba(255,80,100,0.5)); }
}

/* Yolomungandr boss-summon manifest — same fade-grow-pulse-fade arc as
   DDG but tinted with the Cool archetype's cyan + brass-gold palette
   so it reads as the Coolness Stack's apex finisher. */
@keyframes yolomungandrManifest {
  0%   { opacity: 0;    transform: scale(0.35); filter: drop-shadow(0 0 40px rgba(120,210,255,0.85)); }
  20%  { opacity: 0.45; transform: scale(0.65); filter: drop-shadow(0 0 50px rgba(150,225,255,0.95)); }
  50%  { opacity: 1;    transform: scale(1.00); filter: drop-shadow(0 0 60px rgba(180,235,255,1)) drop-shadow(0 0 24px rgba(255,215,140,0.55)); }
  72%  { opacity: 1;    transform: scale(1.10); filter: drop-shadow(0 0 70px rgba(180,235,255,1)) drop-shadow(0 0 32px rgba(255,215,140,0.7)); }
  100% { opacity: 0;    transform: scale(1.45); filter: drop-shadow(0 0 80px rgba(180,235,255,0.5)) drop-shadow(0 0 40px rgba(255,215,140,0.3)); }
}

/* HP display on board cards */
.board-card-hp {
  position: absolute;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: calc(10px * var(--board-scale));
  color: #fff;
  text-shadow: 0 0 4px #000, 0 0 2px #000, 1px 1px 0 #000;
  z-index: 4;
  pointer-events: none;
  line-height: 1;
}
/* Vertikaler Sitz der WÄHREND DES SPIELS aufgedruckten HP/ATK-Werte.
   Nach Als Karten-Layout-Änderung (3.8.) standen sie etwas zu hoch im
   Kartenbild; 3px → 1px setzt sie um zwei Pixel tiefer. Bewusst ein
   fester Wert und kein `calc(... * var(--board-scale))`: der Abstand
   soll bei jeder Brett-Skalierung gleich bleiben, sonst wandert der
   Text bei kleinen Skalierungen wieder nach oben. Gilt für beide
   Hero-Layouts (Legacy-Paar und Flex-Streifen) und für Creatures. */
.board-card-hp-hero {
  bottom: 1px;
  left: 5px;
}
.board-card-atk {
  position: absolute;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: calc(10px * var(--board-scale));
  color: #fff;
  text-shadow: 0 0 4px #000, 0 0 2px #000, 1px 1px 0 #000;
  z-index: 4;
  pointer-events: none;
  line-height: 1;
}
.board-card-atk-hero {
  bottom: 1px;
  right: 5px;
}

/* Hero HP/ATK responsive container.
   Used on Hero board cards in place of the legacy `.board-card-hp-hero`
   + `.board-card-atk-hero` pair. Lays HP and ATK out as flex children
   in a single absolute-positioned strip pinned to the bottom of the
   card.

   Two layouts:
     • Default (card is wide enough): horizontal — HP on the left,
       ATK on the right, both anchored at the card's bottom edge.
       Matches the legacy positioning (left:5px / right:5px corners).
     • Compact (card narrower than 55px via container query below):
       vertical — HP stacked on TOP of ATK, both horizontally
       centered. `flex-direction: column` puts the FIRST child (HP)
       at the container's top edge and the SECOND child (ATK) below;
       since the container is bottom-anchored at `bottom:3px` and
       grows upward to fit content, the bottom child (ATK) ends up
       at the card's bottom and HP sits ABOVE ATK. */
.board-card-stats-hero {
  position: absolute;
  bottom: 1px;
  left: 5px;
  right: 5px;
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  justify-content: space-between;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: calc(10px * var(--board-scale));
  color: #fff;
  text-shadow: 0 0 4px #000, 0 0 2px #000, 1px 1px 0 #000;
  z-index: 4;
  pointer-events: none;
  line-height: 1;
}

/* Compact layout for narrow cards (low resolution / small board scale).
   Threshold of 55px is below the default card width (64px @ scale 1.0)
   and above the smallest practical scale that keeps cards legible at
   all — so cards render side-by-side under normal play but stack
   vertically the moment the player drops into a low-resolution view.
   `flex-direction: column` + `align-items: center` produces HP-on-top
   and ATK-below, both horizontally centered. The container's bottom
   edge stays pinned at `bottom: 3px`; with two stacked children the
   container's height is now ~2 lines and its top edge moves UP into
   the card body. */
@container (max-width: 55px) {
  .board-card-stats-hero {
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    row-gap: calc(1px * var(--board-scale));
  }
}
.board-card-hp-creature {
  bottom: 1px;
  left: 50%;
  transform: translateX(-50%);
}

/* Light Ball aura — soft warm-yellow halo around the host Hero
   while the Attachment Spell is in their Support Zone. The aura is
   a visual-only marker (the actual negative-status protection is
   gated engine-side); a slow pulse keeps it ambient and unmistakable
   without competing for attention with damage / status overlays. */
.status-light-ball-aura {
  position: absolute;
  inset: -6px;
  pointer-events: none;
  z-index: 2;
  border-radius: 14px;
  animation: lightBallPulse 2.4s ease-in-out infinite;
}
@keyframes lightBallPulse {
  0%, 100% {
    opacity: 0.75;
    box-shadow:
      inset 0 0 20px rgba(255,225,130,.55),
      0 0 14px rgba(255,210,80,.45),
      0 0 28px rgba(255,200,60,.3);
  }
  50% {
    opacity: 1;
    box-shadow:
      inset 0 0 32px rgba(255,235,160,.85),
      0 0 24px rgba(255,220,110,.7),
      0 0 44px rgba(255,210,70,.5);
  }
}

/* Overheal barrier indicator (Nao, the Barrier Priestess) */
.board-card-overheal-barrier {
  position: absolute;
  top: 5%;
  left: 50%;
  transform: translateX(-50%);
  width: calc(52px * var(--board-scale));
  height: calc(52px * var(--board-scale));
  border: calc(3px * var(--board-scale)) solid rgba(255, 255, 255, 0.8);
  border-radius: 50%;
  box-shadow: 0 0 10px rgba(255, 255, 255, 0.4), inset 0 0 8px rgba(255, 255, 255, 0.15);
  z-index: 5;
  pointer-events: none;
  animation: overheal-pulse 2.5s ease-in-out infinite;
}
@keyframes overheal-pulse {
  0%, 100% { opacity: 0.7; }
  50% { opacity: 1; }
}
.board-card-guardian-shield {
  position: absolute;
  top: 3%;
  left: 50%;
  transform: translateX(-50%);
  width: calc(56px * var(--board-scale));
  height: calc(56px * var(--board-scale));
  border: calc(3px * var(--board-scale)) solid rgba(255, 60, 60, 0.85);
  border-radius: 50%;
  box-shadow: 0 0 12px rgba(255, 40, 40, 0.5), inset 0 0 10px rgba(255, 60, 60, 0.2);
  z-index: 5;
  pointer-events: none;
  animation: guardian-pulse 2s ease-in-out infinite;
}
@keyframes guardian-pulse {
  0%, 100% { opacity: 0.7; box-shadow: 0 0 12px rgba(255, 40, 40, 0.5), inset 0 0 10px rgba(255, 60, 60, 0.2); }
  50% { opacity: 1; box-shadow: 0 0 20px rgba(255, 40, 40, 0.7), inset 0 0 14px rgba(255, 60, 60, 0.35); }
}

/* Heal beam (Heal spell orbital strike) */
.heal-beam {
  position: fixed;
  width: 22px;
  background: linear-gradient(to top, rgba(68,255,136,0.9), rgba(68,255,136,0.3), transparent);
  border-radius: 11px;
  box-shadow: 0 0 16px rgba(68,255,136,0.6), 0 0 32px rgba(34,204,102,0.3);
  z-index: 99999;
  pointer-events: none;
}
.heal-beam-rise {
  height: 0;
  animation: healBeamRise 0.45s ease-in forwards;
}
.heal-beam-strike {
  height: 0;
  transform-origin: top center;
  animation: healBeamStrike 0.35s ease-in forwards;
}
@keyframes healBeamRise {
  0%   { height: 0; opacity: 1; transform: translateY(0); }
  50%  { height: 50vh; opacity: 0.9; transform: translateY(-50vh); }
  100% { height: 60vh; opacity: 0; transform: translateY(-100vh); }
}
@keyframes healBeamStrike {
  0%   { height: 0; opacity: 0; transform: translateY(-100vh); }
  40%  { height: 40vh; opacity: 0.8; transform: translateY(-40vh); }
  100% { height: 50vh; opacity: 1; transform: translateY(-50vh); }
}

/* Heal sparkle particles */
@keyframes healSparkleParticle {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.5); }
  30%  { opacity: 1; transform: translate(calc(var(--spark-tx) * 0.3), calc(var(--spark-ty) * 0.3)) scale(1.2); }
  100% { opacity: 0; transform: translate(var(--spark-tx), var(--spark-ty)) scale(0); }
}

@keyframes laserBeamShoot {
  0%   { opacity: 0; clip-path: inset(0 100% 0 0); }
  20%  { opacity: 1; clip-path: inset(0 0% 0 0); }
  60%  { opacity: 0.8; clip-path: inset(0 0% 0 0); }
  100% { opacity: 0; clip-path: inset(0 0% 0 60%); }
}

/* Thought bubbles (Brilliant Idea) */
@keyframes thoughtBubbleFloat {
  0%   { opacity: 0; transform: translateY(0) scale(0.4); }
  25%  { opacity: 1; transform: translateY(calc(var(--thought-rise) * 0.3)) scale(1.1); }
  70%  { opacity: 0.9; transform: translateY(calc(var(--thought-rise) * 0.8)) scale(1); }
  100% { opacity: 0; transform: translateY(var(--thought-rise)) scale(0.6); }
}

/* Guardian Angel descending animation */
.guardian-angel-descend {
  position: fixed;
  font-size: 40px;
  z-index: 99999;
  pointer-events: none;
  filter: drop-shadow(0 0 12px rgba(255,215,0,.8)) drop-shadow(0 0 24px rgba(255,255,200,.5));
  animation: guardianAngelDescent 1.1s ease-in forwards;
}
@keyframes guardianAngelDescent {
  0%   { transform: translateY(-100vh) scale(0.5); opacity: 0; }
  30%  { transform: translateY(-30vh) scale(1.2); opacity: 1; }
  70%  { transform: translateY(-5vh) scale(1); opacity: 1; }
  85%  { transform: translateY(0) scale(1.3); opacity: 1; }
  100% { transform: translateY(0) scale(2); opacity: 0; }
}

/* Hand Pick selection (Shard of Chaos) */
.hand-pick-eligible {
  outline: 2px dashed rgba(200,100,255,.5);
  outline-offset: 2px;
  cursor: pointer !important;
}
.hand-pick-selected {
  outline: 3px solid rgba(200,100,255,.9) !important;
  outline-offset: 2px;
  filter: brightness(1.3) drop-shadow(0 0 8px rgba(200,100,255,.6));
  transform: translateY(-6px);
  transition: transform .15s, filter .15s;
}

/* Urgent pick variant — Saint Nicolas's mandatory Potion-give pick
   demands more attention than the standard purple-dashed eligible
   outline. Solid teal-green border + pulsing glow + slight upward
   lift so the eligible Potions stand out from the rest of the hand.
   Composes ON TOP of `.hand-pick-eligible` (both classes are on the
   same slot), so this rule's box-shadow + animation win on cascade. */
.hand-pick-eligible-urgent {
  outline: 3px solid rgba(80, 220, 130, .95) !important;
  outline-offset: 2px;
  cursor: pointer !important;
  animation: stNicolasPickPulse 1.05s ease-in-out infinite;
}
.hand-pick-eligible-urgent .board-card {
  box-shadow: 0 0 14px rgba(80,220,130,.85), 0 0 26px rgba(40,180,90,.55) !important;
  transform: translateY(-4px);
  transition: transform .15s;
}
@keyframes stNicolasPickPulse {
  0%, 100% {
    box-shadow: 0 0 10px rgba(80,220,130,.55), 0 0 20px rgba(40,180,90,.30);
  }
  50% {
    box-shadow: 0 0 22px rgba(120,255,170,.95), 0 0 40px rgba(60,220,120,.65);
  }
}

/* Saint Nicolas escrow marker — paints the marked Potion (between
   pay and commit/refund) with a steady golden glow so the player
   sees which Potion is locked in to leave when the action resolves.
   Different palette from the urgent-pick pulse above (which is
   short-lived during the prompt) — escrow is the "committed,
   waiting" state, so the visual is calm but unmistakable. */
.hand-card-st-nicolas-escrowed {
  outline: 3px solid rgba(255, 200, 80, .95);
  outline-offset: 2px;
}
.hand-card-st-nicolas-escrowed .board-card {
  box-shadow: 0 0 12px rgba(255,200,80,.75), 0 0 22px rgba(220,160,40,.45) !important;
  filter: brightness(1.05);
}

/* Spectral Armor — teal hue overlay applied to the protected target
   for the duration of the halved-damage hit (~700ms). The animation
   component (see `spectral_armor` in app-board.jsx) walks
   `[data-hero-zone],[data-support-zone]` for the closest match to the
   damage origin, adds this class on mount, removes on cleanup. The
   colored drop-shadow gives a ghostly "armored" outline; the hue +
   saturation shift on the children paints the card itself teal
   without losing readability. !important pierces inline filter
   styles the renderer sets on the card image. */
[data-hero-zone].spectral-armor-teal,
[data-support-zone].spectral-armor-teal {
  animation: spectralArmorTint 700ms ease-out forwards;
}
[data-hero-zone].spectral-armor-teal > *,
[data-support-zone].spectral-armor-teal > * {
  animation: spectralArmorTintChild 700ms ease-out forwards !important;
}
@keyframes spectralArmorTint {
  0%   { filter: none; }
  15%, 75% {
    filter: drop-shadow(0 0 14px rgba(110, 240, 240, .9))
            drop-shadow(0 0 28px rgba(60, 200, 210, .55));
  }
  100% { filter: none; }
}
@keyframes spectralArmorTintChild {
  0%   { filter: none; }
  15%, 75% {
    filter: hue-rotate(155deg) saturate(1.4) brightness(1.05);
  }
  100% { filter: none; }
}
.blind-pick-active {
  overflow: visible !important;
}
.blind-pick-eligible {
  outline: 2px dashed rgba(255,150,50,.6);
  outline-offset: 2px;
  cursor: pointer !important;
  animation: blindPickPulse 1.2s ease-in-out infinite;
}
.hand-steal-highlight-active {
  overflow: visible !important;
  padding-top: 12px;
}
.blind-pick-selected {
  outline: 3px solid rgba(255,150,50,.95) !important;
  outline-offset: 2px;
  filter: brightness(1.4) drop-shadow(0 0 10px rgba(255,150,50,.7));
  transform: translateY(-8px);
  transition: transform .15s, filter .15s;
  z-index: 5;
  position: relative;
}
@keyframes blindPickPulse {
  0%, 100% { outline-color: rgba(255,150,50,.4); }
  50% { outline-color: rgba(255,150,50,.8); }
}
@keyframes handStealFly {
  0% { opacity: 1; transform: translate(0,0) scale(1) rotate(0deg); }
  15% { opacity: 1; transform: translate(0,-10px) scale(1.05) rotate(-5deg); }
  80% { opacity: 1; transform: translate(var(--steal-dx), calc(var(--steal-dy) - 8px)) scale(0.95) rotate(3deg); }
  100% { opacity: 1; transform: translate(var(--steal-dx), var(--steal-dy)) scale(1) rotate(0deg); }
}

/* Charmed hero (Charme Lv3) */
.hero-charmed {
  outline: 5px solid var(--charmed-color, #ff69b4) !important;
  outline-offset: -3px;
  box-shadow: 0 0 20px var(--charmed-color, #ff69b4), 0 0 40px var(--charmed-color, #ff69b4), inset 0 0 16px rgba(255,105,180,.2);
  animation: charmedPulse 1.5s ease-in-out infinite;
}
@keyframes charmedPulse {
  0%, 100% { box-shadow: 0 0 20px var(--charmed-color, #ff69b4), 0 0 40px var(--charmed-color, #ff69b4), inset 0 0 16px rgba(255,105,180,.2); }
  50% { box-shadow: 0 0 30px var(--charmed-color, #ff69b4), 0 0 50px var(--charmed-color, #ff69b4), inset 0 0 24px rgba(255,105,180,.3); }
}

/* Shard of Chaos screen effect */
@keyframes chaosScreenFlash {
  0%   { opacity: 0; background: rgba(255,0,200,.15); }
  15%  { opacity: 1; background: rgba(0,200,255,.2); }
  30%  { opacity: .8; background: rgba(255,200,0,.2); }
  50%  { opacity: 1; background: rgba(200,0,255,.25); }
  70%  { opacity: .6; background: rgba(0,255,100,.15); }
  85%  { opacity: .9; background: rgba(255,50,50,.2); }
  100% { opacity: 0; background: rgba(100,0,255,.1); }
}
.chaos-screen-overlay {
  position: fixed;
  inset: 0;
  z-index: 10100;
  pointer-events: none;
  animation: chaosScreenFlash 1.5s ease-in-out forwards;
}

/* Dead/ineligible hero zone */
.board-zone-dead {
  position: relative;
}
.board-zone-dead::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(12, 12, 20, 0.75);
  z-index: 3;
  pointer-events: none;
}

/* Play target highlight */
.board-zone-play-target {
  box-shadow: 0 0 12px rgba(51,255,136,.5) inset;
  border-color: var(--success) !important;
}

/* Attach-pick target: a much louder highlight for the Alex-style "click a
   hero or zone to attach" prompts. Pulsing outer ring + inner glow + a slight
   scale-up so the eye locks onto valid drop targets immediately. Stacks on
   top of `.board-zone-play-target` (both classes are applied together). */
.attach-pick-target {
  position: relative;
  z-index: 4;
  animation: attachPickPulse 1.1s ease-in-out infinite;
  outline: 3px solid #7fffaa;
  outline-offset: 2px;
}
@keyframes attachPickPulse {
  0%, 100% {
    box-shadow:
      0 0 0 0 rgba(140, 255, 180, 0.55),
      0 0 18px 4px rgba(80, 230, 140, 0.55),
      inset 0 0 14px rgba(120, 255, 170, 0.45);
  }
  50% {
    box-shadow:
      0 0 0 6px rgba(140, 255, 180, 0),
      0 0 36px 12px rgba(120, 255, 170, 0.85),
      inset 0 0 22px rgba(160, 255, 200, 0.7);
  }
}

/* Extra-dim overlay for ineligible heroes during an attach-pick. Adds a
   cool-grey desaturation on top of the existing dead-zone darken so the
   eligible/ineligible split reads at a glance. */
.attach-pick-dim::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(8, 10, 20, 0.82);
  z-index: 3;
  pointer-events: none;
}
.attach-pick-dim {
  filter: saturate(0.25) brightness(0.55);
}

/* Panel glow so the instruction card pulls the eye at the same rhythm as
   the board targets. */
.attach-pick-panel {
  box-shadow: 0 0 28px rgba(120, 255, 170, 0.55), 0 0 0 1px rgba(120, 255, 170, 0.4);
  animation: attachPickPanelPulse 1.1s ease-in-out infinite;
}
@keyframes attachPickPanelPulse {
  0%, 100% { box-shadow: 0 0 28px rgba(120, 255, 170, 0.55), 0 0 0 1px rgba(120, 255, 170, 0.4); }
  50%      { box-shadow: 0 0 48px rgba(160, 255, 200, 0.85), 0 0 0 2px rgba(160, 255, 200, 0.6); }
}

/* Stacked abilities */
.board-zone-ability {
  overflow: visible;
  position: relative;
}
.board-ability-stack {
  position: relative;
  width: 100%;
  height: 100%;
}
.board-ability-stack-card {
  position: absolute;
  left: 0;
  width: 100%;
}
.board-ability-stack-card:first-child { position: relative; }
.board-ability-stack .board-card-label {
  position: absolute;
  bottom: 2px; left: 50%; right: auto;
  transform: translateX(-50%);
  font-size: calc(13px * var(--board-scale));
  z-index: 5;
}
.board-card-text {
  width: 100%; height: 100%;
  display: flex;
  align-items: center; justify-content: center;
  font-size: 7px; text-align: center;
  padding: 2px; word-break: break-word;
  color: var(--text2); background: var(--bg3);
}
.board-card-label {
  position: absolute;
  bottom: 2px; right: 3px;
  font-size: calc(10px * var(--board-scale)); font-weight: 700;
  color: #fff;
  text-shadow: 0 0 4px #000, 0 0 2px #000;
  font-family: var(--font-display), sans-serif;
}
.deck-empty-label {
  position: absolute;
  bottom: 2px; right: 3px;
  font-size: calc(10px * var(--board-scale)); font-weight: 700;
  color: #fff;
  text-shadow: 0 0 4px #000, 0 0 2px #000;
  font-family: var(--font-display), sans-serif;
  opacity: 0.5;
}
.board-card.flipped .board-card-label {
  transform: rotate(180deg);
  top: 2px; bottom: auto; left: 3px; right: auto;
}
.board-stack {
  position: relative;
  width: calc(64px * var(--board-scale)); height: calc(90px * var(--board-scale));
}
.board-tooltip {
  position: fixed;
  z-index: 9999;
  /* Deckungsgleich mit der Chat-/Log-Spalte. Fruehere feste 360px passten
     nicht zu `--chat-col-w` (324px) UND ignorierten die Util-Leiste rechts
     daneben — der Tooltip stand also weder links buendig noch rechts an
     der richtigen Kante. Die beiden Variablen misst GameBoard zur Laufzeit
     aus `.chat-log-column`, damit auch der eingeklappte Zustand (28px) und
     responsive Ueberschreibungen automatisch stimmen. Die Fallbacks
     entsprechen dem alten Verhalten, falls die Messung nie laeuft. */
  width: var(--tt-col-w, 360px);
  background: var(--bg2);
  border-left: 1px solid var(--accent);
  box-shadow: -4px 0 20px rgba(0,0,0,.8);
  pointer-events: none;
  top: 0;
  right: 0;
  bottom: 0;
  overflow-y: auto;
  font-size: 14px;
}
/* When chain is visible, shift tooltip to the left of the chain panel */
body:has(.reaction-chain-overlay) .board-tooltip {
  right: 196px;
}

/* In battle / puzzle-player mode the player's hand strip
  (`.game-hand-me`, z-index 10000) renders ABOVE the tooltip
   (z-index 9999) by design — the hand stays interactive while a
   tooltip is open. Without an offset, the bottom ~15-20% of the
   tooltip would render behind the hand and be unreadable. Lift the
   tooltip's bottom edge above the hand strip so its full content
   area is visible AND the wheel-scroll redirect's `scrollHeight >
   clientHeight` test reflects the actual visible viewport.
   Only applies on screens that have a hand strip — puzzle editor /
   shop / deck builder don't render `.game-hand-me`, so the rule
   is naturally scoped via `body:has`. */
body:has(.game-hand-me) .board-tooltip {
  bottom: var(--hand-min-h);
}

/* ===== NOTIFICATION ===== */
.notification {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 9000;
  padding: 12px 20px;
  border: 1px solid var(--accent);
  background: var(--bg2);
  animation: slideIn .3s ease-out;
  max-width: 320px;
}
.notification.error { border-color: var(--danger); color: var(--danger); }
.notification.success { border-color: var(--success); color: var(--success); }

/* ===== MAGIC HAMMER ANIMATION ===== */

/* Hammerhead — metallic rectangle seen from the front, drops from above */
.anim-hammer-head {
  position: absolute;
  border-radius: 3px;
  background: linear-gradient(180deg,
    #e8e8e8 0%, #b8b8b8 15%, #a0a0a0 30%,
    #888 50%, #707070 70%, #585858 90%, #484848 100%);
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.55),
    inset 0 -2px 0 rgba(0,0,0,.45),
    inset 3px 0 0 rgba(255,255,255,.2),
    inset -3px 0 0 rgba(0,0,0,.25),
    0 6px 24px rgba(0,0,0,.7);
  border: 1px solid #555;
  animation: hammerDrop 1s cubic-bezier(.2,0,.8,.4) forwards;
  opacity: 0;
}

@keyframes hammerDrop {
  0%   { transform: translateY(-320px) scaleY(1); opacity: 1; }
  4%   { opacity: 1; }
  34%  { transform: translateY(0) scaleY(1); }
  38%  { transform: translateY(4px) scaleY(.7) scaleX(1.1); }
  44%  { transform: translateY(0) scaleY(1); }
  64%  { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(-280px); opacity: 0; }
}

/* Impact flash — wide horizontal ellipse burst */
.anim-hammer-impact {
  position: absolute;
  width: 140px;
  height: 50px;
  left: -70px;
  top: -25px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(255,255,255,.95) 0%, rgba(255,240,190,.5) 35%, transparent 70%);
  animation: hammerImpactFlash .45s ease-out .33s forwards;
  opacity: 0;
}

@keyframes hammerImpactFlash {
  0%   { transform: scale(.3, .3); opacity: 1; }
  35%  { transform: scale(1.4, 1); opacity: .85; }
  100% { transform: scale(2, 1.3); opacity: 0; }
}

/* Squash class — applied to the actual target DOM element */
.magic-hammer-squashed {
  animation: hammerSquash .65s ease-out forwards !important;
  transform-origin: center bottom !important;
}

@keyframes hammerSquash {
  0%   { transform: scaleY(1)    scaleX(1); }
  12%  { transform: scaleY(.12)  scaleX(1.3); }
  30%  { transform: scaleY(.12)  scaleX(1.3); }
  55%  { transform: scaleY(1.1)  scaleX(.94); }
  75%  { transform: scaleY(.96)  scaleX(1.02); }
  100% { transform: scaleY(1)    scaleX(1); }
}

/* ===== 3-HEADED GIANT — SPIKED CLUB SMASH ===== */

/* Grobe Holzkeule mit Stacheln, die von oben auf das Ziel kracht.
   Timing bewusst am Magic Hammer ausgerichtet, aber schwerer: der
   Aufprall liegt bei 38 % von 1 s, also nach 380 ms. Wer den Wert
   ändert, muss den `_delay` in cards/effects/3-headed-giant.js UND
   einen etwaigen SFX-Delay in app-shared.jsx mitziehen — das ist die
   Lehre aus dem Magic-Hammer-Fall (v197). */
.anim-club-shaft {
  position: absolute;
  /* Nach unten dicker: der Schlagkopf sitzt am unteren Ende. */
  border-radius: 6px 6px 10px 10px;
  background: linear-gradient(90deg,
    #3a2412 0%, #5a3a1e 12%, #7a5228 30%,
    #96682f 50%, #7a5228 68%, #52341b 88%, #331f0f 100%);
  box-shadow:
    inset 0 3px 0 rgba(255, 226, 170, .28),
    inset 0 -4px 0 rgba(0, 0, 0, .5),
    0 8px 28px rgba(0, 0, 0, .75);
  border: 1px solid #2a1a0c;
  animation: clubSmashDrop 1s cubic-bezier(.25, 0, .75, .35) forwards;
  opacity: 0;
}

/* Maserung — zwei dunkle Längsstreifen, damit die Keule als Holz und
   nicht als Metallbalken liest. */
.anim-club-shaft::before {
  content: '';
  position: absolute;
  inset: 8% 32% 14% 30%;
  border-radius: 4px;
  background: repeating-linear-gradient(180deg,
    rgba(0, 0, 0, .22) 0 3px, rgba(0, 0, 0, 0) 3px 11px);
  pointer-events: none;
}

/* Stacheln: sitzen als Kinder IM Keulen-Div, fallen also mit. */
.anim-club-spike {
  position: absolute;
  width: 0;
  height: 0;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
  border-top: 13px solid #d8d2c4;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .6));
}
.anim-club-spike.left  { transform: rotate(-118deg); }
.anim-club-spike.right { transform: rotate(118deg); }
.anim-club-spike.tip   { transform: rotate(180deg); }

@keyframes clubSmashDrop {
  0%   { transform: translateY(-340px) rotate(-4deg); opacity: 1; }
  5%   { opacity: 1; }
  38%  { transform: translateY(0) rotate(0deg); }
  42%  { transform: translateY(7px) scaleY(.86) scaleX(1.08); }
  49%  { transform: translateY(0) scaleY(1) scaleX(1); }
  70%  { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(-300px) rotate(6deg); opacity: 0; }
}

/* Aufprall — breiter, erdiger Staubring statt des hellen Hammerblitzes. */
.anim-club-impact {
  position: absolute;
  width: 170px;
  height: 58px;
  left: -85px;
  top: -29px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(255, 248, 224, .9) 0%, rgba(198, 150, 88, .55) 38%, transparent 72%);
  animation: clubImpactBurst .5s ease-out .38s forwards;
  opacity: 0;
}

@keyframes clubImpactBurst {
  0%   { transform: scale(.25, .25); opacity: 1; }
  35%  { transform: scale(1.5, 1.05); opacity: .8; }
  100% { transform: scale(2.3, 1.5); opacity: 0; }
}

/* Ziel-Stauchung: kürzer und tiefer als beim Hammer — die Keule ist
   stumpf, das Ziel soll regelrecht in den Boden gedrückt werden. */
.club-smash-squashed {
  animation: clubSquash .7s ease-out forwards !important;
  transform-origin: center bottom !important;
}

@keyframes clubSquash {
  0%   { transform: scaleY(1)    scaleX(1); }
  10%  { transform: scaleY(.16)  scaleX(1.34); }
  34%  { transform: scaleY(.16)  scaleX(1.34); }
  58%  { transform: scaleY(1.12) scaleX(.92); }
  78%  { transform: scaleY(.97)  scaleX(1.03); }
  100% { transform: scaleY(1)    scaleX(1); }
}

/* ===== GIGANTISAUR STOMP ANIMATION ===== */

/* Giant scaly dinosaur leg — drops from the top of the screen,
   slams into the target, retracts upward. Wider/taller than the
   magic hammer and travels MUCH further so it really reads as
   "from off-screen." */
.anim-dino-leg {
  position: absolute;
  border-radius: 22px 22px 14px 14px;
  background: linear-gradient(180deg,
    #3a5a25 0%, #2f4e1d 12%, #345a22 28%,
    #2a4a18 48%, #1f3a10 68%, #18300a 88%, #102506 100%);
  background-image:
    linear-gradient(180deg,
      #3a5a25 0%, #2f4e1d 12%, #345a22 28%,
      #2a4a18 48%, #1f3a10 68%, #18300a 88%, #102506 100%),
    repeating-linear-gradient(45deg,
      rgba(255,255,255,.04) 0 4px, transparent 4px 14px),
    repeating-linear-gradient(-45deg,
      rgba(0,0,0,.18) 0 5px, transparent 5px 18px);
  background-blend-mode: normal, overlay, multiply;
  box-shadow:
    inset 8px 0 14px rgba(0,0,0,.55),
    inset -8px 0 14px rgba(0,0,0,.55),
    inset 0 6px 0 rgba(255,255,255,.18),
    inset 0 -8px 18px rgba(0,0,0,.6),
    0 14px 50px rgba(0,0,0,.85);
  border: 2px solid #0a1a04;
  animation: dinoStompDrop 1.35s cubic-bezier(.18,0,.72,.45) forwards;
  opacity: 0;
}

/* Three triangular claws hanging off the bottom edge of the leg */
.anim-dino-leg::after {
  content: '';
  position: absolute;
  left: -8%;
  bottom: -18px;
  width: 116%;
  height: 28px;
  background:
    linear-gradient(135deg, transparent 47%, #18300a 47% 53%, transparent 53%) 0 0/40% 100% no-repeat,
    linear-gradient(135deg, transparent 47%, #18300a 47% 53%, transparent 53%) 30% 0/40% 100% no-repeat,
    linear-gradient(135deg, transparent 47%, #18300a 47% 53%, transparent 53%) 60% 0/40% 100% no-repeat;
  clip-path: polygon(
    0   0,   10% 0,   12% 100%,
    20% 0,   30% 0,   32% 100%,
    40% 0,   50% 0,   52% 100%,
    60% 0,   70% 0,   72% 100%,
    80% 0,   90% 0,   92% 100%,
    100% 0,  100% 0,  100% 0
  );
}

/* Highlight stripe down the front of the leg for some shape */
.anim-dino-leg::before {
  content: '';
  position: absolute;
  left: 18%; top: 8%;
  width: 18%; height: 80%;
  background: linear-gradient(180deg,
    rgba(160,210,120,.32) 0%,
    rgba(120,170,80,.18) 60%,
    transparent 100%);
  border-radius: 50%;
  filter: blur(4px);
}

@keyframes dinoStompDrop {
  0%   { transform: translateY(-820px) scaleY(1); opacity: 0; }
  4%   { opacity: 1; }
  38%  { transform: translateY(0) scaleY(1); }
  42%  { transform: translateY(6px) scaleY(.72) scaleX(1.18); }
  50%  { transform: translateY(0) scaleY(1); }
  62%  { transform: translateY(-4px) scaleY(1.02) scaleX(.99); }
  72%  { transform: translateY(0) scaleY(1); }
  100% { transform: translateY(-780px) scaleY(1); opacity: 0; }
}

/* Impact shockwave — wide brown dust ring blasting outward */
.anim-dino-impact {
  position: absolute;
  width: 220px;
  height: 90px;
  left: -110px;
  top: -45px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(190,150,90,.85) 0%,
    rgba(140,100,55,.55) 30%,
    rgba(90,60,30,.25) 55%,
    transparent 75%);
  animation: dinoImpactRing .7s ease-out .38s forwards;
  opacity: 0;
}

@keyframes dinoImpactRing {
  0%   { transform: scale(.25, .35); opacity: 1; }
  35%  { transform: scale(1.6, 1.1); opacity: .9; }
  100% { transform: scale(2.6, 1.6); opacity: 0; }
}

/* A dimmer secondary flash that lingers for an extra beat */
.anim-dino-impact-flash {
  position: absolute;
  width: 180px;
  height: 70px;
  left: -90px;
  top: -35px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(255,250,200,.7) 0%, rgba(255,225,150,.35) 40%, transparent 70%);
  animation: dinoImpactFlash .5s ease-out .36s forwards;
  opacity: 0;
}

@keyframes dinoImpactFlash {
  0%   { transform: scale(.3, .4); opacity: 1; }
  35%  { transform: scale(1.5, 1); opacity: .9; }
  100% { transform: scale(2.2, 1.4); opacity: 0; }
}

/* Squash class — applied to the actual target DOM element. Faster
   and uglier than magic-hammer's: target gets crushed nearly flat
   and lingers there a beat longer before springing back. */
.giant-dino-stomp-squashed {
  animation: dinoStompSquash .85s cubic-bezier(.2,.7,.3,1) forwards !important;
  transform-origin: center bottom !important;
}

@keyframes dinoStompSquash {
  0%   { transform: scaleY(1)    scaleX(1); }
  10%  { transform: scaleY(.08)  scaleX(1.4); }
  38%  { transform: scaleY(.08)  scaleX(1.4); }
  56%  { transform: scaleY(1.15) scaleX(.92); }
  72%  { transform: scaleY(.95)  scaleX(1.03); }
  100% { transform: scaleY(1)    scaleX(1); }
}

/* ── Wooly Mammoth Stomp (Slippery Whoolmoth) ─────────────────
   Brown fur palette + thicker silhouette than the dino leg.
   Uses the SAME `dinoStompDrop` keyframes for fall/squash/retreat
   timing so the impact lines up with the shared
   `anim-dino-impact` shockwave + `giant-dino-stomp-squashed`
   target squash. Only the leg surface, claws, and front
   highlight are different — everything else is shared. */
.anim-mammoth-leg {
  position: absolute;
  border-radius: 28px 28px 16px 16px;
  background: linear-gradient(180deg,
    #6b4a2a 0%, #5a3a20 14%, #6e4828 28%,
    #4e3318 50%, #3a2412 72%, #2a1808 92%, #1a1004 100%);
  /* Layered shaggy-fur stripes — diagonal short hairs in two
     directions create the matted-fur look without an asset. */
  background-image:
    linear-gradient(180deg,
      #6b4a2a 0%, #5a3a20 14%, #6e4828 28%,
      #4e3318 50%, #3a2412 72%, #2a1808 92%, #1a1004 100%),
    repeating-linear-gradient(110deg,
      rgba(255,235,200,.10) 0 2px, transparent 2px 6px,
      rgba(0,0,0,.22) 6px 8px, transparent 8px 13px),
    repeating-linear-gradient(70deg,
      rgba(0,0,0,.18) 0 3px, transparent 3px 9px);
  background-blend-mode: normal, overlay, multiply;
  box-shadow:
    inset 9px 0 16px rgba(0,0,0,.6),
    inset -9px 0 16px rgba(0,0,0,.6),
    inset 0 6px 0 rgba(255,220,170,.18),
    inset 0 -10px 22px rgba(0,0,0,.65),
    0 14px 50px rgba(0,0,0,.85);
  border: 2px solid #0a0604;
  animation: dinoStompDrop 1.35s cubic-bezier(.18,0,.72,.45) forwards;
  opacity: 0;
}
/* Three blunt toenails along the bottom — wider/rounder than the
   dino's three sharp claws (mammoths don't have hooked talons). */
.anim-mammoth-leg::after {
  content: '';
  position: absolute;
  left: -4%;
  bottom: -12px;
  width: 108%;
  height: 22px;
  background:
    radial-gradient(ellipse at 50% 0%, #2a1808 0% 38%, transparent 40%) 0% 0% / 34% 100% no-repeat,
    radial-gradient(ellipse at 50% 0%, #2a1808 0% 38%, transparent 40%) 50% 0% / 34% 100% no-repeat,
    radial-gradient(ellipse at 50% 0%, #2a1808 0% 38%, transparent 40%) 100% 0% / 34% 100% no-repeat;
}
/* Shaggy fur tufts hanging off the leg sides — a fringe of darker
   strands giving the silhouette a rough edge. */
.anim-mammoth-leg::before {
  content: '';
  position: absolute;
  left: -10%; top: 0;
  width: 120%; height: 100%;
  background:
    linear-gradient(90deg,
      transparent 0%,
      rgba(60,38,20,.55) 6%,
      transparent 12%,
      transparent 88%,
      rgba(60,38,20,.55) 94%,
      transparent 100%);
  pointer-events: none;
  filter: blur(.5px);
}

/* ── Slippery Spikeblock — Ice Block Crash ────────────────────
   A jagged ice block flies in from off-screen left, slows on
   impact (~38% of the keyframe), and shatters at ~50% with a
   frosty shockwave + cold flash. Distinct from `freeze` (snowball
   burst at the target) and from the overhead frost drops Blade /
   Cold Coffin use. */
.anim-ice-block {
  position: absolute;
  /* Anchor the block AT the target — translate from far-left to 0. */
  left: -90px; top: -54px;
  width: 90px; height: 90px;
  background:
    linear-gradient(135deg,
      rgba(245,252,255,.95) 0%, rgba(190,220,238,.95) 30%,
      rgba(140,180,210,.95) 60%, rgba(90,140,180,.95) 100%),
    repeating-linear-gradient(45deg,
      rgba(255,255,255,.18) 0 2px, transparent 2px 7px),
    repeating-linear-gradient(-45deg,
      rgba(120,170,200,.22) 0 3px, transparent 3px 10px);
  background-blend-mode: normal, overlay, multiply;
  /* Jagged ice silhouette — rough crystalline polygon shape. */
  clip-path: polygon(
    8% 18%, 22% 4%, 44% 12%, 62% 0%, 78% 8%, 94% 20%,
    100% 40%, 92% 58%, 100% 78%, 84% 92%, 64% 100%,
    44% 92%, 22% 100%, 6% 84%, 0% 60%, 4% 38%
  );
  border: 1px solid rgba(60,110,150,.6);
  box-shadow:
    inset 6px 6px 10px rgba(255,255,255,.55),
    inset -8px -8px 14px rgba(40,80,120,.55),
    0 6px 18px rgba(80,140,180,.55);
  animation: iceBlockFly 700ms cubic-bezier(.4,.1,.6,.7) forwards;
  opacity: 0;
}
@keyframes iceBlockFly {
  0%   { transform: translate(-560px, -20px) rotate(-25deg) scale(1); opacity: 0; }
  10%  { opacity: 1; }
  38%  { transform: translate(0, 0) rotate(0deg) scale(1); opacity: 1; }
  44%  { transform: translate(8px, 4px) rotate(6deg) scale(1.12); opacity: 1; }
  60%  { transform: translate(4px, 2px) rotate(0deg) scale(.7); opacity: .55; }
  100% { transform: translate(0, 0) rotate(0deg) scale(.2); opacity: 0; }
}

/* Frosty shockwave ring on impact — pale blue-white instead of the
   dino-stomp brown. */
.anim-ice-block-impact {
  position: absolute;
  width: 220px;
  height: 90px;
  left: -110px;
  top: -45px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(220,240,255,.9) 0%,
    rgba(140,200,240,.55) 30%,
    rgba(80,150,200,.25) 55%,
    transparent 75%);
  animation: iceBlockImpactRing .7s ease-out .40s forwards;
  opacity: 0;
}
@keyframes iceBlockImpactRing {
  0%   { transform: scale(.25, .35); opacity: 1; }
  35%  { transform: scale(1.6, 1.15); opacity: .9; }
  100% { transform: scale(2.6, 1.6); opacity: 0; }
}

/* Cool-tinted secondary flash. */
.anim-ice-block-flash {
  position: absolute;
  width: 170px;
  height: 70px;
  left: -85px;
  top: -35px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(240,250,255,.85) 0%, rgba(170,220,250,.4) 40%, transparent 70%);
  animation: iceBlockFlash .55s ease-out .38s forwards;
  opacity: 0;
}
@keyframes iceBlockFlash {
  0%   { transform: scale(.3, .4); opacity: 1; }
  35%  { transform: scale(1.55, 1.1); opacity: .9; }
  100% { transform: scale(2.3, 1.55); opacity: 0; }
}

/* ── Gigantisaur Ankylos Tail Smash ───────────────────────────
   Pivots from the LEFT edge of the assembly (club end) so the
   club lands on the target while the tail trails offscreen-right.
   See `ankylo_tail_smash` in app-board.jsx for the JSX side. */
.anim-ankylo-tail-pivot {
  animation: ankyloTailSwing 1.3s cubic-bezier(.18,0,.62,.45) forwards;
  opacity: 0;
}

.anim-ankylo-tail {
  background:
    linear-gradient(180deg,
      #3a5a25 0%, #2f4e1d 15%, #345a22 30%,
      #2a4a18 50%, #1f3a10 70%, #18300a 90%, #102506 100%),
    repeating-linear-gradient(90deg,
      rgba(0,0,0,.22) 0 8px, transparent 8px 26px);
  background-blend-mode: normal, multiply;
  border-radius: 24px 14px 14px 24px;
  border: 2px solid #0a1a04;
  box-shadow:
    inset 0 4px 0 rgba(255,255,255,.18),
    inset 0 -6px 16px rgba(0,0,0,.55),
    0 6px 22px rgba(0,0,0,.55);
}

.anim-ankylo-club {
  border-radius: 50%;
  background: radial-gradient(circle at 38% 35%,
    #6c8a48 0%, #4a6a28 28%, #2f4e1d 58%, #18300a 86%, #0a1a04 100%);
  border: 3px solid #0a1a04;
  box-shadow:
    inset -8px -6px 14px rgba(0,0,0,.55),
    inset 6px 4px 10px rgba(255,255,255,.18),
    0 8px 28px rgba(0,0,0,.85);
}

/* Bone-spike protrusions jutting from the club. ::before is the
   leading edge (faces the target), ::after the trailing edge. */
.anim-ankylo-club::before {
  content: '';
  position: absolute;
  left: -22%;
  top: 38%;
  width: 42%;
  height: 24%;
  clip-path: polygon(100% 0, 100% 100%, 0 50%);
  background: linear-gradient(90deg, #e4d8b0 0%, #b8a880 60%, #8c7a50 100%);
  filter: drop-shadow(0 1px 1px rgba(0,0,0,.55));
}
.anim-ankylo-club::after {
  content: '';
  position: absolute;
  right: -22%;
  top: 38%;
  width: 42%;
  height: 24%;
  clip-path: polygon(0 0, 0 100%, 100% 50%);
  background: linear-gradient(90deg, #8c7a50 0%, #b8a880 40%, #e4d8b0 100%);
  filter: drop-shadow(0 1px 1px rgba(0,0,0,.55));
}

@keyframes ankyloTailSwing {
  0%   { opacity: 0; transform: translate(180px, -540px) rotate(-78deg); }
  6%   { opacity: 1; }
  22%  { transform: translate(140px, -260px) rotate(-50deg); }
  34%  { transform: translate(54px,  -64px) rotate(-18deg); }
  40%  { transform: translate(0,       0)   rotate(0deg); }
  44%  { transform: translate(-4px,    6px) rotate(2deg); }
  52%  { transform: translate(0,       0)   rotate(0deg); }
  68%  { opacity: 1; transform: translate(0, 0) rotate(0deg); }
  100% { opacity: 0; transform: translate(220px, -540px) rotate(-90deg); }
}

.anim-ankylo-impact {
  position: absolute;
  width: 240px;
  height: 100px;
  left: -120px;
  top: -50px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(200,160,90,.85) 0%,
    rgba(150,110,55,.55) 30%,
    rgba(90,60,30,.25) 55%,
    transparent 75%);
  animation: ankyloImpactRing .75s ease-out .40s forwards;
  opacity: 0;
}
@keyframes ankyloImpactRing {
  0%   { transform: scale(.25, .35); opacity: 1; }
  35%  { transform: scale(1.7, 1.2); opacity: .9; }
  100% { transform: scale(2.8, 1.7); opacity: 0; }
}

.anim-ankylo-impact-flash {
  position: absolute;
  width: 200px;
  height: 80px;
  left: -100px;
  top: -40px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(255,245,200,.75) 0%, rgba(255,220,140,.35) 40%, transparent 70%);
  animation: ankyloImpactFlash .55s ease-out .38s forwards;
  opacity: 0;
}
@keyframes ankyloImpactFlash {
  0%   { transform: scale(.3, .4); opacity: 1; }
  35%  { transform: scale(1.6, 1.1); opacity: .9; }
  100% { transform: scale(2.3, 1.5); opacity: 0; }
}

.stranglehold-squeezed {
  animation: strangleholdSqueeze 1.1s ease-in-out forwards !important;
  transform-origin: center center !important;
}
@keyframes strangleholdSqueeze {
  0%   { transform: scaleX(1); }
  30%  { transform: scaleX(.12); }
  55%  { transform: scaleX(.12); }
  75%  { transform: scaleX(1.08); }
  90%  { transform: scaleX(.97); }
  100% { transform: scaleX(1); }
}

@keyframes tigerFadeInOut {
  0%   { opacity: 0; transform: scale(0.3); }
  20%  { opacity: 1; transform: scale(1.2); }
  35%  { opacity: 1; transform: scale(1); }
  70%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.3); }
}

/* Dumbbell pump animation — Muscle Training */
@keyframes dumbbellPump {
  0%   { transform: translateY(0) scale(0.3); opacity: 0; }
  8%   { transform: translateY(0) scale(1); opacity: 1; }
  20%  { transform: translateY(-30px) scale(1); }
  32%  { transform: translateY(5px) scale(1.08); }
  50%  { transform: translateY(-30px) scale(1); }
  62%  { transform: translateY(5px) scale(1.08); }
  78%  { transform: translateY(-8px) scale(1); opacity: 1; }
  100% { transform: translateY(-12px) scale(0.6); opacity: 0; }
}

/* Water ripple — Jump in the River */
@keyframes waterRipple {
  0%   { transform: scale(0.3); opacity: 0; }
  15%  { opacity: 0.7; }
  100% { transform: scale(2.5); opacity: 0; }
}

/* Dark wave engulf — Spawn Mother's AOE damage. The wave body rises
   from below the target, peaks at full coverage, then recedes off the
   top. CSS variable `--engulf-h` is the target's height in px so the
   wave scales to fit creatures (small) and heroes (taller). */
@keyframes darkWaveRise {
  0%   { transform: translateY(calc(var(--engulf-h, 140px) * 0.6)); opacity: 0; }
  18%  { opacity: 0.95; }
  55%  { transform: translateY(calc(var(--engulf-h, 140px) * -0.05)); opacity: 0.95; }
  85%  { transform: translateY(calc(var(--engulf-h, 140px) * -0.15)); opacity: 0.85; }
  100% { transform: translateY(calc(var(--engulf-h, 140px) * -0.95)); opacity: 0; }
}

/* Foam crest riding the wave's leading edge. Same vertical motion +
   a subtle scale-out so the crest "spreads" as it climbs. */
@keyframes darkWaveCrest {
  0%   { transform: translateY(calc(var(--engulf-h, 140px) * 0.55)) scaleX(0.9); opacity: 0; }
  20%  { opacity: 0.9; }
  55%  { transform: translateY(calc(var(--engulf-h, 140px) * -0.10)) scaleX(1.05); opacity: 0.9; }
  100% { transform: translateY(calc(var(--engulf-h, 140px) * -0.95)) scaleX(1.15); opacity: 0; }
}

/* Spray droplet — flung from the wave crest at impact, decays via
   gravity-style downward arc + fade. dx/dy are negative-up vectors. */
@keyframes darkSprayDrop {
  0%   { transform: translate(0, 0) scale(1); opacity: 0; }
  15%  { opacity: 1; }
  100% { transform: translate(var(--dx, 0), var(--dy, -40px)) scale(0.4); opacity: 0; }
}

/* Sunglasses drop — Divine Gift of Coolness */
@keyframes sunglassesDrop {
  0%   { transform: translateY(-80px) scale(0.6); opacity: 0; }
  15%  { opacity: 1; }
  50%  { transform: translateY(0px) scale(1); opacity: 1; }
  65%  { transform: translateY(-8px) scale(1); opacity: 1; }
  75%  { transform: translateY(0px) scale(1.05); opacity: 1; }
  85%  { transform: translateY(0px) scale(1); opacity: 1; }
  100% { transform: translateY(0px) scale(0.8); opacity: 0; }
}

/* Whirlwind Strike spin effect on attacker */
.whirlwind-spinning {
  animation: whirlwindSpin 2s ease-in-out forwards !important;
  transform-origin: center center !important;
}
@keyframes whirlwindSpin {
  0%   { transform: rotate(0deg) scale(1); }
  8%   { transform: rotate(180deg) scale(1.05); }
  15%  { transform: rotate(540deg) scale(1.1); }
  25%  { transform: rotate(1080deg) scale(1.1); }
  50%  { transform: rotate(2160deg) scale(1.05); }
  75%  { transform: rotate(3060deg) scale(1); }
  90%  { transform: rotate(3500deg) scale(1); }
  100% { transform: rotate(3600deg) scale(1); }
}

/* ===== ICE PROJECTILE TRAIL ===== */

.projectile-ice-trail {
  position: absolute;
  inset: -24px;
  border-radius: 50%;
  background:
    radial-gradient(ellipse at 30% 40%, rgba(180,220,255,.5) 0%, transparent 40%),
    radial-gradient(ellipse at 70% 60%, rgba(140,200,250,.4) 0%, transparent 35%),
    radial-gradient(ellipse at 50% 50%, rgba(100,170,240,.3) 0%, transparent 50%);
  box-shadow:
    -8px -4px 6px rgba(180,220,255,.3),
    -16px -2px 8px rgba(140,200,250,.2),
    -24px 4px 10px rgba(100,180,240,.15),
    -32px -6px 6px rgba(200,230,255,.2),
    -40px 2px 8px rgba(160,210,250,.12),
    -48px -3px 5px rgba(180,220,255,.1),
    -12px 8px 5px rgba(220,240,255,.25),
    -28px 7px 7px rgba(170,215,250,.15),
    -36px -8px 4px rgba(200,230,255,.12);
  animation: iceTrailPulse 80ms ease-in-out infinite alternate;
  pointer-events: none;
}

@keyframes iceTrailPulse {
  0%   { opacity: .6; transform: scale(1); }
  100% { opacity: .9; transform: scale(1.15); }
}

/* ===== ICE BOLT PROJECTILE ===== */

.projectile-ice-bolt {
  display: block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%,
    #fff 0%, #c8e8ff 25%, #70b8ee 55%, #3888cc 100%);
  box-shadow:
    0 0 14px rgba(100,180,255,.9),
    0 0 28px rgba(50,130,220,.5),
    9px -5px 0 3px rgba(180,220,255,.5),
    -7px 7px 0 2px rgba(140,200,250,.4),
    13px 4px 0 2px rgba(200,230,255,.45),
    -11px -9px 0 3px rgba(160,210,250,.35),
    5px 11px 0 2px rgba(190,225,255,.3),
    -4px -13px 0 2px rgba(170,215,250,.35);
  animation: iceBoltPulse 90ms ease-in-out infinite alternate;
}

@keyframes iceBoltPulse {
  0%   { transform: scale(1); filter: brightness(1); }
  100% { transform: scale(1.15); filter: brightness(1.2); }
}

/* ===== LOVE SHOT HEART PROJECTILE =====
   Big light-pink heart that flies slowly from caster to target. The
   class sits inside `.projectile-rotation-wrap` (which rotates by
   `--projAngle` to point shaped projectiles like arrows / phoenix
   at the target). Hearts have a clear top/bottom orientation, so we
   counter-rotate by `-projAngle` to keep the heart visually upright
   regardless of flight angle (src → tgt). The pulse + drop-shadow
   glow live on a `::before` pseudo-element so they don't clobber the
   counter-rotation transform on the host element. */
.projectile-love-heart {
  display: inline-block;
  line-height: 1;
  /* Counter-rotates the parent rotation-wrap's `rotate(--projAngle)`,
     so the heart stays upright for any trajectory. */
  transform: rotate(calc(-1 * var(--projAngle, 0deg)));
}
.projectile-love-heart::before {
  content: '💗';
  font-size: 96px;
  display: inline-block;
  filter:
    drop-shadow(0 0 14px rgba(255, 180, 220, .95))
    drop-shadow(0 0 28px rgba(255, 140, 200, .65))
    drop-shadow(0 0 44px rgba(255, 110, 180, .35));
  animation: loveHeartPulse 240ms ease-in-out infinite alternate;
}
@keyframes loveHeartPulse {
  0%   { transform: scale(1);    filter: brightness(1) drop-shadow(0 0 14px rgba(255,180,220,.95)); }
  100% { transform: scale(1.12); filter: brightness(1.15) drop-shadow(0 0 22px rgba(255,150,210,1)); }
}

.projectile-love-trail {
  position: absolute;
  inset: -32px;
  border-radius: 50%;
  background: radial-gradient(ellipse,
    rgba(255, 200, 230, .55) 0%,
    rgba(255, 160, 210, .35) 35%,
    rgba(255, 120, 190, .15) 60%,
    transparent 80%);
  animation: loveTrailPulse 110ms ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes loveTrailPulse {
  0%   { opacity: .55; transform: scale(.95); }
  100% { opacity: .9;  transform: scale(1.18); }
}

/* ===== RACCOON PROJECTILE (Smugness) ===== */
.projectile-raccoon-trail {
  position: absolute;
  inset: -24px;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(139,90,43,.7) 0%, rgba(180,120,60,.3) 40%, transparent 70%);
  animation: raccoonTrailPulse 100ms ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes raccoonTrailPulse {
  0%   { opacity: .7; transform: scale(.9); }
  100% { opacity: 1; transform: scale(1.15); }
}

/* ===== ROCKET FIST PROJECTILE (Rocket Fist) ===== */
/* A directional rocket-engine exhaust streaming BEHIND the fist. The
   flight-path rotation `--projAngle` is set on `.projectile-anim` and
   inherits down here; we rotate to it, then push the flame back along
   that same axis so it always trails the fist (rather than haloing it
   like the symmetric `.projectile-flame-trail`). */
.rocket-fist-trail {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 78px;
  height: 22px;
  z-index: -1; /* render behind the fist */
  transform-origin: 50% 50%;
  transform: translate(-50%, -50%) rotate(var(--projAngle, 0deg)) translateX(-42px);
  border-radius: 50%;
  /* White-hot at the nozzle (right, by the fist) → orange → out. */
  background: linear-gradient(to left,
    rgba(255,255,235,.95) 0%,
    rgba(255,214,96,.92) 24%,
    rgba(255,132,24,.70) 52%,
    rgba(255,58,0,.34)   76%,
    transparent          100%);
  filter: blur(2px) drop-shadow(0 0 8px rgba(255,140,32,.8));
  animation: rocketFistFlame 64ms ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes rocketFistFlame {
  0%   { opacity: .72; filter: blur(2.4px) brightness(1)    drop-shadow(0 0 7px rgba(255,140,32,.7)); }
  100% { opacity: 1;   filter: blur(1.6px) brightness(1.35) drop-shadow(0 0 13px rgba(255,165,45,.95)); }
}

/* ===== IDEJ PROJECTION — damage-absorb hologram ===== */
/* A green holographic dome flares over the Projection's slot — it
   "ate" the hit in the Hero's place — then dissolves as the
   Projection flies off to the discard pile. Scanlines on the dome
   sell the hologram look. */
.anim-idej-absorb-dome {
  position: absolute;
  border-radius: 50%;
  background:
    repeating-linear-gradient(0deg, transparent 0 3px, rgba(150,255,190,.28) 3px 5px),
    radial-gradient(circle, rgba(170,255,205,.9) 0%, rgba(48,232,128,.6) 42%, rgba(20,170,86,.22) 70%, transparent 86%);
  border: 2px solid rgba(170,255,205,.95);
  box-shadow: 0 0 34px rgba(60,255,140,.95), inset 0 0 34px rgba(130,255,185,.8);
  animation: idejAbsorbDome 880ms cubic-bezier(.18,.72,.3,1) forwards;
  pointer-events: none;
}
@keyframes idejAbsorbDome {
  0%   { transform: scale(.18); opacity: 0; }
  22%  { transform: scale(1.18); opacity: 1; }
  50%  { transform: scale(.98); opacity: 1; }
  100% { transform: scale(1.3);  opacity: 0; }
}
.anim-idej-absorb-ring {
  position: absolute;
  border-radius: 50%;
  border: 3px solid rgba(95,255,155,.85);
  box-shadow: 0 0 18px rgba(60,255,140,.8), inset 0 0 12px rgba(120,255,180,.5);
  animation: idejAbsorbRing 900ms ease-out forwards;
  pointer-events: none;
}
@keyframes idejAbsorbRing {
  0%   { transform: scale(.12); opacity: 0; }
  18%  { opacity: .95; }
  100% { transform: scale(1.5); opacity: 0; }
}
.anim-idej-absorb-flash {
  position: absolute;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(232,255,242,.97) 0%, rgba(130,255,180,.62) 36%, transparent 72%);
  animation: idejAbsorbFlash 340ms ease-out forwards;
  pointer-events: none;
}
@keyframes idejAbsorbFlash {
  0%   { transform: scale(.3);  opacity: 0; }
  35%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(1.35); opacity: 0; }
}
.anim-idej-absorb-glyph {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 42px;
  color: #b6ffce;
  filter: drop-shadow(0 0 10px rgba(70,255,150,.95)) drop-shadow(0 0 20px rgba(40,220,110,.7));
  animation: idejAbsorbGlyph 880ms cubic-bezier(.2,.8,.3,1) forwards;
  pointer-events: none;
}
@keyframes idejAbsorbGlyph {
  0%   { transform: scale(.3) translateY(4px); opacity: 0; }
  28%  { transform: scale(1.15) translateY(0); opacity: 1; }
  60%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(1.1); opacity: 0; }
}

/* ===== CAPTURE NET (Capture Net) ===== */
/* A rope-mesh net plummets from the top of the screen onto the
   targeted Creature, squashing on impact before it fades as the
   Creature is hauled into the capturer's hand. The crossed
   repeating-linear-gradients form the diamond mesh; the border is the
   net's edge rope. `animation-duration` is set inline by the
   CaptureNetEffect component (from the broadcast `duration`). */
.anim-capture-net {
  position: absolute;
  border: 3px solid #7a5526;
  border-radius: 16px;
  background:
    repeating-linear-gradient(45deg,  transparent 0 8px, rgba(214,182,120,.95) 8px 11px),
    repeating-linear-gradient(-45deg, transparent 0 8px, rgba(214,182,120,.95) 8px 11px);
  box-shadow: 0 4px 14px rgba(0,0,0,.55), inset 0 0 22px rgba(90,60,20,.5);
  transform-origin: 50% 100%;
  animation-name: captureNetDrop;
  animation-timing-function: cubic-bezier(.55,.06,.78,.13); /* gravity ease-in */
  animation-fill-mode: forwards;
  pointer-events: none;
}
@keyframes captureNetDrop {
  0%   { transform: translateY(-115vh) scaleY(1.25) scaleX(.9); opacity: 0; }
  10%  { opacity: 1; }
  58%  { transform: translateY(0) scaleY(1) scaleX(1);          opacity: 1; }
  68%  { transform: translateY(0) scaleY(.78) scaleX(1.14);     opacity: 1; } /* impact squash */
  79%  { transform: translateY(0) scaleY(1.06) scaleX(.97);     opacity: 1; }
  88%  { transform: translateY(0) scaleY(1) scaleX(1);          opacity: 1; }
  100% { transform: translateY(0) scaleY(1) scaleX(1);          opacity: 0; }
}

/* ===== DARK ENERGY DELETION ANIMATION ===== */

.discard-anim-deleted {
  filter: brightness(.7) saturate(.5);
}

.delete-energy-overlay {
  position: absolute;
  inset: -6px;
  border-radius: 6px;
  background:
    radial-gradient(ellipse at 30% 20%, rgba(80,0,120,.5) 0%, transparent 50%),
    radial-gradient(ellipse at 70% 80%, rgba(40,0,80,.4) 0%, transparent 45%),
    radial-gradient(ellipse at 50% 50%, rgba(20,0,40,.3) 0%, transparent 60%);
  box-shadow:
    inset 0 0 12px rgba(100,0,160,.6),
    0 0 16px rgba(80,0,140,.5),
    0 0 32px rgba(60,0,120,.3);
  animation: deleteEnergyPulse .4s ease-in-out infinite alternate;
  pointer-events: none;
}

@keyframes deleteEnergyPulse {
  0%   { opacity: .6; transform: scale(1); }
  100% { opacity: 1; transform: scale(1.05); }
}

/* ===== GOLDEN ANKH REVIVAL ANIMATION ===== */

@keyframes ankhFloat {
  0%   { transform: translateY(0) scale(.5); opacity: 0; }
  15%  { opacity: 1; transform: translateY(0) scale(1.1); }
  80%  { opacity: .8; }
  100% { transform: translateY(var(--endY)) scale(.6); opacity: 0; }
}

/* ===== BUFF COLUMN ===== */

/* ═══ GLOBAL GAME TOOLTIP ═══ */
.game-tooltip {
  background: var(--bg2);
  border: 1px solid var(--accent);
  color: var(--text);
  padding: 3px 10px;
  font-size: 11px;
  font-family: var(--font-body), sans-serif;
  font-weight: 600;
  max-width: 280px;
  pointer-events: none;
  box-shadow: 0 2px 8px rgba(0,0,0,.6);
}

/* ═══ STATUS BADGES ═══ */
.status-badges-row {
  position: absolute;
  left: -2px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 8;
  pointer-events: auto;
}
.status-badge {
  position: relative;
  width: calc(18px * var(--board-scale));
  height: calc(18px * var(--board-scale));
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(10px * var(--board-scale));
  background: rgba(180,30,30,.85);
  border: 1.5px solid rgba(255,80,80,.7);
  border-radius: 50%;
  cursor: default;
  filter: drop-shadow(0 0 3px rgba(200,0,0,.5));
  animation: statusBadgePop .3s ease-out backwards;
}
.status-badge-duration {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translate(1px, -50%);
  font-family: var(--font-body), sans-serif;
  font-weight: 800;
  font-size: calc(11px * var(--board-scale));
  line-height: 1;
  color: #e6f6ff;
  text-shadow: 0 0 3px rgba(120,200,255,.9), 1px 1px 0 #000, -1px 1px 0 #000, 1px -1px 0 #000, -1px -1px 0 #000;
  pointer-events: none;
}

/* Bonus action counter (Ghuanjun combo, etc.) */
.bonus-action-counter {
  position: absolute;
  top: 2px;
  left: 2px;
  font-size: calc(11px * var(--board-scale));
  font-weight: 800;
  font-family: var(--font-body), sans-serif;
  background: rgba(255,180,0,.9);
  color: #000;
  padding: calc(1px * var(--board-scale)) calc(5px * var(--board-scale));
  border-radius: 4px;
  z-index: 8;
  pointer-events: auto;
  filter: drop-shadow(0 0 4px rgba(255,150,0,.7));
  animation: statusBadgePop .3s ease-out;
}
.status-badge:nth-child(2) { animation-delay: .05s; }
.status-badge:nth-child(3) { animation-delay: .1s; }
.status-badge:nth-child(4) { animation-delay: .15s; }
.status-badge:nth-child(5) { animation-delay: .2s; }
/* Blinded: eye glyph + a diagonal red slash overlay so the icon reads as
   "crossed-out eye" without depending on a Unicode glyph that mixes
   poorly across platforms. The ::after line uses a CSS rotation so the
   line scales naturally with --board-scale. */
.status-badge.status-blinded::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    to top right,
    transparent calc(50% - 1.2px),
    rgba(255,255,255,.95) calc(50% - 1.2px),
    rgba(255,80,80,1) 50%,
    rgba(255,255,255,.95) calc(50% + 1.2px),
    transparent calc(50% + 1.2px)
  );
  border-radius: 50%;
}
@keyframes statusBadgePop {
  from { transform: scale(0); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* ── Ascension Orbs ── */
.ascension-orbs-container {
  position: absolute;
  top: 0; left: 0; width: 100%; height: 100%;
  pointer-events: none;
  z-index: 7;
}
.ascension-orb {
  position: absolute;
  width: calc(10px * var(--board-scale, 1));
  height: calc(10px * var(--board-scale, 1));
  border-radius: 50%;
  border: 1.5px solid rgba(255,255,255,.35);
  transform: translate(-50%, -50%);
  pointer-events: auto;
  cursor: default;
  transition: background .4s, box-shadow .4s, transform .3s;
}
.ascension-orb-collected {
  border-color: rgba(255,255,255,.8);
  animation: orbCollect .5s ease-out;
}
@keyframes orbCollect {
  0%   { transform: translate(-50%,-50%) scale(0.3); opacity: 0; }
  60%  { transform: translate(-50%,-50%) scale(1.5); }
  100% { transform: translate(-50%,-50%) scale(1); opacity: 1; }
}

/* ascension-button removed — ascension via drag/click instead */
@keyframes ascendPulse {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(170,68,255,.6)); }
  50%      { filter: drop-shadow(0 0 12px rgba(170,68,255,1)); }
}

/* ── Ascension Drop Glow ── */
.ascension-drop-glow {
  position: absolute;
  inset: -4px;
  border-radius: 8px;
  border: 2px solid rgba(170,68,255,.9);
  box-shadow: 0 0 20px rgba(170,68,255,.7), inset 0 0 15px rgba(170,68,255,.3);
  z-index: 6;
  pointer-events: none;
  animation: ascendDropPulse .8s ease-in-out infinite;
}
@keyframes ascendDropPulse {
  0%, 100% { box-shadow: 0 0 20px rgba(170,68,255,.5), inset 0 0 15px rgba(170,68,255,.2); }
  50%      { box-shadow: 0 0 35px rgba(170,68,255,.9), inset 0 0 25px rgba(170,68,255,.5); }
}

/* ── Ascension Burst Overlay ── */
.ascension-burst {
  border-radius: 8px;
  overflow: hidden;
  background: radial-gradient(ellipse at center, rgba(255,255,255,.95) 0%, rgba(170,68,255,.6) 30%, rgba(255,68,170,.3) 60%, transparent 80%);
  animation: ascensionBurstAnim 2s ease-out forwards;
}
@keyframes ascensionBurstAnim {
  0%   { opacity: 0; transform: scale(0.5); }
  15%  { opacity: 1; transform: scale(1.3); }
  40%  { opacity: .8; transform: scale(1.0); }
  100% { opacity: 0; transform: scale(2.5); }
}

/* ── Ascension Particles ── */
@keyframes ascensionParticle {
  0%   { opacity: 0; transform: translate(-50%,-50%) scale(0.3); }
  20%  { opacity: 1; transform: translate(calc(-50% + var(--ap-x) * 0.3), calc(-50% + var(--ap-y) * 0.2)) scale(1.2); }
  100% { opacity: 0; transform: translate(calc(-50% + var(--ap-x)), calc(-50% + var(--ap-y))) scale(0); }
}

/* ── Firework Sparks ── */
@keyframes fireworkSpark {
  0%   { opacity: 0; transform: translate(-50%,-50%) scale(0); }
  15%  { opacity: 1; transform: translate(-50%,-50%) scale(1.5); }
  100% { opacity: 0; transform: translate(calc(-50% + var(--fw-x)), calc(-50% + var(--fw-y))) scale(0.3); }
}

/* ── Ascension Ring ── */
@keyframes ascensionRing {
  0%   { width: 0; height: 0; opacity: 1; border-width: 3px; }
  70%  { opacity: .8; border-width: 2px; }
  100% { width: 250px; height: 250px; opacity: 0; border-width: 1px; }
}

/* ── Card Name Picker ── */
.card-name-picker-row:hover { background: var(--bg4) !important; }

/* ── Alleria Spider Thread ── */
@keyframes spiderThread {
  0%   { width: 0; opacity: 0; }
  30%  { opacity: 1; }
  100% { width: var(--thread-len); opacity: 1; }
}
@keyframes spiderThreadFade {
  to { opacity: 0; }
}
@keyframes spiderRun {
  0%   { opacity: 0; transform: translate(0, 0) scaleX(1); }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--sr-x), var(--sr-y)) scaleX(-1); }
}

/* ── Controlled Attack Dark Energy ── */
@keyframes darkTendril {
  0%   { opacity: 0; transform: translate(0, 0) scale(1.5); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dt-x), var(--dt-y)) scale(0.3); }
}
@keyframes darkControlPulse {
  0%   { opacity: 0; transform: scale(0.3); }
  50%  { opacity: .8; border-color: #9b30ff; box-shadow: 0 0 20px #9b30ff, inset 0 0 20px rgba(75,0,130,.3); }
  100% { opacity: 0; transform: scale(2.5); border-color: transparent; }
}
@keyframes darkEyeAppear {
  0%   { opacity: 0; transform: scale(0.3); }
  50%  { opacity: 1; transform: scale(1.2); }
  80%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.8); }
}

/* ── Burning Finger Fiery Slash ── */
@keyframes fierySlash {
  0%   { opacity: 0; transform: rotate(-30deg) scaleX(0.2); }
  30%  { opacity: 1; }
  60%  { opacity: 1; transform: rotate(-30deg) scaleX(1.3); }
  100% { opacity: 0; transform: rotate(-30deg) scaleX(0.5); }
}
@keyframes fireSpark {
  0%   { opacity: 0; transform: translate(0, 0) scale(1); }
  20%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--fs-x), var(--fs-y)) scale(0.2); }
}

/* ── Punch Impact ── */
@keyframes punchStrike {
  0%   { opacity: 0; transform: scale(2) translate(10px, -10px); }
  30%  { opacity: 1; transform: scale(1.3) translate(0, 0); }
  60%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.8); }
}
@keyframes punchRing {
  0%   { opacity: 0; transform: scale(0.3); }
  40%  { opacity: .8; }
  100% { opacity: 0; transform: scale(2.5); }
}
@keyframes punchLine {
  0%   { opacity: 0; width: 0; }
  30%  { opacity: 1; }
  100% { opacity: 0; }
}

/* ── Cardinal Beast Baihu Petrify ── */
@keyframes petrifyFlash {
  0%   { opacity: 0; transform: scale(0.5); }
  30%  { opacity: .8; }
  70%  { opacity: .6; transform: scale(1.05); }
  100% { opacity: 0; transform: scale(1.1); }
}

/* Persistent petrification overlay */
.baihu-petrify-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  border-radius: inherit;
  background:
    repeating-linear-gradient(45deg, rgba(120,115,110,.15) 0px, rgba(120,115,110,.15) 2px, transparent 2px, transparent 6px),
    repeating-linear-gradient(-45deg, rgba(100,100,95,.1) 0px, rgba(100,100,95,.1) 1px, transparent 1px, transparent 5px),
    radial-gradient(ellipse at 30% 40%, rgba(160,155,150,.25), transparent 60%);
  animation: petrifyShimmer 3s ease-in-out infinite alternate;
  mix-blend-mode: multiply;
}
@keyframes petrifyShimmer {
  0%   { opacity: .6; }
  100% { opacity: .85; }
}

/* ── Cardinal Beast Win Celebration ── */
@keyframes cardinalAnimalRight {
  0%   { transform: translate(-50%,-50%) scale(0); opacity: 0; }
  15%  { opacity: 1; transform: translate(-50%,-50%) scale(1.5); }
  100% { opacity: 0; transform: translate(200px,-50%) scale(2) rotate(360deg); }
}
@keyframes cardinalAnimalDown {
  0%   { transform: translate(-50%,-50%) scale(0); opacity: 0; }
  15%  { opacity: 1; transform: translate(-50%,-50%) scale(1.5); }
  100% { opacity: 0; transform: translate(-50%,200px) scale(2) rotate(360deg); }
}
@keyframes cardinalAnimalLeft {
  0%   { transform: translate(-50%,-50%) scale(0); opacity: 0; }
  15%  { opacity: 1; transform: translate(-50%,-50%) scale(1.5); }
  100% { opacity: 0; transform: translate(-300px,-50%) scale(2) rotate(-360deg); }
}
@keyframes cardinalAnimalUp {
  0%   { transform: translate(-50%,-50%) scale(0); opacity: 0; }
  15%  { opacity: 1; transform: translate(-50%,-50%) scale(1.5); }
  100% { opacity: 0; transform: translate(-50%,-300px) scale(2) rotate(-360deg); }
}
@keyframes cardinalFirework {
  0%   { transform: scale(0); opacity: 0; }
  30%  { opacity: 1; transform: scale(1.5); }
  100% { opacity: 0; transform: scale(0.3); }
}
@keyframes cardinalTitle {
  0%   { opacity: 0; transform: translate(-50%,-50%) scale(0.5); }
  30%  { opacity: 1; transform: translate(-50%,-50%) scale(1.1); }
  70%  { opacity: 1; transform: translate(-50%,-50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%,-50%) scale(1.2); }
}

/* ── Slippery Skates move highlighting ── */
.zone-skates-creature {
  box-shadow: 0 0 8px 2px rgba(0,200,255,.5) !important;
  animation: skatesCreaturePulse 1.2s ease-in-out infinite;
}
.zone-skates-selected {
  box-shadow: 0 0 12px 4px rgba(0,255,200,.7) !important;
  outline: 2px solid rgba(0,255,200,.8);
}
.zone-skates-dest {
  box-shadow: 0 0 10px 3px rgba(0,255,100,.5) !important;
  animation: skatesDestPulse 1s ease-in-out infinite;
}
@keyframes skatesCreaturePulse {
  0%, 100% { box-shadow: 0 0 8px 2px rgba(0,200,255,.4); }
  50%      { box-shadow: 0 0 14px 4px rgba(0,200,255,.7); }
}
@keyframes skatesDestPulse {
  0%, 100% { box-shadow: 0 0 8px 2px rgba(0,255,100,.4); }
  50%      { box-shadow: 0 0 14px 4px rgba(0,255,100,.7); }
}

/* ── Slippery Start-Phase sub-mode highlighting ── */
.zone-slippery-creature {
  box-shadow: 0 0 8px 2px rgba(127,208,255,.55) !important;
  animation: slipperyCreaturePulse 1.3s ease-in-out infinite;
}
.zone-slippery-selected {
  box-shadow: 0 0 14px 5px rgba(170,235,255,.85) !important;
  outline: 2px solid rgba(220,250,255,.9);
}
.zone-slippery-dest {
  box-shadow: 0 0 10px 3px rgba(140,255,220,.55) !important;
  animation: slipperyDestPulse 1.05s ease-in-out infinite;
}
@keyframes slipperyCreaturePulse {
  0%, 100% { box-shadow: 0 0 8px 2px rgba(127,208,255,.45); }
  50%      { box-shadow: 0 0 16px 5px rgba(127,208,255,.8); }
}
@keyframes slipperyDestPulse {
  0%, 100% { box-shadow: 0 0 8px 2px rgba(140,255,220,.45); }
  50%      { box-shadow: 0 0 16px 5px rgba(140,255,220,.8); }
}

/* ── Slippery Pengu Ability-move highlighting ──
   Double box-shadow (hard ring + soft glow) so the highlights remain
   visible on occupied Ability Zones — a glow-only treatment was
   getting visually masked by stacked Ability cards. */
.zone-pengue-src {
  box-shadow: 0 0 0 3px rgba(127,208,255,.95), 0 0 14px 5px rgba(127,208,255,.55) !important;
  animation: pengueSrcPulse 1.3s ease-in-out infinite;
  position: relative;
  z-index: 2;
}
.zone-pengue-selected {
  box-shadow: 0 0 0 4px rgba(220,250,255,.95), 0 0 18px 6px rgba(170,235,255,.8) !important;
  outline: 2px solid rgba(220,250,255,.95);
  position: relative;
  z-index: 3;
}
.zone-pengue-dest {
  box-shadow: 0 0 0 4px rgba(140,255,220,.95), 0 0 16px 6px rgba(140,255,220,.65) !important;
  animation: pengueDestPulse 1.05s ease-in-out infinite;
  position: relative;
  z-index: 2;
}
@keyframes pengueSrcPulse {
  0%, 100% { box-shadow: 0 0 0 3px rgba(127,208,255,.85), 0 0 12px 4px rgba(127,208,255,.5); }
  50%      { box-shadow: 0 0 0 3px rgba(127,208,255,1),   0 0 20px 7px rgba(127,208,255,.85); }
}
@keyframes pengueDestPulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(140,255,220,.85), 0 0 12px 4px rgba(140,255,220,.5); }
  50%      { box-shadow: 0 0 0 4px rgba(140,255,220,1),   0 0 20px 7px rgba(140,255,220,.85); }
}

/* ── Chain Target Pick highlighting ── */
.chain-pick-valid {
  box-shadow: 0 0 12px 3px rgba(255,200,0,.6) !important;
  animation: chainPickPulse 1s ease-in-out infinite;
}
.chain-pick-selected {
  box-shadow: 0 0 14px 4px rgba(255,220,50,.8) !important;
  outline: 2px solid rgba(255,220,50,.9);
}
.chain-pick-number {
  position: absolute; top: 3px; left: 3px; z-index: 8;
  background: #ffcc00; color: #000; font-size: 11px; font-weight: 900;
  width: 20px; height: 20px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 8px rgba(255,200,0,.6);
  pointer-events: none;
}
@keyframes chainPickPulse {
  0%, 100% { box-shadow: 0 0 10px 2px rgba(255,200,0,.4); }
  50%      { box-shadow: 0 0 18px 5px rgba(255,200,0,.8); }
}

/* ── Chain Lightning Animation ── */
@keyframes lightningBolt {
  0%   { width: 0; opacity: 0; }
  10%  { opacity: 1; }
  40%  { width: var(--bolt-len); opacity: 1; }
  60%  { opacity: .7; }
  80%  { opacity: 1; }
  100% { width: var(--bolt-len); opacity: 0; }
}

/* ── Jumpscare Jack-in-the-Box ── */
@keyframes jumpscareFlash {
  0%   { opacity: 0; transform: scale(0.4); }
  35%  { opacity: 1; transform: scale(1.05); }
  100% { opacity: 0; transform: scale(1.3); }
}
@keyframes jumpscareBoxShake {
  0%   { transform: rotate(0deg); }
  20%  { transform: rotate(-12deg); }
  40%  { transform: rotate(12deg); }
  60%  { transform: rotate(-16deg); }
  80%  { transform: rotate(16deg); }
  100% { transform: rotate(0deg) scale(0.8); opacity: 0.5; }
}
@keyframes jumpscareSpring {
  0%   { opacity: 0; transform: translateY(0) scale(0.3); }
  40%  { opacity: 1; transform: translateY(-40px) scale(1.4); }
  70%  { opacity: 1; transform: translateY(-30px) scale(1.1); }
  100% { opacity: 0; transform: translateY(-35px) scale(0.9); }
}
@keyframes jumpscareStars {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.5); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--js-x), var(--js-y)) scale(0.3); }
}

/* ── Anti Magic Shield Bubble ── */
@keyframes magicBubble {
  0%   { opacity: 0; transform: scale(0.3); }
  20%  { opacity: .9; transform: scale(1.1); }
  40%  { transform: scale(0.95); }
  60%  { transform: scale(1.05); }
  80%  { opacity: .8; transform: scale(0.98); }
  100% { opacity: 0; transform: scale(1.15); }
}
@keyframes magicBubbleShine {
  0%   { opacity: 0; }
  20%  { opacity: .7; }
  80%  { opacity: .5; }
  100% { opacity: 0; }
}

/* ── Fireshield Corona ── */
@keyframes fireshieldFlame {
  0%   { opacity: 0; transform: rotate(var(--r, 0)) scale(0.4); }
  30%  { opacity: 1; transform: scale(1.2); }
  70%  { opacity: .8; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.6); }
}
@keyframes fireshieldGlow {
  0%   { opacity: 0; transform: scale(0.5); }
  30%  { opacity: .6; }
  70%  { opacity: .4; transform: scale(1.1); }
  100% { opacity: 0; transform: scale(1.3); }
}

/* ── Hand Card Reveal ── */
@keyframes revealCardsIn {
  0%   { opacity: 0; transform: translateX(-50%) translateY(-20px); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}
@keyframes revealCardsOut {
  0%   { opacity: 1; transform: translateX(-50%) translateY(0); }
  100% { opacity: 0; transform: translateX(-50%) translateY(-20px); }
}

/* ── Willy Leprechaun Particles ── */
@keyframes willyParticle {
  0%   { opacity: 0; transform: translate(-50%,-50%) scale(0.3) rotate(0deg); }
  25%  { opacity: 1; transform: translate(calc(-50% + var(--wp-x) * 0.3), calc(-50% + var(--wp-y) * 0.2)) scale(1.2) rotate(30deg); }
  100% { opacity: 0; transform: translate(calc(-50% + var(--wp-x)), calc(-50% + var(--wp-y))) scale(0.5) rotate(120deg); }
}

.buff-column {
  position: absolute;
  top: 3px;
  right: 3px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 5;
  pointer-events: auto;
}

.buff-icon {
  width: calc(20px * var(--board-scale));
  height: calc(20px * var(--board-scale));
  border-radius: 50%;
  background: rgba(100,180,255,.2);
  border: 1px solid rgba(150,210,255,.5);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(12px * var(--board-scale));
  cursor: default;
  box-shadow: 0 0 6px rgba(100,180,255,.3);
  animation: buffIconPulse 2s ease-in-out infinite;
}

@keyframes buffIconPulse {
  0%, 100% { box-shadow: 0 0 6px rgba(100,180,255,.3); }
  50% { box-shadow: 0 0 10px rgba(100,180,255,.5); }
}

/* ===== CLOUD GATHER ANIMATION ===== */

@keyframes cloudGather {
  0%   { transform: translate(0, 0) scale(.6); opacity: 0; }
  20%  { opacity: .8; }
  100% { transform: translate(calc(var(--startX) * -1), calc(var(--startY) * -1)) scale(.3); opacity: .4; }
}

@keyframes cloudFormCenter {
  0%   { transform: scale(.3); opacity: 0; }
  60%  { transform: scale(1.15); opacity: 1; }
  100% { transform: scale(1); opacity: .9; }
}

/* ===== CLOUD DISPERSE ANIMATION ===== */

@keyframes cloudDisperseCenter {
  0%   { transform: scale(1); opacity: .9; }
  40%  { transform: scale(1.1); opacity: .7; }
  100% { transform: scale(.4); opacity: 0; }
}

@keyframes cloudDisperse {
  0%   { transform: translate(0, 0) scale(.8); opacity: .9; }
  70%  { opacity: .6; }
  100% { transform: translate(var(--endX), var(--endY)) scale(.3); opacity: 0; }
}

/* ===== DARK GEAR SPIN ANIMATION ===== */

.anim-dark-gear {
  position: absolute;
  font-size: 72px;
  left: -36px;
  top: -40px;
  color: #1a1a1a;
  filter: drop-shadow(0 0 8px rgba(0,0,0,.8)) drop-shadow(0 0 16px rgba(40,0,60,.5));
  animation-duration: 1.4s;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}
.anim-dark-gear.cw  { animation-name: darkGearSpinCW; }
.anim-dark-gear.ccw { animation-name: darkGearSpinCCW; }

@keyframes darkGearSpinCW {
  0%   { transform: rotate(0deg) scale(.3); opacity: 0; }
  15%  { transform: rotate(40deg) scale(1); opacity: 1; }
  85%  { transform: rotate(320deg) scale(1); opacity: 1; }
  100% { transform: rotate(360deg) scale(.3); opacity: 0; }
}
@keyframes darkGearSpinCCW {
  0%   { transform: rotate(0deg) scale(.3); opacity: 0; }
  15%  { transform: rotate(-40deg) scale(1); opacity: 1; }
  85%  { transform: rotate(-320deg) scale(1); opacity: 1; }
  100% { transform: rotate(-360deg) scale(.3); opacity: 0; }
}

.anim-dark-gear-flash {
  position: absolute;
  width: 100px;
  height: 120px;
  left: -50px;
  top: -50px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(40,0,60,.6) 0%, rgba(20,0,30,.3) 40%, transparent 70%);
  animation: darkGearFlash 1.4s ease-out forwards;
  opacity: 0;
}

@keyframes darkGearFlash {
  0%   { transform: scale(.3); opacity: 0; }
  15%  { transform: scale(1.2); opacity: .8; }
  85%  { transform: scale(1); opacity: .5; }
  100% { transform: scale(1.5); opacity: 0; }
}

/* ===== CARD TRANSFER ANIMATION ===== */

.transfer-anim-card {
  position: fixed;
  width: var(--zone-w);
  height: var(--zone-h);
  z-index: 10110;
  pointer-events: none;
  animation: cardTransferFly var(--dur, 800ms) ease-in-out forwards;
  filter: drop-shadow(0 0 12px rgba(40,0,60,.7));
}
.transfer-anim-card .board-card {
  width: 100%;
  height: 100%;
}

@keyframes cardTransferFly {
  0%   { transform: translate(0, 0) scale(1); opacity: 1; }
  50%  { transform: translate(calc(var(--transferDx) * .5), calc(var(--transferDy) * .5 - 30px)) scale(1.15); opacity: 1; }
  100% { transform: translate(var(--transferDx), var(--transferDy)) scale(1); opacity: 1; }
}

/* ===== MULLIGAN RETURN-TO-DECK ANIMATION ===== */

.discard-anim-deck-return {
  animation: cardReturnToDeck .35s ease-in forwards;
}

@keyframes cardReturnToDeck {
  0%   { transform: translate(0, 0) scale(1); opacity: 1; filter: brightness(1); }
  50%  { filter: brightness(1.4); }
  100% { transform: translate(var(--dx), var(--dy)) scale(.7); opacity: .6; filter: brightness(1.6); }
}

/* ===== SKIN GALLERY ===== */

.skin-gallery-panel {
  background: var(--bg2);
  border: 1px solid var(--accent);
  padding: 20px 24px;
  border-radius: 6px;
  max-width: 500px;
  max-height: 80vh;
  overflow-y: auto;
  text-align: center;
  box-shadow: 0 8px 40px rgba(0,0,0,.8);
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.skin-gallery-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

.skin-gallery-item {
  width: 120px;
  cursor: pointer;
  border: 2px solid var(--bg4);
  border-radius: 4px;
  overflow: hidden;
  transition: border-color .15s, box-shadow .15s, transform .15s;
  background: var(--bg3);
}
.skin-gallery-item:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,.5);
}
.skin-gallery-item.skin-selected {
  border-color: #ffd700;
  box-shadow: 0 0 10px rgba(255,215,0,.4);
}
.skin-gallery-item img {
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  display: block;
}
.skin-gallery-label {
  padding: 4px 6px;
  font-size: 9px;
  color: var(--text2);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ===== SET SCORE DISPLAY ===== */

.set-score-fixed {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(18px * var(--board-scale));
  letter-spacing: 1px;
  padding: calc(4px * var(--board-scale)) calc(14px * var(--board-scale));
  background: rgba(0,0,0,.5);
  border: 1px solid var(--bg4);
  border-radius: 6px;
  z-index: 100;
  pointer-events: none;
}

/* ===== SET COMPLETE FIREWORKS ===== */

.set-fireworks {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}

.firework-particle {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--fw-color);
  box-shadow: 0 0 8px var(--fw-color), 0 0 16px var(--fw-color);
  animation: fireworkBurst var(--fw-dur) ease-out var(--fw-delay) infinite;
  opacity: 0;
}
/* Beefier particle variant for the final tutorial's victory overlay —
   larger body + more saturated glow so the celebration actually reads
   as a BIG fireworks show instead of the compact "set complete" spray. */
.firework-particle.firework-big {
  width: 10px;
  height: 10px;
  box-shadow: 0 0 14px var(--fw-color), 0 0 28px var(--fw-color), 0 0 48px var(--fw-color);
}

@keyframes fireworkBurst {
  0%   { transform: translate(0, 0) scale(1); opacity: 1; }
  20%  { opacity: 1; }
  100% { transform: translate(var(--fw-x), var(--fw-y)) scale(0); opacity: 0; }
}

.set-complete-overlay {
  position: relative;
}

/* SC icon tooltip */
.sc-icon-wrapper { position: relative; display: inline-flex; }
.sc-icon-tooltip {
  display: none;
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--bg2);
  border: 2px solid #ffd700;
  border-radius: 8px;
  padding: 8px;
  box-shadow: 0 0 20px rgba(255,215,0,.3), 0 4px 20px rgba(0,0,0,.6);
  z-index: 1000;
  pointer-events: none;
}
.sc-icon-tooltip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #ffd700;
}
.sc-icon-wrapper:hover .sc-icon-tooltip { display: block; }

/* ===== SHOP SCREEN ===== */

.shop-tabs {
  display: flex;
  gap: 4px;
  padding: 8px 16px 0;
  border-bottom: 1px solid var(--bg4);
}
.shop-tab {
  padding: 8px 18px;
  font-family: var(--font-display), sans-serif;
  font-size: 11px;
  background: transparent;
  border: none;
  color: var(--text2);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: color .15s, border-color .15s;
  display: flex;
  align-items: center;
  gap: 6px;
}
.shop-tab:hover { color: var(--text); }
.shop-tab-active {
  color: #ffd700;
  border-bottom-color: #ffd700;
}
.shop-tab-count {
  font-size: 9px;
  background: var(--bg4);
  padding: 1px 6px;
  border-radius: 8px;
  color: var(--text2);
}
.shop-tab-active .shop-tab-count {
  background: rgba(255,215,0,.15);
  color: #ffd700;
}

.shop-content {
  padding: 16px;
  overflow-y: auto;
  flex: 1;
}

/* Shopkeep — decorative portrait pinned to the top-left corner of the
   ShopScreen at roughly the same vertical level as each sub-tab's
   "Random X" button. The SPRITE floats up/down via a slow CSS animation
   (.shop-antonia-float); the name label below is on the outer container
   so it stays put. Pointer-events disabled so clicks still reach the
   shop content behind her. */
.shop-antonia {
  position: fixed;
  /* Horizontally centered in the gutter between the screen edge and the
     centered 900px items gallery, so the gap to the left border equals the
     gap to the gallery. galleryLeft = 50vw - 450px; centering her 220px
     width in [0, galleryLeft] gives (galleryLeft - 220)/2 = 25vw - 335px.
     Clamped so she never slides off-screen on narrower viewports. */
  left: max(16px, calc(25vw - 335px));
  /* Sits well down in the tab body so that even her tall idle-chatter
     speech bubble (which rises above her head) clears the Skins/Avatars
     tabs row above. Viewport-relative so the offset scales with height. */
  top: calc(265px + 10vh);
  width: 220px;
  pointer-events: none;
  z-index: 500;
  display: flex;
  flex-direction: column;
  align-items: center;
  animation: shopAntoniaIn .6s ease-out;
}
.shop-antonia-float {
  position: relative;           /* anchor for .shop-antonia-bubble */
  width: 100%;
  animation: shopAntoniaFloat 3.5s ease-in-out infinite;
  will-change: transform;
}
.shop-antonia-img {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
  -ms-interpolation-mode: nearest-neighbor;
  filter: drop-shadow(0 8px 18px rgba(0,0,0,.6))
          drop-shadow(0 0 16px rgba(255,40,40,.6))
          drop-shadow(0 0 36px rgba(255,30,30,.45));
  /* Gentle vertical squash-and-stretch, anchored at her feet. Separate from
     the float on .shop-antonia-float so the two layer. Slightly off her
     float period so they don't lock in sync. */
  transform-origin: 50% 100%;
  animation: shopAntoniaSquash 3.1s ease-in-out infinite;
}
@keyframes shopAntoniaSquash {
  0%, 100% { transform: scaleY(1); }
  30%      { transform: scaleY(1.03); }
  70%      { transform: scaleY(0.97); }
}
.shop-antonia-name {
  margin-top: 10px;
  font-family: 'Press Start 2P', monospace;
  font-size: 15px;
  letter-spacing: 1px;
  color: #ff4444;
  /* Chunky black outline (stroke painted behind the fill so the letters
     don't thin out) makes the pixel font read bigger and bolder. */
  -webkit-text-stroke: 3px #000;
  paint-order: stroke fill;
  text-shadow:
    -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000,
    0 -2px 0 #000, 0 2px 0 #000, -2px 0 0 #000, 2px 0 0 #000,
    0 0 14px rgba(255,68,68,.7);
  white-space: nowrap;
}
/* Khekhekhe! speech bubble — pops above the sprite on every purchase.
   Lives inside .shop-antonia-float, so it floats with her. */
.shop-antonia-bubble {
  position: absolute;
  bottom: calc(100% + 30px);
  left: 50%;
  /* Faint lavender sheen up top fading to pure white at the tail so the
     tail's white fill stays seamless. */
  background: linear-gradient(165deg, #fbfaff 0%, #ffffff 70%);
  color: #1a1a2e;
  font-family: 'Press Start 2P', monospace;
  font-size: 12px;
  padding: 11px 17px;
  border: 3px solid #1a1a2e;
  border-radius: 16px;
  /* Fancy framed look: a gold ring hugging the dark border, then a thin
     dark outline around that, plus a drop shadow and a soft gold glow. */
  box-shadow:
    0 0 0 3px #ffd700,
    0 0 0 5px #1a1a2e,
    0 8px 16px rgba(0,0,0,.55),
    0 0 22px rgba(255,215,0,.30);
  white-space: nowrap;
  animation: shopAntoniaBubble 1.4s ease-out forwards;
  transform-origin: 50% 100%;
}
/* Idle chatter variant — longer lines need to wrap and stay on screen
   long enough to read. Animation timing is re-scaled in
   @keyframes shopAntoniaBubbleChatter so the hold phase is longer. */
.shop-antonia-bubble-chatter {
  white-space: normal;
  max-width: 280px;
  min-width: 180px;
  line-height: 1.55;
  text-align: center;
  animation: shopAntoniaBubbleChatter 3.6s ease-out forwards;
}
/* Speech-bubble tail — two stacked triangles so the border reads cleanly.
   ::before is the outer dark triangle; ::after is the inner white fill
   overlapping it by a pixel, same trick as standard CSS comic bubbles. */
.shop-antonia-bubble::before {
  content: '';
  position: absolute;
  bottom: -12px;
  left: 50%;
  transform: translateX(-50%);
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-top: 12px solid #1a1a2e;
}
.shop-antonia-bubble::after {
  content: '';
  position: absolute;
  bottom: -6px;
  left: 50%;
  transform: translateX(-50%);
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
  border-top: 8px solid #ffffff;
}
@keyframes shopAntoniaIn {
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes shopAntoniaFloat {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}
/* Bubble pop: scale-up entrance, brief hold, soft fade-out. Uses
   `translate(-50%, ...)` throughout so the bubble stays horizontally
   centered over Antonia while its vertical position animates. */
@keyframes shopAntoniaBubble {
  0%   { opacity: 0; transform: translate(-50%, 10px) scale(.6); }
  18%  { opacity: 1; transform: translate(-50%, -4px) scale(1.12); }
  28%  { opacity: 1; transform: translate(-50%, 0)   scale(1); }
  78%  { opacity: 1; transform: translate(-50%, 0)   scale(1); }
  100% { opacity: 0; transform: translate(-50%, -8px) scale(1); }
}
/* Chatter bubble — same pop-in, much longer hold so the reader has time
   to actually parse the sentence. Percentages recomputed against 3.6s. */
@keyframes shopAntoniaBubbleChatter {
  0%   { opacity: 0; transform: translate(-50%, 10px) scale(.6); }
  7%   { opacity: 1; transform: translate(-50%, -4px) scale(1.08); }
  12%  { opacity: 1; transform: translate(-50%, 0)   scale(1); }
  90%  { opacity: 1; transform: translate(-50%, 0)   scale(1); }
  100% { opacity: 0; transform: translate(-50%, -6px) scale(1); }
}
@media (max-width: 900px) {
  .shop-antonia { width: 150px; left: 12px; top: calc(251px + 10vh); }
  .shop-antonia-name { font-size: 12px; margin-top: 7px; -webkit-text-stroke-width: 2px; }
  .shop-antonia-bubble { font-size: 10px; padding: 7px 12px; }
  .shop-antonia-bubble-chatter { max-width: 220px; min-width: 150px; }
}
@media (max-width: 600px) {
  .shop-antonia { width: 92px; left: 6px; top: calc(265px + 10vh); }
  .shop-antonia-name { font-size: 10px; margin-top: 4px; letter-spacing: 0; -webkit-text-stroke-width: 1.5px; }
  .shop-antonia-bubble { font-size: 9px; padding: 5px 9px; border-width: 2px; }
  .shop-antonia-bubble::before { border-top-width: 10px; border-left-width: 8px; border-right-width: 8px; }
  .shop-antonia-bubble::after { border-top-width: 6px; border-left-width: 5px; border-right-width: 5px; }
  .shop-antonia-bubble-chatter { max-width: 170px; min-width: 120px; }
}

.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 14px;
  max-width: 900px;
  margin: 0 auto;
}
.shop-grid-skins {
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
}

.shop-item {
  background: var(--bg2);
  border: 2px solid var(--bg4);
  border-radius: 6px;
  overflow: hidden;
  transition: border-color .15s, box-shadow .15s, transform .15s;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 8px;
}
.shop-item:hover {
  border-color: var(--accent);
  /* No translateY lift on hover: moving the card shifts its hit-box (and the
     tooltip trigger) out from under the cursor at the bottom edge, which
     un-hovers it, drops it back under the cursor, and loops forever. A deeper
     shadow gives the same "pop" without moving the pointer target. */
  box-shadow: 0 6px 22px rgba(0,0,0,.6);
}
.shop-item.shop-owned {
  border-color: #ffd700;
  border-width: 3px;
  /* Static gold glow (the periodic pulse was moved to the unowned items). */
  box-shadow: 0 0 10px rgba(255,215,0,.35), 0 0 20px rgba(255,215,0,.15), inset 0 0 8px rgba(255,215,0,.1);
  position: relative;
  overflow: visible;
}
.shop-item.shop-owned::before {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 8px;
  border: 2px solid transparent;
  background: linear-gradient(135deg, transparent 30%, rgba(255,255,240,.4) 45%, rgba(255,215,0,.6) 50%, rgba(255,255,240,.4) 55%, transparent 70%) border-box;
  background-size: 300% 300%;
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: shop-owned-shine 3s ease-in-out infinite;
  pointer-events: none;
}
@keyframes shop-owned-shine {
  0% { background-position: 100% 100%; }
  100% { background-position: -100% -100%; }
}
.shop-item.shop-owned::after {
  content: '';
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #ffd700;
  box-shadow: 0 0 6px 2px rgba(255,215,0,.7), 0 0 12px 4px rgba(255,215,0,.3);
  animation: shop-owned-particle 4s linear infinite;
  pointer-events: none;
  top: -4px;
  left: 20%;
}
@keyframes shop-owned-particle {
  0% { left: -5%; top: -4px; opacity: 0; }
  10% { opacity: 1; }
  25% { left: 100%; top: -4px; }
  50% { left: 105%; top: 100%; opacity: 0.6; }
  75% { left: 0%; top: 105%; }
  90% { opacity: 1; }
  100% { left: -5%; top: -4px; opacity: 0; }
}
.shop-item.shop-owned:hover {
  border-color: #ffd700;
  box-shadow: 0 0 18px rgba(255,215,0,.6), 0 0 36px rgba(255,215,0,.25), inset 0 0 10px rgba(255,215,0,.15);
}

/* Unowned items: an enticing iridescent gradient border (::before) plus a
   periodic glow (::after) so they draw the eye toward a purchase (owned items
   get the static gold border instead). Excludes:
   - .shop-owned          → owned, gets the gold border
   - .structure-deck-default → owned default deck (not flagged .shop-owned)
   - .shop-selected       → clicked, the gold selection highlight takes over */
.shop-item:not(.shop-owned):not(.structure-deck-default):not(.shop-selected) {
  position: relative;
  border-color: transparent;
  overflow: visible;
  /* Periodic glow — animated box-shadow on the item itself (the same mechanism
     the owned glow used, so it renders reliably). */
  animation: shop-unowned-glow 3s ease-in-out infinite;
}
.shop-item:not(.shop-owned):not(.structure-deck-default):not(.shop-selected)::before {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 8px;
  border: 2px solid transparent;
  background: linear-gradient(120deg, #00f0ff, #5b8cff, #c44bff, #ff49b0, #c44bff, #5b8cff, #00f0ff) border-box;
  background-size: 200% 100%;
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}
@keyframes shop-unowned-glow {
  0%, 100% { box-shadow: 0 0 6px rgba(0,240,255,.25), 0 0 14px rgba(196,75,255,.12); }
  50%      { box-shadow: 0 0 18px rgba(0,240,255,.6), 0 0 32px rgba(255,73,176,.38); }
}
/* Hover: shimmer the gradient border + quicken the glow pulse. */
.shop-item:not(.shop-owned):not(.structure-deck-default):not(.shop-selected):hover {
  animation-duration: 1.4s;
}
.shop-item:not(.shop-owned):not(.structure-deck-default):not(.shop-selected):hover::before {
  animation: shop-unowned-shimmer 2s linear infinite;
}
@keyframes shop-unowned-shimmer {
  0%   { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}

/* Structure Decks — inherit frame styling from .shop-item / .shop-owned
   so locked (default dim border) vs unlocked (gold) vs default (green) read
   the same as other shop tiles. */
.structure-deck-item {
  padding: 8px;
  gap: 4px;
  transition: transform .15s, box-shadow .2s, border-color .2s;
}
/* The DEFAULT badge must match the SELECT / buy buttons' box exactly, or
   swapping between them changes the control height — and since the grid
   stretches every card in a row to the tallest, that resizes the whole row.
   Mirror the .btn metrics (padding 4x10, 11px, 2px border) so the height is
   identical in every state. Colours come from the inline style. */
.shop-price {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display), sans-serif;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: 4px 10px;
  border: 2px solid currentColor;
  border-radius: 0;
}

/* Bottom-align the action control (SELECT / DEFAULT / buy). The grid stretches
   every card in a row to the tallest, and deck names wrap to differing heights,
   so without this the buttons sit "right below the name" at varying heights.
   margin-top:auto eats the slack above the control, lining them all up at the
   card's bottom. !important beats the buttons' inline marginTop. */
.structure-deck-item > .btn,
.structure-deck-item > .shop-price {
  margin-top: auto !important;
}
.structure-deck-item:hover:not([disabled]) {
  /* Shadow-only hover (no translateY) — see .shop-item:hover for why moving the
     card on hover causes the tooltip flicker loop. */
  box-shadow: 0 6px 22px rgba(0,0,0,.55);
}
.structure-deck-item.structure-deck-default {
  animation: structure-deck-equipped-pulse 2s ease-in-out infinite;
  background: linear-gradient(180deg, rgba(51,255,136,.12) 0%, rgba(0,0,0,.25) 100%);
  /* Match the owned (shop-owned) state's 3px border so an owned deck's height
     is identical whether or not it's the default. Otherwise toggling default
     flips this card between 2px and 3px borders, and since the grid stretches
     every card in a row to the tallest, the whole row shifts by ~2px. */
  border-width: 3px;
}
@keyframes structure-deck-equipped-pulse {
  0%, 100% { box-shadow: 0 0 16px rgba(51,255,136,.7), 0 0 28px rgba(51,255,136,.35); }
  50%      { box-shadow: 0 0 22px rgba(80,255,160,.9), 0 0 44px rgba(51,255,136,.5); }
}

/* Slight gray-out on unbought non-skin items */
.shop-item:not(.shop-owned):not(.shop-unowned-skin) .shop-item-img-wrap img {
  filter: grayscale(0.4) brightness(0.7);
  transition: filter .2s;
}
.shop-item:not(.shop-owned):not(.shop-unowned-skin):hover .shop-item-img-wrap img {
  filter: grayscale(0.15) brightness(0.85);
}

.shop-item-img-wrap {
  width: 100%;
  position: relative;
  overflow: hidden;
  background: var(--bg3);
}
.shop-item-img-wrap img {
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  display: block;
  /* Pixel art — nearest-neighbor scaling, no interpolation. */
  image-rendering: pixelated;
}

/* ── Avatar squash-and-stretch ──
   A looping bouncy idle. Applied to the whole image container (not the
   <img>) so it scales as one unit inside the overflow:hidden frame with no
   clipping/bg-reveal artifacts. Bottom-anchored for a grounded bounce, and
   roughly volume-preserving (wider+shorter, then taller+narrower). */
@keyframes shop-avatar-squash {
  0%, 100% { transform: scaleY(1); }
  35%      { transform: scaleY(0.98); }
  70%      { transform: scaleY(1.02); }
}
.shop-avatar-item .shop-item-img-wrap {
  transform-origin: 50% 100%;
  animation: shop-avatar-squash 3.6s ease-in-out infinite;
  will-change: transform;
}
/* Desync the grid so they don't all pulse together. */
.shop-avatar-item:nth-child(3n+2) .shop-item-img-wrap { animation-delay: -1.2s; }
.shop-avatar-item:nth-child(3n+3) .shop-item-img-wrap { animation-delay: -2.4s; }

/* Unowned skins read as "collectible, not yet unlocked" — keep most of the
   colour (a light desaturate + dim) instead of a full grayscale, which made
   them look broken / sold-out. The lock overlay below sells the "unlock me"
   read. On hover they brighten toward full colour to feel attainable. */
.shop-skin-locked {
  filter: grayscale(0.35) brightness(0.72) contrast(0.95);
}
.shop-unowned-skin:hover .shop-skin-locked {
  filter: grayscale(0.1) brightness(0.9) contrast(1);
}

/* Lock overlay on unowned skins — a soft scrim + padlock badge so the tile
   reads as a desirable thing to unlock rather than a disabled entry. The
   scrim lifts on hover to preview the art in fuller colour. */
.shop-lock-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding: 6px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(8,8,16,.05) 0%, rgba(8,8,16,.45) 100%);
  transition: opacity .2s;
}
.shop-unowned-skin:hover .shop-lock-overlay { opacity: .55; }
.shop-lock-badge {
  font-size: 13px;
  line-height: 1;
  padding: 3px 5px;
  border-radius: 4px;
  background: rgba(8,8,16,.72);
  border: 1px solid rgba(255,215,0,.5);
  box-shadow: 0 1px 3px rgba(0,0,0,.5);
}

.shop-owned-badge {
  position: absolute;
  top: 6px;
  right: 6px;
  background: rgba(255,215,0,.9);
  color: #000;
  font-family: var(--font-display), sans-serif;
  font-size: 8px;
  font-weight: 700;
  padding: 2px 6px;
  border-radius: 3px;
  letter-spacing: .5px;
}
.shop-equipped-badge {
  background: rgba(51,255,136,.95);
}
.shop-item.shop-equipped {
  border-color: #33ff88 !important;
  animation: structure-deck-equipped-pulse 2s ease-in-out infinite;
}

.shop-item-name {
  padding: 6px 6px 2px;
  font-size: 10px;
  color: var(--text);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 100%;
  font-weight: 600;
}

@keyframes shop-sparkle {
  0%, 100% { background-position: 0% 50%; text-shadow: 0 0 4px rgba(255,215,0,.3); }
  25% { background-position: 100% 50%; text-shadow: 0 0 8px rgba(255,215,0,.6), 0 0 16px rgba(255,170,0,.25); }
  50% { background-position: 50% 0%; text-shadow: 0 0 6px rgba(255,215,0,.5), 0 0 20px rgba(255,200,0,.3); }
  75% { background-position: 0% 100%; text-shadow: 0 0 10px rgba(255,215,0,.7), 0 0 24px rgba(255,170,0,.35); }
}

/* Hero/Skin flip toggle under each skin tile (replaces the old skin-name and
   hero-name labels, which overflowed the tile). Styled like the buy button but
   in accent (cyan) so it reads clearly as an interactive control. */
.shop-skin-toggle {
  margin-top: 7px;
  padding: 4px 14px !important;
  font-size: 10px !important;
  font-weight: 700;
  letter-spacing: 1px;
  border-color: var(--accent) !important;
  color: var(--accent) !important;
  background: rgba(0,240,255,.12) !important;
}
.shop-skin-toggle:hover {
  background: rgba(0,240,255,.24) !important;
  box-shadow: 0 0 10px rgba(0,240,255,.35);
}

.shop-buy-btn {
  margin-top: 4px;
  padding: 4px 14px !important;
  font-size: 11px !important;
  border-color: #ffd700 !important;
  color: #ffd700 !important;
  background: rgba(255,215,0,.08) !important;
  display: flex;
  align-items: center;
  gap: 4px;
}
.shop-buy-btn:hover:not(:disabled) {
  background: rgba(255,215,0,.2) !important;
  box-shadow: 0 0 10px rgba(255,215,0,.3);
}
.shop-buy-btn:disabled {
  opacity: .4;
  cursor: not-allowed;
}

.shop-sc-icon {
  width: 14px;
  height: 14px;
  image-rendering: pixelated;
}

.shop-empty {
  text-align: center;
  color: var(--text2);
  padding: 40px 20px;
  font-size: 13px;
}

.shop-random-wrap {
  /* Pinned to the top of the scrolling shop content — the item grid scrolls
     behind it. Full-bleed across the content's 16px padding on all sides
     (negative margins + matching top offset) so its opaque backdrop runs
     flush up to the tabs row above with no gap, and nothing shows through. */
  position: sticky;
  top: -16px;
  z-index: 10;
  text-align: center;
  margin: -16px -16px 16px;
  padding: 16px;
  background: var(--bg);
  border-bottom: 1px solid var(--bg4);
}
.shop-random-btn {
  padding: 10px 28px !important;
  font-size: 14px !important;
  border-color: var(--player-color) !important;
  color: var(--player-color) !important;
  background: color-mix(in srgb, var(--player-color) 8%, transparent) !important;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.shop-random-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--player-color) 20%, transparent) !important;
  box-shadow: 0 0 12px color-mix(in srgb, var(--player-color) 35%, transparent);
}
.shop-random-btn:disabled {
  opacity: .4;
  cursor: not-allowed;
}
.shop-random-hint {
  display: block;
  margin-top: 8px;
  font-size: 10px;
  color: var(--text2);
}

.shop-filter-row {
  margin-bottom: 12px;
  text-align: center;
}

/* Shop item selection highlight */
.shop-item.shop-selected {
  border-color: #ffd700 !important;
  box-shadow: 0 0 12px rgba(255,215,0,.5), 0 0 24px rgba(255,215,0,.2), inset 0 0 8px rgba(255,215,0,.1);
  /* No transform lift on select — the gold border + glow mark the selection,
     and moving the item shifts its hit-box (see .shop-item:hover). */
}

/* Purchase celebration overlay text */
@keyframes purchase-text-in {
  0% { transform: translateX(-50%) scale(0.3); opacity: 0; }
  30% { transform: translateX(-50%) scale(1.3); opacity: 1; }
  50% { transform: translateX(-50%) scale(1); }
  80% { transform: translateX(-50%) scale(1); opacity: 1; }
  100% { transform: translateX(-50%) scale(1.5); opacity: 0; }
}
.shop-purchase-text {
  position: absolute;
  transform: translateX(-50%);
  font-family: var(--font-display), sans-serif;
  font-size: 42px;
  font-weight: 900;
  color: #ffd700;
  text-shadow: 0 0 20px rgba(255,215,0,.8), 0 0 40px rgba(255,170,0,.5), 0 2px 0 #b8860b;
  letter-spacing: 4px;
  animation: purchase-text-in 2s ease-out forwards;
  pointer-events: none;
  white-space: nowrap;
}

/* Profile avatar gallery */
.profile-avatar-gallery {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}
.profile-avatar-gallery-item {
  cursor: pointer;
  overflow: visible;
  transition: transform .15s;
  width: 80px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.profile-avatar-gallery-item:hover {
  transform: scale(1.05);
}
.profile-avatar-gallery-item:hover .profile-avatar-gallery-img {
  border-color: var(--accent);
  box-shadow: 0 2px 12px rgba(0,0,0,.5);
}
.profile-avatar-gallery-item.active .profile-avatar-gallery-img {
  border-color: #ffd700;
  box-shadow: 0 0 10px rgba(255,215,0,.4);
}
.profile-avatar-gallery-img {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg3);
  border: 2px solid var(--bg4);
  transition: border-color .15s, box-shadow .15s;
}
.profile-avatar-gallery-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ===== SHOP RANDOM REVEAL MODAL ===== */

@keyframes shop-reveal-appear {
  0% { transform: scale(0.4) rotateY(90deg); opacity: 0; }
  40% { transform: scale(1.1) rotateY(-5deg); opacity: 1; }
  60% { transform: scale(0.95) rotateY(2deg); }
  80% { transform: scale(1.02) rotateY(0deg); }
  100% { transform: scale(1) rotateY(0deg); }
}
@keyframes shop-reveal-glow-pulse {
  0%, 100% { opacity: 0.4; transform: translate(-50%, -50%) scale(1); }
  50% { opacity: 0.7; transform: translate(-50%, -50%) scale(1.15); }
}

.shop-reveal-modal {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 32px 40px;
  background: linear-gradient(145deg, #1a1a2e 0%, #0a0a12 100%);
  border: 3px solid #ffd700;
  border-radius: 12px;
  box-shadow: 0 0 40px rgba(255,215,0,.3), 0 0 80px rgba(255,215,0,.1), 0 20px 60px rgba(0,0,0,.8);
  position: relative;
  overflow: visible;
  animation: shop-reveal-appear 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

.shop-reveal-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 280px;
  height: 280px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,215,0,.25) 0%, rgba(255,170,0,.1) 40%, transparent 70%);
  animation: shop-reveal-glow-pulse 2s ease-in-out infinite;
  pointer-events: none;
}

.shop-reveal-img-wrap {
  width: 180px;
  border-radius: 8px;
  overflow: hidden;
  border: 2px solid #ffd700;
  box-shadow: 0 0 20px rgba(255,215,0,.4), 0 8px 30px rgba(0,0,0,.6);
  position: relative;
  z-index: 1;
}
.shop-reveal-img-wrap img {
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  display: block;
}

.shop-reveal-label {
  margin-top: 16px;
  font-family: var(--font-display), sans-serif;
  font-size: 16px;
  font-weight: 800;
  background: linear-gradient(105deg, #ffd700 0%, #fff8b0 30%, #ffaa00 60%, #ffd700 100%);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  text-align: center;
  letter-spacing: 1px;
  position: relative;
  z-index: 1;
}

.shop-reveal-subtitle {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text2);
  text-align: center;
  position: relative;
  z-index: 1;
}

/* ===== VOLUME CONTROL ===== */
.volume-control {
  position: relative;
  flex-shrink: 0;
}
/* Discord invite button — brand-coloured pill, used on the title/login
   screen (fixed top-right) and the main menu (below the Logout button). */
.discord-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border-radius: 6px;
  background: #5865F2;
  color: #fff;
  text-decoration: none;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 1px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 0 14px rgba(88, 101, 242, .45);
  transition: background .15s, box-shadow .15s, transform .1s;
}
.discord-btn:hover {
  background: #4752c4;
  box-shadow: 0 0 20px rgba(88, 101, 242, .7);
  transform: translateY(-1px);
}
.discord-btn:active { transform: translateY(0); }
.discord-btn svg { display: block; }
.discord-btn-label { white-space: nowrap; }
/* Full-width banner variant (main menu): the logo fills the button width,
   so the height scales proportionally with however wide the button gets. */
.discord-btn--block {
  width: 100%;
  justify-content: center;
  padding: 14px 18px;
}
.discord-btn--block svg {
  width: 67%;
  height: auto;
}
/* Large pill variant (title/login screen). */
.discord-btn--lg {
  padding: 12px 24px;
  gap: 12px;
  font-size: 16px;
  letter-spacing: 1.5px;
}
.volume-btn {
  background: none;
  border: 1px solid transparent;
  font-size: 18px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
  opacity: 0.7;
  transition: opacity .15s, border-color .15s;
  line-height: 1;
}
.volume-btn:hover {
  opacity: 1;
  border-color: var(--bg4);
}
.volume-slider-popup {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  background: var(--bg2);
  border: 1px solid var(--bg4);
  border-radius: 8px;
  padding: 10px 14px;
  z-index: 10100;
  box-shadow: 0 6px 24px rgba(0,0,0,.7);
  white-space: nowrap;
}
.volume-slider {
  width: 120px;
  height: 4px;
  accent-color: var(--accent);
  cursor: pointer;
  vertical-align: middle;
}

/* ===== PHASE BUTTONS ===== */
.phase-buttons-row {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--board-scale));
  margin-top: calc(4px * var(--board-scale));
}
.phase-btn {
  padding: calc(3px * var(--board-scale)) calc(10px * var(--board-scale)) !important;
  font-size: calc(9px * var(--board-scale)) !important;
  white-space: nowrap;
}
.phase-end-check {
  display: flex;
  align-items: center;
  gap: calc(4px * var(--board-scale));
  font-size: calc(9px * var(--board-scale));
  color: var(--text2);
  cursor: pointer;
  margin-left: calc(4px * var(--board-scale));
  white-space: nowrap;
  user-select: none;
}
.phase-end-check input { width: calc(12px * var(--board-scale)); height: calc(12px * var(--board-scale)); accent-color: var(--accent); cursor: pointer; }
.phase-column {
  position: absolute;
  left: var(--sidebar-w);
  /* Seit dem Perspektiven-Update ist `top: 50%` FALSCH: die gekippte
     Ebene laesst die gegnerische Haelfte in die Ferne schrumpfen, also
     liegt die optische Naht zwischen den Haelften deutlich HOEHER als die
     geometrische Mitte des Containers. `--phase-mid-y` ist die gemessene
     Naht (Mitte der `.board-mid-row`, projiziert) in Koordinaten des
     positionierten Vorfahren; GameBoard schreibt sie zur Laufzeit. Der
     50%-Fallback erhaelt das alte Verhalten, falls die Messung ausfaellt. */
  top: var(--phase-mid-y, 50%);
  /* Verschoben wird um die halbe Hoehe der PHASENZEILE, nicht um die
     halbe Hoehe der ganzen Spalte (Als Praezisierung): die Spalte
     enthaelt unter den Phasennamen noch die Button-Reihe, und ein
     `translateY(-50%)` haette das GESAMTMODUL auf die Mittellinie
     zentriert — die Phasennamen saessen dann darueber. Mit der halben
     Zeilenhoehe liegt die Phasenzeile selbst mittig auf der Linie und die
     Buttons haengen darunter. `--phase-tracker-h` misst GameBoard. */
  transform: translateY(calc(var(--phase-tracker-h, 0px) / -2));
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  z-index: 50;
}
.phase-debuffs {
  position: absolute;
  top: 4px;
  left: var(--sidebar-w);
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-width: calc(280px * var(--board-scale));
  z-index: 100;
  pointer-events: none;
}
.phase-debuff-item {
  font-size: calc(13px * var(--board-scale));
  font-weight: 700;
  text-shadow: 0 0 6px rgba(255,0,0,.3), 1px 1px 0 #000;
  line-height: 1.3;
  animation: summonLockPulse 2s ease-in-out infinite;
}

/* ===== TOKEN TOOLTIP (left of main tooltip) ===== */
.board-tooltip-token {
  right: 360px;
  border-right: 1px solid var(--accent);
  border-left: 1px solid rgba(255,170,0,.4);
  box-shadow: -4px 0 20px rgba(0,0,0,.6);
}
body:has(.reaction-chain-overlay) .board-tooltip-token {
  right: calc(360px + 196px);
}

/* ===== TURN INDICATOR ON AVATARS ===== */
.avatar-active {
  box-shadow: 0 0 10px rgba(0,240,255,.6), 0 0 20px rgba(0,240,255,.3), 0 0 4px rgba(0,240,255,.8) inset;
  border: 2px solid rgba(0,240,255,.7) !important;
  animation: avatar-pulse 1.5s ease-in-out infinite;
}
.avatar-inactive {
  filter: grayscale(0.7) brightness(0.6);
  border: 2px solid transparent !important;
}
@keyframes avatar-pulse {
  0%, 100% { box-shadow: 0 0 10px rgba(0,240,255,.6), 0 0 20px rgba(0,240,255,.3); }
  50% { box-shadow: 0 0 14px rgba(0,240,255,.8), 0 0 28px rgba(0,240,255,.4); }
}

/* ===== CARD PING FLASH ===== */
.ping-flash {
  border: 2px solid;
  border-radius: 4px;
  animation: ping-flash-anim 1.2s ease-out forwards;
}
@keyframes ping-flash-anim {
  0% { opacity: 1; }
  15% { opacity: 0.3; }
  30% { opacity: 1; }
  45% { opacity: 0.3; }
  60% { opacity: 1; }
  80% { opacity: 0.5; }
  100% { opacity: 0; }
}

/* ===== DEAD HERO TOMBSTONE ===== */
.hero-dead-marker {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: calc(32px * var(--board-scale));
  z-index: 4;
  pointer-events: none;
  filter: drop-shadow(0 2px 6px rgba(0,0,0,.8));
  opacity: 0.85;
}

/* ═══════════════════════════════════════════════════════════════
   RULES SCREEN
   ═══════════════════════════════════════════════════════════════ */

.rules-layout {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow: hidden;
}

/* ── Sidebar ── */
.rules-sidebar {
  width: 220px;
  flex-shrink: 0;
  background: var(--bg2);
  border-right: 1px solid var(--bg4);
  overflow-y: auto;
  padding: 10px 0;
  display: flex;
  flex-direction: column;
}

.rules-nav-item {
  background: none;
  border: none;
  border-left: 3px solid transparent;
  color: var(--text2);
  font-family: var(--font-body), sans-serif;
  font-size: 16px;
  font-weight: 600;
  padding: 8px 16px;
  text-align: left;
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.rules-nav-item:hover {
  color: var(--text);
  background: rgba(255,255,255,.03);
}
.rules-nav-item.active {
  color: var(--accent);
  border-left-color: var(--accent);
  background: rgba(0,240,255,.05);
}

/* ── Content ── */
.rules-content {
  flex: 1;
  overflow-y: auto;
  padding: 24px 36px;
  scroll-behavior: smooth;
  line-height: 1.7;
}

.rules-section-title {
  font-size: 28px;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: 1px;
  margin-bottom: 18px;
  padding-bottom: 10px;
  border-bottom: 2px solid var(--bg4);
  text-shadow: 0 0 20px rgba(0,240,255,.25);
}

.rules-sub-title {
  font-size: 20px;
  font-weight: 700;
  color: var(--accent4);
  letter-spacing: 0.5px;
  margin-top: 18px;
  margin-bottom: 10px;
}

.rules-divider {
  border-top: 1px solid var(--bg4);
  margin: 28px 0;
}

/* ── Text blocks ── */
.rules-text-block {
  max-width: 100%;
}
.rules-text-block p,
.rules-content > p {
  font-size: 18px;
  color: var(--text);
  line-height: 1.75;
  margin-bottom: 12px;
}
.rules-text-block p:last-child { margin-bottom: 0; }

.rules-text-block strong,
.rules-content strong {
  color: #fff;
  font-weight: 700;
}
.rules-text-block em {
  color: var(--accent2);
  font-style: italic;
}

.rules-flavor {
  color: var(--accent2) !important;
  font-weight: 700;
  font-size: 20px !important;
  margin-top: 6px;
}

.rules-callout {
  background: rgba(255,170,0,.08);
  border-left: 3px solid var(--accent4);
  padding: 12px 16px;
  font-size: 17px;
  color: var(--accent4);
  font-weight: 600;
  margin-top: 12px;
  margin-bottom: 12px;
  line-height: 1.7;
}

/* ── Card image + text rows ── */
.rules-card-row {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  margin-bottom: 12px;
}

.rules-card-img {
  width: 480px;
  flex-shrink: 0;
  border-radius: 6px;
  border: 1px solid var(--bg4);
  box-shadow: 0 4px 20px rgba(0,0,0,.5);
}
.rules-card-img-sm {
  width: 360px;
}

.rules-img {
  image-rendering: auto;
}

/* ── Board images ── */
.rules-board-img {
  width: 100%;
  max-width: 100%;
  border-radius: 6px;
  border: 1px solid var(--bg4);
  box-shadow: 0 4px 24px rgba(0,0,0,.5);
  margin: 12px 0 16px;
}

/* ── Board legend ── */
.rules-board-legend {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px 20px;
  max-width: 100%;
  margin-bottom: 8px;
}
.rules-legend-item {
  font-size: 16px;
  color: var(--text);
  line-height: 1.6;
  display: flex;
  gap: 8px;
  align-items: baseline;
}
.rules-legend-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 50%;
  background: #fff;
  color: #000;
  font-weight: 800;
  font-size: 11px;
  font-family: var(--font-display), sans-serif;
}

/* ── Icon rows (subtypes, schools) ── */
.rules-icon-row {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  margin: 12px 0 16px;
}
.rules-icon-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.rules-icon-img {
  width: 62px;
  height: 52px;
  object-fit: contain;
  image-rendering: pixelated;
  filter: drop-shadow(0 0 6px rgba(0,240,255,.3));
}
.rules-icon-label {
  font-size: 11px;
  font-weight: 700;
  color: var(--text2);
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

/* ── Subtype grid ── */
.rules-subtype-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  max-width: 100%;
  margin-bottom: 8px;
}
.rules-subtype-entry {
  background: rgba(255,255,255,.02);
  border: 1px solid var(--bg4);
  border-radius: 6px;
  padding: 14px 16px;
}
.rules-subtype-entry p {
  font-size: 16px;
  color: var(--text);
  line-height: 1.7;
  margin-bottom: 8px;
}
.rules-subtype-entry p:last-child { margin-bottom: 0; }

/* ── Spell schools grid ── */
.rules-schools-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  max-width: 100%;
}
.rules-school-entry {
  background: rgba(255,255,255,.02);
  border: 1px solid var(--bg4);
  border-radius: 6px;
  padding: 14px 16px;
}
.rules-school-entry-wide {
  grid-column: 1 / -1;
}
.rules-school-entry p {
  font-size: 16px;
  color: var(--text);
  line-height: 1.7;
  margin-bottom: 8px;
}
.rules-school-entry p:last-child { margin-bottom: 0; }

.rules-school-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  font-family: var(--font-display), sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: var(--accent);
}
.rules-school-icon {
  width: 40px;
  height: 40px;
  object-fit: contain;
  image-rendering: pixelated;
}

/* ── Restriction / action lists ── */
.rules-restriction-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0;
}
.rules-restriction-item {
  font-size: 16px;
  color: var(--text);
  padding: 6px 14px;
  background: rgba(255,51,102,.06);
  border-left: 3px solid var(--danger);
  line-height: 1.6;
}
.rules-restriction-item.rules-phase-action {
  background: rgba(0,240,255,.04);
  border-left-color: var(--accent);
}

/* ── Turn phases ── */
.rules-phases {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 100%;
}
.rules-phase {
  background: rgba(255,255,255,.02);
  border: 1px solid var(--bg4);
  border-radius: 6px;
  padding: 16px 18px;
}
.rules-phase p {
  font-size: 17px;
  color: var(--text);
  line-height: 1.7;
  margin-bottom: 6px;
}
.rules-phase p:last-child { margin-bottom: 0; }

.rules-phase-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.rules-phase-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: 50%;
  background: #fff;
  color: #000;
  font-weight: 800;
  font-size: 13px;
  font-family: var(--font-display), sans-serif;
}
.rules-phase-name {
  font-size: 17px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.5px;
}

/* ── Status effects grid ── */
.rules-status-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 12px;
  max-width: 100%;
}
.rules-status-entry {
  border: 1px solid var(--bg4);
  border-radius: 6px;
  padding: 12px 14px;
  border-top: 3px solid var(--bg4);
}
.rules-status-entry p {
  font-size: 16px;
  color: var(--text);
  line-height: 1.65;
  margin: 0;
}
.rules-status-name {
  font-family: var(--font-display), sans-serif;
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 6px;
  letter-spacing: 0.5px;
}
.rules-status-stunned { border-top-color: #ffaa00; }
.rules-status-stunned .rules-status-name { color: #ffaa00; }
.rules-status-frozen { border-top-color: #66ddff; }
.rules-status-frozen .rules-status-name { color: #66ddff; }
.rules-status-blinded { border-top-color: #aa88cc; }
.rules-status-blinded .rules-status-name { color: #aa88cc; }
.rules-status-poisoned { border-top-color: #aa44ff; }
.rules-status-poisoned .rules-status-name { color: #aa44ff; }
.rules-status-burned { border-top-color: #ff6633; }
.rules-status-burned .rules-status-name { color: #ff6633; }
.rules-status-bleeding { border-top-color: #991122; }
.rules-status-bleeding .rules-status-name { color: #991122; }

/* ── Deck construction ── */
.rules-deck-limits {
  display: flex;
  gap: 16px;
  margin-top: 14px;
}
.rules-deck-limit {
  flex: 1;
  background: rgba(255,255,255,.02);
  border: 1px solid var(--bg4);
  border-radius: 6px;
  padding: 14px 16px;
}
.rules-deck-limit-label {
  font-family: var(--font-display), sans-serif;
  font-size: 15px;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 6px;
  letter-spacing: 0.5px;
}
.rules-deck-limit-desc {
  font-size: 16px;
  color: var(--text);
  line-height: 1.6;
}

/* ===== PUZZLE CREATOR ===== */

.pz-layout {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.pz-search-panel {
  width: 580px;
  flex-shrink: 0;
  background: var(--bg2);
  border-right: 1px solid var(--bg4);
  display: flex;
  flex-direction: row;
  padding: 0;
  gap: 0;
}

/* Faint pixel dither — same coarse 2x2 ordered checkerboard as the main-menu
   boxes (section 12), but at 3% opacity. Used by the Puzzle Creator's surface
   boxes, the menu's pop-up sub-menus (.menu-popup-dither: How to Play, Daily
   Challenge, Puzzle Library), the Player Profile's surface boxes, the
   Shop's content area + header, the Deck Editor's side panels, and the online
   lobby / VS CPU content areas. Layered as a
   !important background-IMAGE so it rides on
   top of each box's background-COLOR (incl. inline styles); foreground content
   renders above it. Only applied to boxes whose own background is a colour (or
   transparent over a coloured parent) — never one with its own
   background-IMAGE/gradient, which this would clobber. */
.pz-search-panel,
.pz-hand,
.menu-popup-dither,
.profile-identity-panel,
.profile-section,
.shop-content,
.shop-screen .top-bar,
.db-panel-left,
.db-panel-right,
.lobby-content,
.vscpu-content {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2' height='2'%3E%3Crect width='1' height='1' fill='%23ffffff' fill-opacity='0.03'/%3E%3Crect x='1' y='1' width='1' height='1' fill='%23ffffff' fill-opacity='0.03'/%3E%3C/svg%3E") !important;
  background-size: 8px 8px !important;
  background-repeat: repeat !important;
  image-rendering: pixelated;
}
/* Left filter sidebar — same control set as the deck builder. Fixed
   width chosen to give the side-by-side filter inputs (Lv / Cost,
   HP / ATK) breathing room without eating too much gallery width. */
.pz-filter-sidebar {
  width: 200px;
  flex-shrink: 0;
  border-right: 1px solid var(--bg4);
  padding: 8px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
/* Gallery column — search input on top, scrollable card grid below. */
.pz-gallery-column {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 8px;
  gap: 6px;
}
.pz-search-results {
  flex: 1;
  height: 100%;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* `grid-auto-rows: max-content` is the key: without it, the grid
     stretches its rows to fill the panel's height, collapsing each
     card into a tall but invisible band that visually overlaps with
     the next row's card images. With max-content, each row sizes
     itself to the card's aspect-ratio-derived height. */
  grid-auto-rows: max-content;
  gap: 5px;
  align-content: start;
  scrollbar-width: auto;
  scrollbar-color: #4d4d57 #1e1e2a;
}
.pz-search-results::-webkit-scrollbar { width: 18px; }
/* Opaque track/thumb (composited equivalents of the old translucent
   white) so the panel's dither doesn't show through the scrollbar. */
.pz-search-results::-webkit-scrollbar-track { background: #1e1e2a; border-radius: 9px; }
.pz-search-results::-webkit-scrollbar-thumb { background: #4d4d57; border-radius: 9px; min-height: 40px; }
.pz-search-results::-webkit-scrollbar-thumb:hover { background: var(--accent); }
/* Hide native scrollbar on touch devices — custom DOM scrollbar replaces it */
@media (pointer: coarse) {
  .pz-search-results::-webkit-scrollbar { display: none; }
  .pz-search-results { padding-right: 22px; }
}
/* ── Custom touch scrollbar (real DOM element for mobile) ── */
.pz-custom-scrollbar {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: 20px;
  z-index: 10;
  touch-action: none;
}
.pz-custom-scrollbar-thumb {
  position: absolute;
  right: 2px;
  width: 16px;
  min-height: 40px;
  /* Opaque (composited equivalent of the old translucent white) so the
     panel's dither doesn't show through the touch scrollbar thumb. */
  background: #595962;
  border-radius: 8px;
  touch-action: none;
}
.pz-custom-scrollbar-thumb:active {
  background: var(--accent);
}
.pz-search-card {
  width: 100%;
  aspect-ratio: 5 / 7;
  border: 1px solid var(--bg4);
  border-radius: 3px;
  overflow: hidden;
  cursor: pointer;
  transition: border-color .15s, transform .1s;
  position: relative;
}
.pz-search-card:hover {
  border-color: var(--accent);
  transform: scale(1.06);
  z-index: 2;
}
.pz-search-card-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}
.pz-search-card-text {
  width: 100%;
  height: 100%;
  background: var(--bg3);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 4px;
  text-align: center;
  gap: 2px;
}

.pz-board-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* min-width: 0 is the keystone of the whole hscroll feature. The wrap
     is a flex ITEM of .pz-layout (a ROW), and flex items default to
     min-width: auto = their intrinsic min-content — which propagates up
     through pz-plane-clip → plane → sides → rows all the way from the
     shrink-proof zones. Without this override the wrap can NEVER be
     narrower than the board content: clientWidth always ≥ content
     width, needsHScroll is mathematically unreachable, and the excess
     gets clipped by .pz-layout's overflow:hidden instead (content cut
     off with no scrollbar — the Flying-Island symptom). With it, the
     wrap caps at the available column width and genuine overflow
     becomes measurable + scrollable. */
  min-width: 0;
  overflow-y: auto;
  /* overflow-x: clip (was auto) — the tilted plane's near rows project
     wider than the layout box, which used to surface a phantom
     horizontal scroll range on EVERY board. Legitimate horizontal
     scrolling (Flying Island in the Sky adding zones, narrow screens)
     is re-enabled via .pz-can-hscroll, which the scaler toggles from a
     FLAT layout measurement (see .pz-flat-measure) so transform
     overflow can never trigger it. */
  overflow-x: clip;
  padding: 6px 16px;
  gap: 2px;
  /* `safe center`: centered while the column fits, but falls back to
     flex-start the moment it overflows — plain `center` in a scroll
     container pushes the START of the content (the opponent-hand bar)
     into an unreachable region above the scrollport. Same failure mode
     as the horizontal case documented below. The plain `center` line
     stays as a fallback for engines without `safe` support. */
  justify-content: center;
  justify-content: safe center;
}
/* Measurement mode — toggled synchronously by the scaler for one layout
   read: neutralizes the tilt so scrollWidth reflects LAYOUT truth only
   (mirrors board-flat-measure on the battle board). */
.pz-board-wrap.pz-flat-measure .pz-plane-clip { perspective: none !important; }
.pz-board-wrap.pz-flat-measure .pz-board-plane {
  transform: none !important;
  margin-bottom: 0 !important;
  padding-inline: 0 !important; /* overhang padding is transform-derived —
                                   must not count as layout width during
                                   the flat measurement */
}
/* Legit horizontal-scroll mode — layout genuinely wider than the wrap
   (Flying Island zones / narrow viewports). v10 (Al): the tilt STAYS ON
   while scrolling — only the unnecessary scrollbar was the issue, not
   the projection. The min-width chain lets the centered rows expose
   their full content to the scrollbar instead of cropping it
   unreachably on the left; --pz-overhang-x (measured by the scaler)
   additionally reserves the lateral projection overhang of the
   magnified near rows as real scrollable padding, so their left edge
   never sits before scroll position 0. */
.pz-board-wrap.pz-can-hscroll { overflow-x: auto; }
/* pz-plane-clip — untransformed pass-through wrapper (see JSX comment):
   mirrors .board-plane-clip on the battle board. In hscroll mode it
   clips the plane's horizontal TRANSFORM overflow at its own layout
   edge, capping the scrollable range at layout width — the tilted
   plane's transformed border box would otherwise extend it by hundreds
   of phantom pixels. Its layout edge equals the projected near-edge
   content width (--pz-overhang-x reserves (f−1)/2 per side), so no
   visible content is cut. overflow-y stays visible: the vertical
   projection overhang is already reserved as the plane's
   margin-bottom. */
.pz-plane-clip {
  display: flex;
  flex-direction: column;
  /* Containing block for the PzAmbiance layer (first child, inset:0) —
     mirrors the battle board's .board-plane-clip: position:relative +
     z-index:auto creates no stacking context, so paint order vs the
     plane stays pure DOM order (ambiance first → behind). */
  position: relative;
  /* Content-sized like the plane inside it (v15): with `flex: 1 0
     auto` this wrapper kept growing into the wrap's leftover height,
     re-collecting below the now content-sized plane the very slack the
     fold-parity change removes. */
  flex: 0 0 auto;
  /* Direct parent of the plane → perspective must sit here (see
     .pz-board-wrap note). Origin re-based from the wrap's coordinate
     space (50% 15% of the full column incl. hand bars/labels) to this
     box, which spans only the plane: 15% of the wrap lands at the
     plane's top edge, hence 50% 0%. */
  perspective: var(--pz-persp);
  /* x re-anchored on the ME middle hero (v18, battle parity) — see
     --pz-anchor-x in the scaler. */
  perspective-origin: var(--pz-anchor-x, 50%) 0%;
}
.pz-board-wrap.pz-can-hscroll .pz-plane-clip {
  min-width: min-content;
  overflow-x: clip;
  overflow-y: visible;
}
.pz-board-wrap.pz-can-hscroll .pz-board-plane {
  /* v18: separate sides (asymmetric anchor), measured by the scaler. */
  padding-left: var(--pz-overhang-l, 0px);
  padding-right: var(--pz-overhang-r, 0px);
}
.pz-board-wrap.pz-can-hscroll .pz-board-plane,
.pz-board-wrap.pz-can-hscroll .pz-board-plane .board-player-side,
.pz-board-wrap.pz-can-hscroll .pz-board-plane .board-row {
  min-width: min-content;
}
/* When the puzzle board's natural width exceeds the wrapper, let rows
   shrink to their content width so the horizontal scrollbar can surface
   them all — otherwise the flex 'justify-content: center' crops the
   overflow without ever letting the user reach it. */
.pz-board-wrap > * { flex-shrink: 0; }

.pz-side-label {
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--text2);
  text-align: center;
  padding: 2px 0;
}

/* ── Hand area ── */
.pz-hand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 12px;
  border-top: 1px solid var(--bg4);
  background: var(--bg2);
  min-height: 54px;
  flex-shrink: 0;
}
.pz-hand-label {
  font-size: 9px;
  color: var(--text2);
  letter-spacing: 2px;
  white-space: nowrap;
}
.pz-hand-cards {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  flex: 1;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 8px 2px;
}
.pz-hand-card {
  /* v6 (Al): battle-sized hand cards — same --card-w/--card-h the
     in-battle hand uses (86×120 × --board-scale) instead of the old
     fixed 48×67 thumbnails. The bars (.pz-hand) have min-height only,
     so they grow with the cards automatically. */
  width: var(--card-w);
  height: var(--card-h);
  border: 1px solid var(--bg4);
  border-radius: 3px;
  overflow: hidden;
  flex-shrink: 0;
  cursor: grab;
  transition: border-color .15s, transform .1s;
}
.pz-hand-card:hover {
  border-color: var(--accent);
  transform: translateY(-3px);
}
.pz-hand-card:active { cursor: grabbing; }
.pz-hand-card-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}
.pz-hand-card-text {
  width: 100%;
  height: 100%;
  background: var(--bg3);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 3px;
  text-align: center;
  font-size: 7px;
  color: var(--text);
}

/* ── Puzzle Creator: mobile tap-to-select highlight ── */
.pz-hand-card-selected {
  border-color: var(--accent) !important;
  box-shadow: 0 0 12px rgba(0,240,255,.7), inset 0 0 8px rgba(0,240,255,.2);
}

/* ── Puzzle Creator: remove popup ── */
.pz-remove-popup {
  background: var(--danger, #ff4444);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 4px;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0,0,0,.5);
  pointer-events: auto;
}

/* ── Puzzle Creator: mobile touch fixes ── */
@media (pointer: coarse) {
  .pz-search-card,
  .pz-hand-card {
    touch-action: manipulation;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
}

/* ── Puzzle Creator: touch drag ghost ── */
.pz-touch-drag-ghost {
  position: fixed;
  width: 60px;
  height: 84px;
  z-index: 99999;
  pointer-events: none;
  border: 2px solid var(--accent);
  border-radius: 4px;
  box-shadow: 0 0 20px rgba(0,240,255,.5);
  background: var(--bg3);
  overflow: hidden;
  opacity: .85;
}

/* ── Puzzle Creator: mobile layout ── */
@media (max-width: 900px) {
  .pz-search-panel {
    width: 160px;
    padding: 4px;
    gap: 4px;
  }
  .pz-search-results {
    gap: 3px;
  }
  .pz-search-card {
    width: calc((100% - 6px) / 3);
    height: 70px;
  }
  .pz-hand-card {
    width: 40px;
    height: 56px;
  }
  .pz-hand-label {
    font-size: 7px;
    letter-spacing: 1px;
  }
  .pz-hand {
    padding: 4px 6px;
    min-height: 44px;
    gap: 6px;
  }
  .pz-gold-input {
    padding: 2px 6px !important;
    margin-left: 4px !important;
  }
  .pz-gold-input span { font-size: 14px !important; }
  .pz-gold-input input { width: 50px !important; font-size: 13px !important; }
  .pz-side-label { font-size: 7px !important; letter-spacing: 1px !important; }
  .pz-board-wrap { justify-content: flex-start; }
}

/* ═══════════════════════════════════════════════════════════════
   SC FLOAT REWARD — Floating +X SC above puzzle browser
   ═══════════════════════════════════════════════════════════════ */
.sc-float-reward {
  position: absolute;
  top: -10px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 20px;
  color: #ffdd44;
  text-shadow: 0 0 10px rgba(255,221,68,.6), 0 2px 4px rgba(0,0,0,.8);
  pointer-events: none;
  z-index: 10;
  animation: sc-float-up 2.2s ease-out forwards;
}
@keyframes sc-float-up {
  0%   { opacity: 0; transform: translateX(-50%) translateY(0) scale(0.5); }
  15%  { opacity: 1; transform: translateX(-50%) translateY(-10px) scale(1.1); }
  30%  { opacity: 1; transform: translateX(-50%) translateY(-18px) scale(1); }
  100% { opacity: 0; transform: translateX(-50%) translateY(-60px) scale(0.9); }
}

/* ═══════════════════════════════════════════════════════════════
   TEXTBOX — Dialogue overlay with typewriter effect
   ═══════════════════════════════════════════════════════════════ */
.textbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 90000;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 18px;
  background: rgba(0,0,0,.45);
  pointer-events: none;
  animation: textbox-fade-in .25s ease-out;
}
@keyframes textbox-fade-in { from { opacity: 0; } to { opacity: 1; } }
.textbox-fading {
  animation: textbox-fade-out 1s ease-in forwards;
  pointer-events: none !important;
}
@keyframes textbox-fade-out { from { opacity: 1; } to { opacity: 0; } }

/* Boost tooltip above textbox overlay when active */
body:has(.textbox-overlay) .board-tooltip,
body:has(.textbox-overlay) .tooltip {
  z-index: 90003 !important;
}

.textbox {
  display: flex;
  align-items: stretch;
  gap: 0;
  width: min(720px, 92vw);
  background: linear-gradient(135deg, var(--bg2) 0%, var(--bg3) 100%);
  border: 2px solid var(--accent);
  border-radius: 10px;
  box-shadow: 0 0 24px rgba(0,240,255,.15), 0 8px 32px rgba(0,0,0,.6);
  overflow: hidden;
  animation: textbox-slide-in .3s ease-out;
  pointer-events: auto;
  cursor: pointer;
}
@keyframes textbox-slide-in {
  from { opacity: 0; transform: translateY(-20px); }
  to   { opacity: 1; transform: translateY(0); }
}

.textbox-portrait {
  width: 110px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,.3);
  border-right: 1px solid var(--bg4);
  padding: 10px 8px 6px;
  gap: 4px;
  transition: opacity .3s, filter .3s;
}
.textbox-portrait-right {
  border-right: none;
  border-left: 1px solid var(--bg4);
  animation: textbox-portrait-enter .5s ease-out;
}
.textbox-portrait-inactive {
  opacity: 0.4;
  filter: brightness(0.6) saturate(0.5);
}
.textbox-portrait-exit {
  animation: textbox-portrait-leave .5s ease-in forwards;
}
/* Mirror of .textbox-portrait-exit for the left-side speaker — slides
   out to the LEFT instead of the right. */
.textbox-portrait-exit-left {
  animation: textbox-portrait-leave-left .5s ease-in forwards;
}
@keyframes textbox-portrait-enter {
  from { transform: translateX(100%); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}
@keyframes textbox-portrait-leave {
  from { transform: translateX(0); opacity: 1; }
  to   { transform: translateX(100%); opacity: 0; }
}
@keyframes textbox-portrait-leave-left {
  from { transform: translateX(0); opacity: 1; }
  to   { transform: translateX(-100%); opacity: 0; }
}
.textbox-portrait-frame {
  position: relative;
  width: 80px;
  height: 80px;
}
.textbox-portrait img {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--accent);
  box-shadow: 0 0 10px rgba(0,240,255,.25);
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.textbox-sparkle {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 6px 1px rgba(0,240,255,.7), 0 0 12px 2px rgba(0,240,255,.3);
  pointer-events: none;
  animation: textbox-sparkle-anim 2.1s ease-in-out infinite;
  opacity: 0;
}
@keyframes textbox-sparkle-anim {
  0%   { opacity: 0; transform: scale(0) translateY(0); }
  15%  { opacity: 1; transform: scale(1.2) translateY(-2px); }
  40%  { opacity: 1; transform: scale(0.8) translateY(-6px); }
  70%  { opacity: 0.4; transform: scale(0.5) translateY(-10px); }
  100% { opacity: 0; transform: scale(0) translateY(-14px); }
}
.textbox-speaker-name {
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--accent);
  text-transform: uppercase;
  text-align: center;
}

.textbox-body {
  flex: 1;
  display: flex;
  align-items: flex-start;
  padding: 14px 20px 8px;
  height: 110px;
  position: relative;
  overflow: hidden;
}
.textbox-text {
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 18px;
  color: var(--text);
  line-height: 1.5;
  white-space: pre-wrap;
  display: block;
  width: 100%;
}
.textbox-text strong {
  color: var(--accent);
  font-weight: 700;
}
.textbox-text em {
  color: #ffcc66;
  font-style: italic;
}
/* Shake text — each character bobs independently */
.textbox-shake-char {
  position: relative;
  animation: textbox-char-shake 0.5s ease-in-out infinite alternate;
}
@keyframes textbox-char-shake {
  0%   { top: -1.5px; }
  100% { top: 1.5px; }
}
/* Tilde nudge — the pixel font ('Pixel Intv' / 'Press Start 2P') draws
   "~" as a high diacritic instead of an inline squiggle, which leaves
   it floating above the baseline. Shift it down so it sits after the
   preceding word (e.g. "lot~" reads inline instead of "lot" with a
   loose tilde hovering above). Applied via a wrapper span added by
   `applyShake` / `renderMarkdownSlice` — composes cleanly with the
   shake animation (which only touches `top`, not transform). */
.textbox-text .textbox-tilde {
  display: inline-block;
  transform: translateY(0.45em);
}
/* Tutorial boost — card grows dramatically */
.tutorial-boost-anim {
  animation: tutorial-card-grow 2s ease-out !important;
  z-index: 100 !important;
  position: relative !important;
}
@keyframes tutorial-card-grow {
  0%   { transform: scale(1); filter: brightness(1); }
  10%  { transform: scale(1.5); filter: brightness(1.8) saturate(1.5); box-shadow: 0 0 40px rgba(255,200,0,.9), 0 0 80px rgba(255,100,0,.5); }
  30%  { transform: scale(1.35); filter: brightness(1.4); box-shadow: 0 0 30px rgba(255,200,0,.6); }
  100% { transform: scale(1); filter: brightness(1); box-shadow: none; }
}
.textbox-advance {
  position: absolute;
  right: 12px;
  bottom: 8px;
  font-size: 12px;
  color: var(--accent);
  animation: textbox-bounce 0.8s ease-in-out infinite;
}
@keyframes textbox-bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(4px); }
}
.textbox-footer {
  position: absolute;
  right: 28px;
  bottom: 6px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.textbox-page-indicator {
  font-family: 'Pixel Intv', 'Press Start 2P', monospace;
  font-size: 10px;
  color: var(--text2);
}
.textbox-back {
  font-size: 12px;
  color: var(--accent);
  cursor: pointer;
  opacity: 0.7;
  transition: opacity .15s;
}
.textbox-back:hover { opacity: 1; }

/* ── Textbox highlight overlays — cloned cards above the veil ── */
.textbox-highlight {
  z-index: 90001;
  border-radius: 6px;
  box-shadow: 0 0 16px rgba(0,240,255,.6), 0 0 30px rgba(0,240,255,.25);
  animation: textbox-hl-glow 1.5s ease-in-out infinite;
  overflow: hidden;
}
.textbox-highlight-clone {
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.textbox-highlight-clone,
.textbox-highlight-clone * {
  filter: none !important;
  opacity: 1 !important;
  visibility: visible !important;
}
.textbox-highlight-clone *::before,
.textbox-highlight-clone *::after {
  display: none !important;
}
@keyframes textbox-hl-glow {
  0%, 100% { box-shadow: 0 0 16px rgba(0,240,255,.6), 0 0 30px rgba(0,240,255,.25); }
  50%      { box-shadow: 0 0 24px rgba(0,240,255,.8), 0 0 40px rgba(0,240,255,.4); }
}
.textbox-highlight-pulse {
  animation: textbox-hl-pulse 1s ease-in-out infinite;
}
@keyframes textbox-hl-pulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 16px rgba(0,240,255,.6), 0 0 30px rgba(0,240,255,.25); }
  50%      { transform: scale(1.06); box-shadow: 0 0 28px rgba(0,240,255,.8), 0 0 44px rgba(0,240,255,.4); }
}

/* ── Responsive ── */
@media (max-height: 500px) {
  .textbox-overlay { padding-top: 6px; }
  .textbox { width: min(600px, 94vw); }
  .textbox-portrait { width: 80px; padding: 6px 4px 4px; }
  .textbox-portrait-frame { width: 60px; height: 60px; }
  .textbox-portrait img { width: 48px; height: 48px; }
  .textbox-sparkle { width: 4px; height: 4px; }
  .textbox-speaker-name { font-size: 7px; }
  .textbox-body { padding: 10px 14px 6px; height: 80px; }
  .textbox-text { font-size: 14px; }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE & RESPONSIVE
   ═══════════════════════════════════════════════════════════════ */

/* Portrait orientation blocker — game requires landscape */
.rotate-device-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 999999;
  background: var(--bg);
  color: var(--text);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  font-family: var(--font-display), sans-serif;
  text-align: center;
  padding: 24px;
}
.rotate-device-overlay .rotate-icon {
  font-size: 64px;
  animation: rotateHint 2s ease-in-out infinite;
}
.rotate-device-overlay .rotate-text {
  font-size: 14px;
  color: var(--accent);
  font-weight: 700;
}
.rotate-device-overlay .rotate-sub {
  font-size: 11px;
  color: var(--text2);
  max-width: 280px;
}
@keyframes rotateHint {
  0%, 100% { transform: rotate(0deg); }
  25% { transform: rotate(-15deg); }
  75% { transform: rotate(15deg); }
}

/* Show overlay + hide ALL screens in portrait on small devices */
@media (orientation: portrait) and (max-width: 900px) {
  .rotate-device-overlay { display: flex !important; }
  .screen-full, .screen-center { display: none !important; }
}

/* Touch-friendly: ensure minimum tap targets at small scales */
@media (max-width: 1024px) and (pointer: coarse) {
  .board-zone { min-width: 40px; min-height: 56px; }
  .board-phase-item { min-height: 28px; min-width: 44px; }
  .phase-btn { min-height: 32px; }
}

/* ═══ Deck Builder responsive ═══ */

/* Narrow landscape (phones, small tablets in landscape) */
@media (max-width: 1100px) {
  .db-panel-right { width: 280px !important; }
  .db-panel-left { width: 120px !important; }
  .db-card-grid { grid-template-columns: repeat(3, 1fr) !important; }
}

@media (max-width: 850px) {
  /* Top bar: allow wrapping */
  .top-bar { flex-wrap: wrap; gap: 4px; padding: 4px 8px !important; }
  .top-bar .btn, .top-bar label.btn { padding: 3px 6px !important; font-size: 8px !important; }
  .top-bar h2 { font-size: 11px !important; }

  /* Deck panels: shrink further */
  .db-panel-right { width: 200px !important; }
  .db-panel-left { width: 90px !important; }
  .db-card-grid { grid-template-columns: repeat(2, 1fr) !important; }

  /* Deck section body: tighter spacing */
  .deck-section-body { gap: 3px !important; }
  .db-hero-row { gap: 12px !important; padding: 6px !important; }

  /* Deck list items */
  .deck-list-item { font-size: 9px !important; padding: 4px 6px !important; }

  /* Card mini in deck sections — shrink */
  .deck-section-body .card-mini { max-width: 86px; }
}

@media (max-width: 700px) {
  /* Very narrow landscape: collapse left panel */
  .db-panel-left { width: 70px !important; }
  .db-panel-left .orbit-font { font-size: 7px !important; }
  .db-panel-right { width: 160px !important; }
  .db-card-grid { grid-template-columns: repeat(2, 1fr) !important; gap: 2px !important; }

  /* Filters: single column */
  .db-panel-right div[style*="grid-template-columns: 1fr 1fr"] {
    grid-template-columns: 1fr !important;
  }
  .db-panel-right .db-filter-input,
  .db-panel-right .db-filter-select {
    font-size: 9px !important;
    padding: 3px 4px !important;
  }
}

/* ── Scaled inline overrides for game hand ── */
.game-hand .orbit-font { font-size: calc(18px * var(--board-scale)) !important; }
.game-hand-me .orbit-font { font-size: calc(16px * var(--board-scale)) !important; }

/* ═══ General responsive ═══ */

/* Ensure nothing overflows viewport */
@media (max-width: 1024px) {
  .screen-full, .screen-center { max-width: 100vw; overflow-x: hidden; }
  .panel { max-width: calc(100vw - 32px); }
}

/* Scale card-slot and card-mini OUTSIDE deck sections for narrow screens */
@media (max-width: 850px) {
  .card-slot:not(.deck-section-body *) {
    width: 72px !important;
    height: 100px !important;
    font-size: 7px;
  }
  .db-card-grid .card-mini,
  .card-mini:not(.deck-section-body *) {
    font-size: 7px;
  }
  .card-mini .card-name { font-size: 6px; }
  .card-mini .card-type { font-size: 6px; }
  .card-mini .card-stats { font-size: 6px; }
  /* Deck section cards: let grid auto-size them */
  .deck-section-body .card-mini { font-size: 6px; }
  .deck-section-body .card-mini .card-name { font-size: 5px; }
}

@media (max-width: 700px) {
  .card-slot:not(.deck-section-body *) {
    width: 58px !important;
    height: 82px !important;
    font-size: 6px;
  }
  .deck-section-body .card-mini { font-size: 5px; }
}

/* Profile screen responsive */
@media (max-width: 850px) {
  .profile-identity-col { width: 220px !important; min-width: 180px !important; }
  .profile-identity-panel { padding: 14px 12px 10px; }
  .profile-right-col { padding: 12px 14px; }
}
@media (max-width: 700px) {
  .profile-identity-col { width: 160px !important; min-width: 140px !important; }
}

/* Lobby/Play screen responsive */
@media (max-width: 850px) {
  .top-bar { gap: 6px; padding: 6px 10px; }
}

/* ═══ Main Menu card-wall background ═══ */
/* A seamless wall of random card art scrolling bottom→top, layered
   behind the menu. The scroller stacks two identical tiles and slides
   up by exactly one tile height, then loops with no visible seam. */
.menu-card-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
  background: var(--bg1, #0a0a12);
}
.menu-card-bg-scroller {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  will-change: transform;
  animation-name: menu-card-bg-scroll;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.menu-card-bg-tile {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  width: 100%;
}
.menu-card-bg-cell {
  position: relative;
  width: 100%;
  aspect-ratio: 750 / 1050;
}
.menu-card-bg-cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  -webkit-user-drag: none;
  user-select: none;
  /* Push the card art back into "wallpaper": a soft blur + slight darken/
     desaturate so individual cards stop competing with the foreground
     panels for attention. The blur also softens the grid seams. */
  filter: blur(1.5px) brightness(.85) saturate(.95);
}
/* Dark veil so the menu stays readable over the bright card art. The
   centre is kept darker (not lighter) than before so the three panels —
   which sit over the middle of the screen — clearly read as on top. */
.menu-card-bg-veil {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at center, rgba(10,10,18,.58) 0%, rgba(10,10,18,.78) 70%, rgba(10,10,18,.9) 100%);
}
/* EXPERIMENTAL — faint pixel dither over the scrolling card wall, just to add a
   touch of texture to the background. Sits above the cards + veil but below all
   menu content (the whole .menu-card-bg layer is z-index:0; content is z-index
   1+). Kept semi-transparent so the card art still reads through it. To remove,
   delete this single rule. Tuning knobs: `opacity` and `background-size`. */
.menu-card-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .03;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2' height='2'%3E%3Crect width='1' height='1' fill='%23ffffff'/%3E%3Crect x='1' y='1' width='1' height='1' fill='%23ffffff'/%3E%3Crect x='1' y='0' width='1' height='1' fill='%23000000'/%3E%3Crect x='0' y='1' width='1' height='1' fill='%23000000'/%3E%3C/svg%3E");
  background-size: 8px 8px;
  background-repeat: repeat;
  image-rendering: pixelated;
}
@keyframes menu-card-bg-scroll {
  from { transform: translateY(0); }
  to   { transform: translateY(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .menu-card-bg-scroller { animation: none; }
}

/* ═══ Ornate player-coloured frame (menu strip + both side panels) ═══ */
/* --player-color is set on .main-menu-screen and inherited here. The
   frame layers a bold outer border, a glow, an inset accent line, and
   four corner brackets — all in the player's colour. */
.ornate-frame {
  position: relative;
  /* 4px to match the chunky corner ornaments' arm thickness (--bt). */
  border: 4px solid var(--player-color, #00f0ff);
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, .45),
    inset 0 0 26px color-mix(in srgb, var(--player-color, #00f0ff) 16%, transparent),
    0 0 30px color-mix(in srgb, var(--player-color, #00f0ff) 30%, transparent),
    0 12px 46px rgba(0, 0, 0, .55);
}
/* Inset accent line, following the rounded corners. */
.ornate-frame::before {
  content: '';
  position: absolute;
  inset: 5px;
  z-index: -1;
  border: 1px solid color-mix(in srgb, var(--player-color, #00f0ff) 45%, transparent);
  border-radius: 13px;
  pointer-events: none;
}
/* Four chunky stepped corners in the player's colour — a blocky L arm plus
   a diagonally-offset square at each corner, so they read as hand-placed
   pixel-art ornaments rather than sleek thin brackets. Hard edges, no blur
   or glow, to match the 16-bit panel theme. Kept inside the padding box
   (inset > 0) because the side panels clip overflow. The chunky sizes are
   multiples of the panel dither's 8px grid (8 / 4) so they sit "on grid". */
.ornate-frame::after {
  content: '';
  position: absolute;
  inset: 4px;
  z-index: 2;
  pointer-events: none;
  --bk: var(--player-color, #00f0ff);
  --bl: 16px; /* bracket arm length */
  --bt: 4px;  /* bracket arm thickness (also the stair-step square size) */
  background:
    /* top-left: h-arm, v-arm, stair square */
    linear-gradient(var(--bk), var(--bk)) left top / var(--bl) var(--bt) no-repeat,
    linear-gradient(var(--bk), var(--bk)) left top / var(--bt) var(--bl) no-repeat,
    linear-gradient(var(--bk), var(--bk)) left var(--bt) top var(--bt) / var(--bt) var(--bt) no-repeat,
    /* top-right */
    linear-gradient(var(--bk), var(--bk)) right top / var(--bl) var(--bt) no-repeat,
    linear-gradient(var(--bk), var(--bk)) right top / var(--bt) var(--bl) no-repeat,
    linear-gradient(var(--bk), var(--bk)) right var(--bt) top var(--bt) / var(--bt) var(--bt) no-repeat,
    /* bottom-left */
    linear-gradient(var(--bk), var(--bk)) left bottom / var(--bl) var(--bt) no-repeat,
    linear-gradient(var(--bk), var(--bk)) left bottom / var(--bt) var(--bl) no-repeat,
    linear-gradient(var(--bk), var(--bk)) left var(--bt) bottom var(--bt) / var(--bt) var(--bt) no-repeat,
    /* bottom-right */
    linear-gradient(var(--bk), var(--bk)) right bottom / var(--bl) var(--bt) no-repeat,
    linear-gradient(var(--bk), var(--bk)) right bottom / var(--bt) var(--bl) no-repeat,
    linear-gradient(var(--bk), var(--bk)) right var(--bt) bottom var(--bt) / var(--bt) var(--bt) no-repeat;
}

/* ═══ Main Menu hub side panels ═══ */
.menu-side {
  position: absolute;
  top: 78px;
  z-index: 2;
  width: clamp(300px, 26vw, 470px);
  max-height: calc(100vh - 104px);
  display: flex;
}
/* Anchor each panel's inner edge a fixed distance from screen centre
   (just outside the ~360px menu strip) so they hug the menu instead of
   pinning to the screen edges and leaving a big gap on wide monitors. */
.menu-side-left { right: calc(50% + 256px); left: auto; }
.menu-side-right { left: calc(50% + 256px); right: auto; }
/* Left gutter between the screen edge and the Top Players panel's inner edge.
   The avatar is centered inside it so its left/right gaps stay equal. */
.menu-profile-gutter {
  position: absolute;
  top: 78px; /* fallback before panelTop is measured; matches .menu-side */
  left: 0;
  width: calc(50% - 256px - clamp(300px, 26vw, 470px));
  display: flex;
  flex-direction: column;   /* name stacked above the avatar */
  align-items: center;
  gap: 8px;                 /* slight gap between name and avatar */
  z-index: 5;
}
.menu-profile-avatar {
  /* Scale with the gutter, leaving room for the equal side gaps, but stay
     within sensible bounds. */
  width: clamp(72px, calc(50vw - 256px - clamp(300px, 26vw, 470px) - 64px), 160px);
  aspect-ratio: 1;
  border-radius: 0;            /* angular, matching the menu boxes */
  overflow: hidden;
  cursor: pointer;
  border: 4px solid var(--accent);
  background: rgba(0, 0, 0, .35);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform .12s ease;
}
.menu-profile-avatar:hover {
  transform: scale(1.06);
  filter: brightness(1.15);
  box-shadow: 0 0 26px currentColor !important;
}

/* Clickable player name: always reads as a link (dotted underline +
   pointer), and lights up clearly on hover. Colour is inherited from the
   inline player colour. */
.menu-player-name {
  cursor: pointer;
  text-decoration: underline dotted;
  text-decoration-color: currentColor;
  text-underline-offset: 4px;
  transition: text-shadow .12s ease, transform .12s ease, text-decoration-color .12s ease;
}
.menu-player-name:hover {
  text-decoration: underline solid;
  text-shadow: 0 0 12px currentColor, 0 0 20px currentColor;
  transform: scale(1.04);
}
.menu-side-panel {
  width: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;        /* allow the inner scroll area to shrink */
  overflow: hidden;     /* the scrolling happens in .menu-side-scroll */
  background: rgba(8, 10, 18, .66);
  border-radius: 18px;
  padding: 22px 24px 26px;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
/* The one growable, scrollable region inside a panel (leaderboard list /
   decks list). Fixed sections above it stay put; this fills the rest of
   the panel height and scrolls when its content overflows. */
.menu-side-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  margin: 0 -6px;       /* let rows use the panel's full width */
  padding: 0 6px;
}
.menu-side-title {
  font-family: var(--font-display), sans-serif;
  font-size: 16px;
  letter-spacing: 1.8px;
  color: var(--accent);
  margin: 22px 0 13px;
  padding-bottom: 9px;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.menu-side-title:first-child { margin-top: 0; }
.menu-side-empty {
  font-size: 14px;
  color: var(--text2);
  text-align: center;
  padding: 14px 4px;
}
.menu-side-link {
  display: block; width: 100%;
  background: none; border: none; cursor: pointer;
  color: var(--accent); font-family: inherit;
}
.menu-side-link:hover { text-decoration: underline; }

/* Live counter strip */
.menu-side-live {
  display: flex; align-items: center; gap: 9px;
  font-size: 14px; color: var(--text);
  margin-bottom: 16px;
}
.menu-live-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--success, #33ff88);
  box-shadow: 0 0 10px var(--success, #33ff88);
  animation: menu-live-pulse 1.8s ease-in-out infinite;
}
@keyframes menu-live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

/* Leaderboard list */
.menu-lb-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.menu-lb-row {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 10px; border-radius: 0;
  font-size: 15px;
  border: 1px solid transparent;   /* keep height stable vs. medal rows */
}
.menu-lb-row.is-me { background: rgba(0, 240, 255, .12); outline: 1px solid rgba(0, 240, 255, .3); }
/* Top-3 podium: gold / silver / bronze tinted backdrop + border. */
.menu-lb-row.rank-1 {
  background: rgba(255, 215, 0, .16);
  border-color: rgba(255, 215, 0, .6);
  box-shadow: 0 0 12px rgba(255, 215, 0, .22);
}
.menu-lb-row.rank-2 {
  background: rgba(196, 200, 210, .15);
  border-color: rgba(206, 210, 220, .6);
  box-shadow: 0 0 12px rgba(200, 205, 215, .18);
}
.menu-lb-row.rank-3 {
  background: rgba(205, 127, 50, .16);
  border-color: rgba(214, 137, 60, .6);
  box-shadow: 0 0 12px rgba(205, 127, 50, .2);
}
.menu-lb-rank {
  flex-shrink: 0; width: 24px; text-align: center;
  font-weight: 700; color: var(--text2); font-size: 14px;
}
.menu-lb-rank.top { color: #ffd700; }
.menu-lb-row.rank-1 .menu-lb-rank { color: #ffd700; }
.menu-lb-row.rank-2 .menu-lb-rank { color: #d8dce6; }
.menu-lb-row.rank-3 .menu-lb-rank { color: #e0903c; }
.menu-lb-name {
  flex: 1; min-width: 0; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.menu-lb-elo { flex-shrink: 0; color: var(--accent3, #aaff00); font-weight: 700; font-size: 14px; }

/* Record row */
.menu-record { display: flex; gap: 9px; }
.menu-record-cell {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 13px 6px; border-radius: 0;
  background: rgba(255, 255, 255, .04);
  border: 1px solid var(--player-color, #00f0ff);
}
.menu-record-cell b { font-size: 28px; font-weight: 800; line-height: 1; }
.menu-record-cell span { font-size: 10.5px; letter-spacing: .6px; color: var(--text2); }

/* Top heroes */
.menu-hero-row { display: flex; gap: 12px; justify-content: center; }
.menu-hero-cell { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.menu-hero-wr { font-size: 13px; font-weight: 700; color: var(--accent3, #aaff00); }

/* Decks list */
.menu-deck-list { display: flex; flex-direction: column; gap: 8px; }
.menu-deck-card {
  display: flex; align-items: center; gap: 13px; width: 100%;
  padding: 10px 12px; border-radius: 0; cursor: pointer; text-align: left;
  background: rgba(255, 255, 255, .04);
  border: 1px solid var(--player-color, #00f0ff);
  transition: background .15s, border-color .15s;
  /* <button> doesn't inherit the page font — set the pixel UI font explicitly
     so the deck rows match the rest of the menu. */
  font-family: var(--font-display), sans-serif;
}
.menu-deck-card:hover { background: rgba(0, 240, 255, .1); border-color: var(--player-color, #00f0ff); }
.menu-deck-card.is-active {
  background: rgba(0, 240, 255, .14);
  border-color: var(--player-color, #00f0ff);
  cursor: default;
}
/* The standalone "Active Deck" card up top is clickable (→ deck builder). */
.menu-deck-card.is-editable { cursor: pointer; }
.menu-deck-card.is-editable:hover { background: rgba(0, 240, 255, .2); }
.menu-deck-card.is-busy { opacity: .55; cursor: progress; }
/* Illegal decks can't be set active — dim them and signal not-allowed,
   but keep them clickable so the click can explain why. */
.menu-deck-card.is-illegal { opacity: .5; cursor: not-allowed; }
.menu-deck-card.is-illegal:hover { background: rgba(255, 51, 102, .1); border-color: rgba(255, 51, 102, .35); }
.menu-deck-meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
.menu-deck-name {
  font-size: 15px; font-weight: 700; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.menu-deck-status { font-size: 12px; }
.menu-deck-status.ok { color: var(--success, #33ff88); }
.menu-deck-status.bad { color: var(--danger, #ff3366); }
.menu-deck-flag {
  flex-shrink: 0; font-size: 10px; font-weight: 800; letter-spacing: .5px;
  color: var(--accent); white-space: nowrap;
}

/* Hide the flanking panels when there isn't room beside the menu. */
@media (max-width: 1180px) {
  .menu-side { display: none; }
}

/* ═══ Main Menu responsive ═══ */
.menu-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 30px;
  /* Frosted dark panel so buttons/labels read over the scrolling card
     wall (the menu buttons are mostly transparent on their own). Border +
     glow come from .ornate-frame. */
  background: rgba(8, 10, 18, .66);
  border-radius: 18px;
  padding: 30px 40px;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
/* Title + subtitle get their own backdrop plaque (they sit above the
   menu-body panel in the layout). */
.menu-title,
.menu-subtitle {
  background: rgba(8, 10, 18, .55);
  border-radius: 12px;
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
.menu-title { padding: 14px 24px; }
.menu-subtitle { padding: 5px 16px; border-radius: 9px; }
.menu-user-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
/* With the title/subtitle gone from above and the player info up in the
   top row, the central strip grows to fill the freed vertical space —
   the buttons get taller and larger so they fill the now-higher (and
   wider) container. (font-size needs !important to beat the per-button
   inline fontSize.) */
.menu-buttons .btn-big {
  height: 66px;
  padding-top: 0;
  padding-bottom: 0;
  font-size: 18px !important;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  /* Every main-menu button tracks the player's chosen colour (text, border
     and icon), the way Play Online always has. */
  color: var(--player-color, #00f0ff);
  border-color: var(--player-color, #00f0ff);
  background: color-mix(in srgb, var(--player-color, #00f0ff) 8%, transparent);
}
/* Labels are centered on the button. */
.menu-nav-label {
  text-align: center;
}
/* Puzzle pair: two half-width buttons sharing a row. They sit flush with
   the full-width buttons above/below. */
.menu-nav-row {
  display: flex;
  gap: 12px;
}
.menu-nav-row .btn-big {
  flex: 1 1 0;
  min-width: 0;
}
/* Half buttons are narrower, so shrink the label a touch and allow it to
   wrap to two lines. */
.menu-buttons .btn-big.menu-nav-btn--half {
  font-size: 14px !important;
  padding-left: 12px;
  padding-right: 12px;
}
.menu-nav-btn--half .menu-nav-label {
  white-space: normal;
  line-height: 1.15;
}

/* ═══ Daily challenge button ═══ */
@keyframes daily-glow {
  0%, 100% { box-shadow: 0 0 8px rgba(255,215,0,.55), 0 0 16px rgba(255,215,0,.25); border-color: #ffd700; }
  50%      { box-shadow: 0 0 18px rgba(255,215,0,.95), 0 0 32px rgba(255,215,0,.45); border-color: #fff2a8; }
}
.menu-daily-btn { transition: transform .15s, filter .15s; }
.menu-daily-btn:hover { transform: scale(1.04); filter: brightness(1.15); }
.menu-daily-btn.is-available { animation: daily-glow 1.6s ease-in-out infinite; }

@media (max-height: 500px) {
  /* Landscape mobile — reorganize menu to horizontal */
  .main-menu-screen { flex-direction: row !important; gap: 0 !important; padding: 10px 24px !important; justify-content: center !important; }
  .menu-title { font-size: 16px !important; position: absolute; top: 10px; left: 50%; transform: translateX(-50%); }
  .menu-subtitle { font-size: 9px !important; position: absolute; top: 32px; left: 50%; transform: translateX(-50%); }
  .menu-body { flex-direction: row; gap: 40px; margin-top: 20px; }
  .menu-buttons { width: 200px !important; gap: 8px !important; }
  .menu-buttons .btn-big { font-size: 12px !important; padding: 6px 12px !important; height: auto !important; }
  .menu-user-info { align-items: flex-start; gap: 8px; }
  .menu-user-info .orbit-font { font-size: 16px !important; }
  .menu-user-info .badge { font-size: 13px !important; padding: 5px 10px !important; }
  .menu-logout-btn { display: block; }
}

/* ═══ Create Game modal compact ═══ */
@media (max-height: 500px) {
  .modal { padding: 12px 16px !important; min-width: 280px !important; max-height: 90vh !important; }
  .modal h3 { font-size: 13px !important; margin-bottom: 8px !important; }
  .modal .btn { padding: 5px 8px !important; font-size: 10px !important; }
  .modal .input { padding: 4px 8px !important; font-size: 10px !important; }
  /* Hide Game Type / Format labels on mobile */
  .modal div[style*="fontSize: 11"] { display: none !important; }
}

/* ═══ Profile responsive — deck collection + hero medals ═══ */
/* Win% overlay on hero card */
.profile-top-hero-wr-overlay {
  display: none;
}
.profile-top-hero-card { position: relative; }

@media (max-width: 850px) {
  .profile-top-hero-rank { display: none !important; }
  .profile-deck-tile-card { width: 64px !important; height: 90px !important; }
  .profile-deck-wall { grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)) !important; gap: 6px !important; }

  /* Top Heroes: bigger images, win% overlay, hide stats/W/L text */
  .profile-top-hero-card { width: 78px !important; height: 109px !important; }
  .profile-top-hero-wr-overlay {
    display: flex !important;
    position: absolute; bottom: 0; left: 0; right: 0;
    background: rgba(0,0,0,.75);
    font-family: var(--font-display), sans-serif;
    font-size: 12px; font-weight: 800;
    justify-content: center; padding: 2px 0;
    text-shadow: 0 0 6px currentColor;
  }
  .profile-top-hero-stats { display: none !important; }
  .profile-top-hero-wl { display: none !important; }
  .profile-top-hero-bar { display: none !important; }
  .profile-top-hero-name { font-size: 10px !important; }

  /* Deck collection: ensure it's visible and scrollable */
  .profile-section-wide { flex: none !important; min-height: 200px !important; }
  .profile-deck-wall { min-height: 120px; }

  /* Fix profile container width — prevent cut-off */
  .profile-identity-col { width: 200px !important; min-width: 160px !important; }
  .profile-right-col { padding: 10px 12px !important; overflow-x: hidden; }
}
@media (max-width: 700px) {
  .profile-deck-tile-card { width: 50px !important; height: 70px !important; }
  .profile-deck-wall { grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)) !important; }
  .profile-top-hero { gap: 2px !important; }
  .profile-top-hero-card { width: 68px !important; height: 95px !important; }
  .profile-top-hero-details { display: none !important; }
}

/* ═══ Battle board mobile ═══ */
@media (max-height: 500px) {
  /* Compact opponent hand */
  .game-hand-opp {
    min-height: 40px !important;
    padding: 2px 8px !important;
  }
  .game-hand-opp .game-hand-avatar-big {
    width: 28px !important; height: 28px !important;
  }
  .game-hand-opp .orbit-font { font-size: 11px !important; }
  .game-hand-opp .game-hand-cards .board-card {
    height: 36px !important;
  }
  .game-hand-opp .game-gold-icon { font-size: 18px !important; }
  .game-hand-opp .game-gold-value { font-size: 16px !important; }

  /* Compact player hand. The container is small (54px) but the cards
     inside remain full-size and interactive. Anchor cards to the
     BOTTOM of the container so they overflow UPWARD into the board
     area instead of spilling past the viewport's bottom edge (which
     was the old bug — `align-items: center` centered the cards on
     a 54px line at the very bottom of the screen, clipping the
     lower half). game-hand-me has overflow: visible + z-index 10000
     so the upward overflow paints on top of the board and stays
     fully interactive. */
  .game-hand-me {
    min-height: 54px !important;
    padding: 2px 8px !important;
    overflow: visible;
    align-items: flex-end !important;
  }
  .game-hand-me .game-hand-cards {
    align-items: flex-end !important;
    overflow: visible !important;
  }
  .game-hand-me .game-hand-avatar-big {
    width: 28px !important; height: 28px !important;
  }
  .game-hand-me .orbit-font { font-size: 11px !important; }

  /* Make BOARD-CENTER scroll, not the whole game-board — sidebars stay fixed */
  .game-board {
    overflow: hidden !important;
  }
  .board-center {
    min-height: 0 !important;
    overflow-y: auto !important;
    overflow-x: hidden !important;
  }
  /* Player sides: natural height so board-center scrolls */
  .board-player-side {
    flex: none !important;
    min-height: auto !important;
    justify-content: flex-start !important;
  }

  /* Separate player halves */
  .board-side-opp { padding-bottom: 2px; }
  .board-side-me { padding-top: 2px; }

  /* ── Area zones: flow-based at midpoint, keep JS horizontal positions ── */
  .board-area-zones-center {
    position: relative !important;
    top: auto !important;
    transform: none !important;
    height: 0 !important;
    overflow: visible !important;
    pointer-events: none !important;
    z-index: 10 !important;
  }
  .board-area-zones-center .board-zone {
    position: absolute !important;
    top: 0 !important;
    transform: translateY(-50%) translateX(calc(var(--spacer-w) * 1.15)) !important;
    pointer-events: auto !important;
  }

  /* ── Floating numbers: scale down ── */
  .damage-number { font-size: 12px !important; }
  .gold-gain-number, .gold-loss-number { font-size: 20px !important; }
  .toughness-hp-number { font-size: 14px !important; }
  .fighting-atk-number { font-size: 14px !important; }
  .level-change-number { font-size: 9px !important; }

  /* Compact phase tracker + buttons — much smaller */
  .board-mid-row { padding: 1px 0 !important; gap: 4px !important; }
  .board-phase-tracker { gap: 1px !important; }
  .board-phase-item { padding: 1px 4px !important; font-size: 6px !important; min-height: auto !important; }
  .phase-buttons-row { gap: 2px !important; margin-top: 1px !important; }
  .phase-btn { padding: 1px 5px !important; font-size: 6px !important; min-height: 16px !important; }
  .phase-end-check { font-size: 6px !important; }
  .phase-end-check input { width: 8px !important; height: 8px !important; }
  /* Keep the phase column anchored to the screen-left, but its inner
     tracker / buttons must stay in a single ROW (the intended layout).
     The previous override forced flex-direction: column, which stacked
     the phase items vertically and broke the strip. */
  .phase-column { zoom: 0.9; left: 0 !important; }
  .board-phase-tracker { flex-direction: row !important; gap: 1px !important; }
  .phase-buttons-row { flex-direction: row !important; }

  /* Compact top bar in battle */
  .game-layout > .top-bar { padding: 2px 8px !important; }
  .game-layout > .top-bar .btn { padding: 2px 8px !important; font-size: 8px !important; }
  .game-layout > .top-bar h2 { font-size: 10px !important; }
}

/* ═══ Deck editor: make right panel fully scrollable on mobile ═══ */
@media (max-height: 500px) {
  .db-panel-right {
    overflow-y: auto !important;
  }
  /* Allow filter section to scroll away */
  .db-panel-right > div[style*="flexShrink: 0"] {
    flex-shrink: 1 !important;
  }
  /* Compact filter inputs */
  .db-filter-input, .db-filter-select {
    padding: 2px 4px !important;
    font-size: 9px !important;
  }
  /* Deck section hero row — tighter gaps at narrow widths. Sizes come
     from the responsive grid + aspect-ratio, so no pixel overrides. */
  .db-hero-row { gap: 8px !important; padding: 4px !important; }

  /* ── Card tooltip: match right column width, image-only ── */
  .card-tooltip {
    width: 200px !important;
    height: auto !important;
    max-height: calc(100vh - 40px) !important;
    top: 38px !important;
  }
  /* Gallery tooltip: position to the LEFT of the gallery panel */
  .card-tooltip-gallery {
    right: 200px !important;
  }
  /* Hide text info, show only card image */
  .card-tooltip .card-tooltip-info { display: none !important; }
  .card-tooltip .card-tooltip-img img {
    aspect-ratio: 750/1050 !important;
    width: 100% !important;
  }

  /* Gallery cards: smaller in grid */
  .db-card-grid .card-mini {
    height: 80px !important;
  }
}

/* Touch devices: make tooltips tappable/scrollable */
@media (pointer: coarse) {
  .board-tooltip, .tooltip { pointer-events: auto !important; cursor: default; }
}

/* ═══ Board tooltip mobile ═══ */
@media (max-height: 500px) {
  .board-tooltip {
    width: 240px !important;
    font-size: 11px !important;
  }
  /* Hide text info under card image — image only */
  .board-tooltip > div:last-child { display: none !important; }
  .board-tooltip img { max-width: 100%; }
  .board-tooltip-token { display: none !important; }

  /* Scale down status/buff/immune icons */
  .status-badge { width: 12px !important; height: 12px !important; font-size: 7px !important; }
  .buff-icon { width: 14px !important; height: 14px !important; font-size: 8px !important; }
  .status-immune-icon { font-size: 10px !important; }
  .additional-action-icon { font-size: 8px !important; }
  .bonus-action-counter { font-size: 8px !important; padding: 0 3px !important; }
}

/* Tooltip width matches right panel at narrower breakpoints */
@media (max-height: 500px) and (max-width: 700px) {
  .card-tooltip { width: 160px !important; }
  .card-tooltip-gallery { right: 160px !important; }
}

/* Profile heroes column — tighter on mobile */
@media (max-width: 850px) {
  .profile-heroes-col {
    padding-left: 8px !important;
    min-width: 100px !important;
  }
}

/* ═══ Battle: fix sidebar pile positions on mobile ═══ */
@media (max-height: 500px) {
  .board-util-side {
    justify-content: center !important;
    gap: 2px !important;
  }
  .board-util-spacer { flex: 0 0 2px !important; }
  .board-util { padding: 2px !important; }
}

/* ═══════════════════════════════════════════
   CHAT + ACTION LOG COLUMN
   ═══════════════════════════════════════════ */
.chat-log-column {
  width: var(--chat-col-w);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: rgba(0,0,0,.25);
  border-left: 1px solid var(--bg4);
  border-right: 1px solid var(--bg4);
  overflow: hidden;
  position: relative;
}

/* ── Action Log Panel ── */
.action-log-panel {
  flex: 0 0 38%;
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid var(--bg4);
  min-height: 0;
}
.action-log-header {
  font-family: var(--font-display), sans-serif;
  font-size: 9px;
  font-weight: 700;
  color: var(--text2);
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: 6px 8px 4px;
  background: rgba(0,0,0,.3);
  border-bottom: 1px solid rgba(255,255,255,.06);
  flex-shrink: 0;
}
.action-log-body {
  flex: 1;
  overflow-y: auto;
  padding: 4px 6px;
  min-height: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text2);
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.12) transparent;
}
.action-log-body::-webkit-scrollbar { width: 4px; }
.action-log-body::-webkit-scrollbar-track { background: transparent; }
.action-log-body::-webkit-scrollbar-thumb { background: rgba(255,255,255,.12); border-radius: 2px; }
.action-log-entry {
  padding: 3px 0;
  border-bottom: 1px solid rgba(255,255,255,.03);
  word-wrap: break-word;
}
.action-log-entry:last-child { border-bottom: none; }
.action-log-turn-divider {
  text-align: center;
  font-family: var(--font-display), sans-serif;
  font-size: 8px;
  color: rgba(255,255,255,.3);
  padding: 4px 0 2px;
  letter-spacing: 1px;
}
.log-player-name { font-weight: 700; }
.log-card-name { font-weight: 600; }
.log-amount { font-weight: 700; }
.log-damage { color: #ff6644; }
.log-heal { color: #66ff88; }
.log-status { color: #aa88ff; }
.log-info { color: var(--text2); opacity: .8; }
.log-hero-defeated {
  font-weight: 800;
  color: #ff4444;
  text-shadow: 0 0 6px rgba(255,68,68,.4);
  background: rgba(255,0,0,.08);
  display: block;
  padding: 2px 4px;
  border-left: 2px solid #ff4444;
  margin: 2px 0;
}

/* ── Chat Panel ── */
.chat-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.chat-header {
  font-family: var(--font-display), sans-serif;
  font-size: 9px;
  font-weight: 700;
  color: var(--text2);
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: 6px 8px 4px;
  background: rgba(0,0,0,.3);
  border-bottom: 1px solid rgba(255,255,255,.06);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
.chat-header-back {
  cursor: pointer;
  font-size: 12px;
  color: var(--accent);
  transition: transform .15s;
}
.chat-header-back:hover { transform: translateX(-2px); }
.chat-tabs {
  display: flex;
  flex-shrink: 0;
  border-bottom: 1px solid rgba(255,255,255,.06);
}
.chat-tab {
  flex: 1;
  text-align: center;
  padding: 5px 4px;
  font-size: 9px;
  font-weight: 600;
  color: var(--text2);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: all .15s;
  font-family: var(--font-display), sans-serif;
  letter-spacing: .5px;
}
.chat-tab:hover { color: #fff; background: rgba(255,255,255,.03); }
.chat-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.chat-body {
  flex: 1;
  overflow-y: auto;
  padding: 4px 6px;
  min-height: 0;
  font-size: 12px;
  line-height: 1.45;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.12) transparent;
}
.chat-body::-webkit-scrollbar { width: 4px; }
.chat-body::-webkit-scrollbar-track { background: transparent; }
.chat-body::-webkit-scrollbar-thumb { background: rgba(255,255,255,.12); border-radius: 2px; }
.chat-msg {
  padding: 3px 0;
  word-wrap: break-word;
}
.chat-msg-name {
  font-weight: 700;
  font-size: 10px;
  cursor: pointer;
}
.chat-msg-name:hover { text-decoration: underline; }
.chat-msg-text { color: rgba(255,255,255,.88); margin-left: 2px; }
.chat-msg.spectator-msg .chat-msg-text { color: rgba(255,255,255,.55); }
.chat-msg-avatar {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  object-fit: cover;
  vertical-align: middle;
  margin-right: 3px;
}
.chat-input-row {
  display: flex;
  gap: 0;
  flex-shrink: 0;
  border-top: 1px solid rgba(255,255,255,.06);
}
.chat-input {
  flex: 1;
  background: rgba(0,0,0,.3);
  border: none;
  color: #fff;
  font-size: 12px;
  padding: 6px 8px;
  outline: none;
  font-family: inherit;
  min-width: 0;
}
.chat-input::placeholder { color: rgba(255,255,255,.25); }
.chat-send-btn {
  background: var(--accent);
  border: none;
  color: #000;
  font-weight: 700;
  font-size: 10px;
  padding: 0 10px;
  cursor: pointer;
  font-family: var(--font-display), sans-serif;
  letter-spacing: .5px;
  transition: background .15s;
}
.chat-send-btn:hover { background: #fff; }

/* ── Player List ── */
.player-list {
  padding: 4px 6px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  border-bottom: 1px solid rgba(255,255,255,.06);
  flex-shrink: 0;
  max-height: 80px;
  overflow-y: auto;
}
.player-list-entry {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  cursor: pointer;
  padding: 2px 4px;
  border-radius: 3px;
  transition: background .1s;
}
.player-list-entry:hover { background: rgba(255,255,255,.06); }
.player-list-avatar {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  object-fit: cover;
}
.player-list-name { font-weight: 600; }
.player-list-badge {
  font-size: 7px;
  font-family: var(--font-display), sans-serif;
  opacity: .5;
  margin-left: auto;
}

/* ── Ping Flash ── */
.chat-ping-flash {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 100;
  animation: pingFlash .8s ease-out forwards;
  pointer-events: none;
}
@keyframes pingFlash {
  0% { opacity: 1; box-shadow: 0 0 20px currentColor, 0 2px 30px currentColor; }
  100% { opacity: 0; box-shadow: 0 0 0 transparent; }
}

/* ── Scrollable Battlefield ── */
.board-center {
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.board-center::-webkit-scrollbar { height: 0; }
.board-center.can-scroll { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.15) transparent; }
.board-center.can-scroll::-webkit-scrollbar { height: 5px; }
.board-center.can-scroll::-webkit-scrollbar-track { background: transparent; }
.board-center.can-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,.15); border-radius: 3px; }
.board-center.can-scroll .board-player-side { min-width: min-content; }
.board-center.can-scroll .board-row { min-width: min-content; flex-wrap: nowrap; }
.board-center.can-scroll .board-mid-row { min-width: min-content; flex-wrap: nowrap; }
/* When the battlefield is wider than its normal center slot, collapse the
   left-side symmetry spacer so the extra zones have room to extend into
   that space before horizontal scrolling kicks in. The board-center-spacer
   only existed for visual symmetry with the chat column anyway. */
.game-layout:has(.board-center.can-scroll) .board-center-spacer { width: 0 !important; flex-shrink: 1 !important; }

/* ── Mobile: collapse chat-log-column by default (JS-controlled) ── */
@media (max-width: 900px) {
  .board-center-spacer { display: none; }
  .board-player-side, .board-mid-row { transform: none !important; }
  .game-hand-me .game-hand-info { position: static; transform: none; }
  .game-hand-cards { margin-left: 0 !important; }
}

/* ── Sidebar collapsed state ── */
.chat-log-column.chat-log-collapsed {
  width: 28px !important;
  min-width: 28px;
  flex-shrink: 0;
  cursor: pointer;
}
.sidebar-toggle-btn {
  background: none;
  border: none;
  color: var(--text2);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 100%;
  padding: 8px 2px;
  font-family: var(--font-display), sans-serif;
  font-weight: 700;
  transition: color .2s;
}
.sidebar-toggle-btn:hover { color: var(--accent); }
.sidebar-toggle-close {
  position: absolute;
  top: 4px;
  right: 4px;
  width: auto;
  height: auto;
  padding: 2px 6px;
  z-index: 2;
  font-size: 11px;
}

/* ── Center heroes on screen via JS-calculated offset ── */
.board-player-side,
.board-mid-row {
  transform: translateX(var(--center-offset, 0px));
}

/* ═══ Pseudo-3D battlefield (YGO-style perspective tilt) ═══════════════
   .board-plane wraps both player sides + area zones + mid row (see JSX)
   and is tilted as ONE continuous ground plane. The rotation axis sits
   near the LOCAL player's rows (transform-origin 82%): their zones stay
   ~1:1 while the opponent's rows recede hard toward the horizon (~0.72×)
   — the classic YGO board look. A short perspective (750px) makes the
   trapezoid pronounced; 1250px at 22° (first attempt) projected to a
   ~4% shrink and read as "nothing happened".
   • Cards inside zones lie ON the plane, so any card placed in — or
     drag-previewed over — a zone is automatically angled with the field.
   • The cursor-following drag ghost (.hand-floating-card) deliberately
     stays flat until it snaps into a zone.
   • Pointer events + getBoundingClientRect honour 3D transforms, so all
     existing drag-&-drop hit detection and fly-to animations keep
     working against the projected positions unchanged.
   • BoardAmbiance, area-atmosphere overlays and the permanents column
     live OUTSIDE the plane as flat full-bleed layers.
   • board-flat-measure is toggled by the can-scroll detector in
     app-board.jsx: the tilted plane's near edge inflates scrollWidth,
     which would latch can-scroll on and — via the fallback below —
     disable the tilt itself. Measuring flat breaks that loop (the
     !important also neutralizes the zoom, which is intended: scroll
     UX must depend on layout width only).
   Tune the look in ONE place via the four custom properties:
   v4 tuning (Al: "gap between top zones and opponent hand still too
   large — close it and enlarge the zones"): the key change is the
   pivot moving from 82% to 96%, i.e. onto the local player's BOTTOM
   row. With the rotation/zoom axis anchored there, increasing the zoom
   grows the plane exclusively UPWARD into the top gap — the previous
   pivot made the bottom rows grow downward past the hand instead, so
   zoom and gap-filling fought each other. Result at 29° / 900px /
   zoom 1.26: far rows ≈0.90× (was 0.84×), own rows ≈1.06×, bottom row
   ≈1.26×, top row rises ~100px. The .board-side-opp rule below
   additionally removes the LAYOUT slack above the opponent rows
   (justify-content:center kept them vertically centered in the top
   half). If outer zones ever clip at the sides on narrow boards,
   lower --board-plane-zoom first. */
.board-center {
  --board-tilt: 29deg;
  --board-persp: 900px;
  --board-pivot: 96%;   /* rotation/zoom axis — anchored on the bottom row */
  --board-plane-zoom: 1.26;
  /* NOTE: perspective itself lives on .board-plane-clip below, NOT here.
     CSS perspective only applies to DIRECT children — with the clip
     wrapper between this element and .board-plane, a perspective
     declared here reaches nothing, and rotateX without perspective
     degenerates to a flat affine squash (the "board renders top-down
     2D" regression). Only the tuning custom properties stay here so
     they inherit everywhere. */
}
.board-plane {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  position: relative;   /* containing block for .board-area-zones-center */
  /* scale() applies before rotateX (right-to-left): zoom happens IN the
     plane, so far rows shrink-from-enlarged consistently. */
  transform: rotateX(var(--board-tilt)) scale(var(--board-plane-zoom));
  /* x-origin follows the same middle-hero anchor as the perspective, so
     the zoom magnifies about the hero axis instead of shifting content
     sideways relative to it. */
  transform-origin: var(--board-anchor-x, 50%) var(--board-pivot);
  will-change: transform;
}
/* Pull the opponent rows to the top of their half — their default
   justify-content:center left ~40px of layout slack above the hero row,
   which the projection then widened further. Scoped to the tilted
   desktop layout only: the mobile media below disables the tilt and has
   its own flex-start rule, and can-scroll mode stays centered. */
@media (min-width: 901px) and (min-height: 561px) {
  .board-center:not(.can-scroll) .board-plane .board-side-opp {
    justify-content: flex-start;
    padding-top: calc(10px * var(--board-scale));
  }
}
/* Fallbacks — the projection fights cramped / scrolling layouts, drop it.
   (rotateX without perspective would still squash vertically, so the
   transform must be reset too, not just the perspective.) */
.board-center.board-flat-measure .board-plane-clip { perspective: none; }
.board-center.board-flat-measure .board-plane {
  transform: none !important;
  padding-inline: 0 !important; /* overhang padding is transform-derived —
                                   must not count as layout width during
                                   the flat measurement */
}
/* During the flat measurement the box must REMAIN a scroll container:
   check() briefly drops can-scroll each pass (it runs on every render),
   and without this exception the :not(.can-scroll) clip below would
   hard-reset the user's scroll offset to 0 every second. */
.board-center.board-flat-measure:not(.can-scroll) { overflow-x: auto; }
/* board-plane-clip — untransformed pass-through wrapper between the
   scroll container and the tilted plane. Normally invisible plumbing;
   in scroll mode it caps the scrollable range at LAYOUT width: the
   tilted plane's transformed border box (layout contentW·f, projected
   contentW·f²) would otherwise extend board-center's scrollable
   overflow by hundreds of phantom pixels past the last zone. Clipping
   the x-axis at this wrapper stops that propagation; its layout edge
   equals the projected near-edge content width (the overhang padding
   reserves exactly (f−1)/2 per side), so no visible content is lost.
   overflow-y stays visible — the far rows project upward and are
   clipped by board-center itself, exactly as before (clip+visible is
   the one legal mixed overflow pair). */
.board-plane-clip {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* Containing block for the atmosphere overlays (BoardAmbiance + area
     backgrounds) that live inside this wrapper: in scroll mode the
     wrapper is min-content wide, so their inset:0 spans the FULL
     widened battlefield instead of one viewport width. position:
     relative + z-index:auto creates no stacking context, so the
     overlays' z-indices keep resolving against .board-center. */
  position: relative;
  /* The wrapper is the plane's direct parent, so the perspective MUST
     sit here (see .board-center note). Its box spans the same area the
     plane occupied before the wrapper existed — the sides, mid row and
     overlays are absolute or inside the plane — so 50% 30% references
     the same visual point as the pre-wrapper tuning. The element's own
     overflow clipping (scroll mode) does not affect perspective: the
     child is projected first, clipped after. */
  perspective: var(--board-persp);
  /* x re-anchored on the me-side middle hero (v18): --board-anchor-x is
     published in px by check() so the middle hero is ALWAYS the
     laterally unskewed axis, however asymmetric islands widen the box. */
  perspective-origin: var(--board-anchor-x, 50%) 30%;
}
.board-center.can-scroll .board-plane-clip {
  min-width: min-content;
  overflow-x: clip;
  overflow-y: visible;
}
/* can-scroll keeps the tilt (v10, Al): with extra zones in play (Flying
   Island in the Sky) the board legitimately scrolls horizontally — the
   3D projection stays on and the scrollbar appears. The magnified near
   rows project PAST the plane's layout box sideways; without
   compensation their left edge would sit in the unreachable region
   before scroll position 0. check() in app-board.jsx measures that
   lateral overhang and publishes --board-overhang-x, reserved here as
   real scrollable padding. min-width: min-content lets the plane grow
   with its content so the scrollbar exposes all zones. */
.board-center.can-scroll .board-plane {
  min-width: min-content;
  /* v18: separate sides — with the off-center anchor the projection
     spills asymmetrically; check() measures each side from the
     projected zone union. */
  padding-left: var(--board-overhang-l, 0px);
  padding-right: var(--board-overhang-r, 0px);
}
/* Outside scroll mode the tilted plane's near corners protrude a few px
   past the box — clip instead of exposing a phantom horizontal scroll
   range (base rule sets overflow-x: auto for the scroll feature). */
.board-center:not(.can-scroll) { overflow-x: clip; }
@media (max-width: 900px), (max-height: 560px) {
  .board-plane-clip { perspective: none; }
  .board-plane { transform: none; }
}

/* ── Puzzle-creator variant of the plane ──────────────────────────────
   Same tilt + perspective as the battle board so the creator previews
   the in-battle look 1:1, but its OWN pivot/zoom: the creator column
   packs labels and hand bars directly against the rows, so there is no
   vertical gap to grow into — a centered pivot with a small zoom keeps
   the plane inside its slot instead of pushing rows into the OPP-HAND
   bar (battle values 96% / 1.26 would do exactly that). .pz-board-wrap
   is a real scroll container (overflow auto both axes), so the zoom
   stays small enough that the plane's transform overflow doesn't
   surface phantom scroll range. The test mode is NOT covered here on
   purpose — it mounts the regular GameBoard and inherits .board-plane. */
.pz-board-wrap {
  --pz-tilt: 24deg;      /* milder than battle: the creator plane is much
                            taller (all rows of both sides), so the same
                            angle produces far larger displacement */
  --pz-persp: 1400px;
  --pz-plane-zoom: 1;
  /* NOTE: perspective lives on .pz-plane-clip (the plane's DIRECT
     parent) — declared here it reaches nothing since the clip wrapper
     sits in between; rotateX without perspective renders as flat 2D.
     Custom props stay here so they inherit. */
}
.pz-board-plane {
  /* CONTENT-sized plane, battle-parity fold (v15, Al): the previous
     `flex: 1 0 auto` + `justify-content: space-between` grew the plane
     into the wrap's leftover height and parked ALL slack in the middle
     — the two sides drifted apart and the area zones floated in a
     void. Al's requirement is the battle layout: both hero rows close
     together at the fold, area zones straddling it half-on-each-side.
     Row order already guarantees hero rows are fold-adjacent (opp:
     support→ability→hero, me: hero→ability→support), so packing the
     plane to content height makes the whole board a tight cluster:
     opp hand → rows → fold → rows → staging hand, with .pz-board-wrap's
     `safe center` centering the compact column. Leftover viewport
     space is consumed by the scaler instead: it grows --board-scale
     until the projected column fills clientHeight−6 (capped by the
     width fits), so slack becomes zone size, not gaps — which also
     keeps the v6 requirement (no gap between top rows and the OPP-HAND
     bar) intact.
     TOP pivot (origin 50% 0%): the plane's top edge is ON the rotation
     axis and does not move — the opponent rows sit flush under their
     hand bar with NO projection sag, regardless of plane height (the
     v6 centered pivot sagged the top by ~125px on tall boards). All
     displacement goes to the bottom instead: the near rows project
     PAST the layout box toward the camera; the JS scaler in
     app-puzzle.jsx measures that overhang each pass and reserves it as
     --pz-overhang margin so the bottom rows never paint over the YOU
     label / staging hand. */
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  transform: rotateX(var(--pz-tilt)) scale(var(--pz-plane-zoom));
  transform-origin: var(--pz-anchor-x, 50%) 0%;
  margin-bottom: var(--pz-overhang, 0px);
  will-change: transform;
}
/* Inside the plane the sides must be content-sized: their global
   `flex: 1` (basis 0) worked against .pz-board-wrap's definite height,
   but the plane is content-sized — basis-0 children would collapse to
   height 0 there. Vertical centering of the whole column is already
   handled by .pz-board-wrap's justify-content: center. */
.pz-board-plane .board-player-side {
  flex: 0 0 auto;
  min-height: auto;
}
/* Zero-height area fold (battle parity, see JSX comment): the row takes
   no vertical layout space; align-items centers the fixed-height zones
   on the fold line so they straddle both sides' rows, exactly like the
   battle overlay. z-index 10 matches .board-area-zones-center — without
   it the later sibling (player side) paints over the zones' lower half
   and eats hover/drop events there. */
.pz-area-fold {
  height: 0;
  overflow: visible;
  position: relative;
  z-index: 10;
  /* Small symmetric corridor between the two hero rows — mirrors the
     battle board's mid-row band. The zones (≈95px·scale tall, centered
     on the line) still straddle the fold and overlap onto BOTH sides'
     field halves, as required. */
  margin-block: calc(12px * var(--board-scale));
}
/* v21: fold zones are FLOW children again (state-derived spacer groups
   position them — see the pz-area-fold JSX); the row's align-items
   centers the fixed-height zones on the zero-height line, straddling
   both sides. The measured-absolute variant (v16–v20) is gone: it was
   inherently prone to one-frame-stale CSS vars on layout-frame
   changes. */
@media (max-width: 900px), (max-height: 560px) {
  .pz-plane-clip { perspective: none; }
  .pz-board-plane { transform: none; }
}
/* ── Opponent hand cards: no offset needed, symmetric margins handle centering ── */
/* ── Player hand info: absolute so cards center at viewport midpoint ── */
.game-hand-me .game-hand-info {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  /* Push the player name further from the avatar. */
  gap: calc(22px * var(--board-scale));
}
/* Vertical divider separating the avatar/name cluster from the hand cards. */
.game-hand-me .game-hand-info::after {
  content: '';
  position: absolute;
  right: calc(-14px * var(--board-scale));
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: calc(var(--hand-min-h) * 0.78);
  background: linear-gradient(to bottom,
    transparent 0%,
    color-mix(in srgb, var(--hand-accent, #00f0ff) 60%, transparent) 16%,
    color-mix(in srgb, var(--hand-accent, #00f0ff) 60%, transparent) 84%,
    transparent 100%);
}
/* ── Centering spacer — mirrors chat-log-column to make layout symmetric ── */
.board-center-spacer {
  width: var(--chat-col-w);
  flex-shrink: 1;
  min-width: 0;
}

/* ── Rules Screen Responsive ── */
@media (max-width: 850px) {
  .rules-sidebar { width: 160px; }
  .rules-nav-item { font-size: 13px; padding: 6px 12px; }
  .rules-content { padding: 18px 22px; }
  .rules-section-title { font-size: 22px; }
  .rules-card-img { width: 340px; }
  .rules-card-img-sm { width: 260px; }
  .rules-subtype-grid,
  .rules-schools-grid { grid-template-columns: 1fr; }
  .rules-status-grid { grid-template-columns: 1fr 1fr; }
  .rules-board-legend { grid-template-columns: 1fr; }
}

@media (max-width: 700px) {
  .rules-sidebar { display: none; }
  .rules-content { padding: 14px 16px; }
  .rules-card-row { flex-direction: column; }
  .rules-card-img { width: 100%; max-width: 480px; }
  .rules-card-img-sm { width: 100%; max-width: 360px; }
  .rules-status-grid { grid-template-columns: 1fr; }
  .rules-deck-limits { flex-direction: column; }
}

/* ═══════════════════════════════════════════════════════════════════════
   RETRO PIXEL THEME  —  appended override layer (menus only)
   One self-contained block so it's easy to tweak or rip out. Leans the
   interface into the 16-bit look: crisp pixel text, square chrome, chunky
   bevels, hard offset shadows, pressed-in buttons, and a subtle CRT pass.
   The in-game board opts out via `.screen-full.no-crt`.
   ═══════════════════════════════════════════════════════════════════════ */

/* 1 ── Crisp pixel text — kill the anti-alias blur on the pixel fonts */
body, button, input, select, textarea,
.btn, .orbit-font, .pixel-font, .tab, .badge {
  -webkit-font-smoothing: none;
  -moz-osx-font-smoothing: grayscale;
  font-smooth: never;
  text-rendering: optimizeSpeed;
}

/* 2 ── Keep scaled 16-bit art crisp instead of bilinear-blurred */
img, .card-tooltip-img img { image-rendering: pixelated; }

/* 3 ── Buttons: flat fill, square, hard offset shadow, press-in on click */
.btn {
  letter-spacing: 0;
  border-radius: 0;
  border-width: 3px; /* chunkier pixel frame, matching the menu ornaments */
  box-shadow: 2px 2px 0 rgba(0,0,0,.5);
  transition: filter .1s, transform .05s, box-shadow .05s,
              background .15s, color .15s, border-color .15s;
}
.btn:hover  { box-shadow: 3px 3px 0 rgba(0,0,0,.5); }
.btn:active { transform: translate(2px,2px); box-shadow: none; }
.btn:disabled { box-shadow: 1px 1px 0 rgba(0,0,0,.35); }
.btn-big, .menu-nav-btn { border-radius: 0; }
/* menu-hub buttons keep their colour-glow hover but adopt the press-in feel */
.menu-buttons .btn:active { transform: translate(2px,2px) scale(1); box-shadow: none; }

/* 4 ── Inputs & selects: square, chunkier border, hard focus outline */
.input, .select, .db-filter-input, .db-filter-select {
  border-radius: 0;
  border-width: 3px;
}
.input:focus, .select:focus,
.db-filter-input:focus, .db-filter-select:focus {
  box-shadow: none;
  outline: 2px solid var(--accent);
  outline-offset: 0;
}

/* 5 ── Panels: square + 16-bit bevel (light top-left, dark bottom-right) */
.panel {
  border-radius: 0;
  border: 4px solid var(--bg4);
  box-shadow:
    inset 2px 2px 0 rgba(255,255,255,.05),
    inset -2px -2px 0 rgba(0,0,0,.45),
    5px 5px 0 rgba(0,0,0,.5);
}

/* 6 ── Modals: square, hard drop shadow, darker dim */
.modal { border-radius: 0; box-shadow: 6px 6px 0 rgba(0,0,0,.6); }
.modal-overlay { background: rgba(4,4,10,.8); }

/* 7 ── Badges: square with a crisp 3px frame */
.badge { border-radius: 0; border: 3px solid currentColor; }

/* 8 ── Auth/login panel: drop the frosted glass for a flat pixel window */
.auth-screen .panel {
  border-radius: 0;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: rgba(8,8,16,.92);
  border: 4px solid rgba(170,136,255,.55);
  box-shadow: 6px 6px 0 rgba(0,0,0,.7), 0 0 0 1px rgba(0,0,0,.6);
  /* Own stacking context + relative positioning + visible overflow so the
     .auth-panel-explosion child can be a full-viewport overlay that renders
     ABOVE the box's background/dither (painted first) but BELOW the form
     controls (.auth-panel-content, z-index:2). The box no longer clips it. */
  position: relative;
  z-index: 0;
  overflow: visible;
}
/* Form controls, lifted above the explosion overlay. */
.auth-panel-content {
  position: relative;
  z-index: 2;
  /* The dithered box surface (the text's old dark backing) now sits BELOW the
     explosion, so bare text would read teal-on-yellow over the blast. Give all
     panel text a hard 1px pixel outline in the box's near-black tone: it stays
     legible on the bright explosion without touching any text colour. Inputs
     opt out — their opaque field background already backs the typed text. */
  --pixel-outline:
    1px 1px 0 #05050c, -1px 1px 0 #05050c, 1px -1px 0 #05050c, -1px -1px 0 #05050c,
    0 1px 0 #05050c, 0 -1px 0 #05050c, 1px 0 0 #05050c, -1px 0 0 #05050c;
  text-shadow: var(--pixel-outline);
}
.auth-panel-content .input { text-shadow: none; }
/* "Trading Card Game" gets a small border in the logo's colour to match — a
   slim 4-direction 1px ring (thinner than the full 8-direction outline). */
.auth-panel-content .auth-subtitle {
  text-shadow:
    1px 0 0 var(--player-color, #00f0ff), -1px 0 0 var(--player-color, #00f0ff),
    0 1px 0 var(--player-color, #00f0ff), 0 -1px 0 var(--player-color, #00f0ff);
}
/* Keep the link hover glow, but preserve the readable outline underneath it. */
.auth-panel-content .auth-link:hover { text-shadow: var(--pixel-outline), 0 0 8px var(--accent); }

/* Login screen reuses the main-menu wordmark logo. .pp-logo-img is sized for
   the full-width menu header (831×105 art at 92px tall ≈ 728px wide), so here
   we centre the stack and fit the image to the panel width instead. */
.auth-logo { display: block; margin: 0 auto 10px; transform: scale(1.06); transform-origin: center bottom; }
.auth-logo .pp-logo-img { width: 100%; height: auto; max-height: none; }

/* Explosion overlay. The wrapper is a viewport-sized box centred on the panel
   — and .screen-center centres the panel in the viewport, so it lines up flush
   with the backdrop scene. The inner core reproduces the backdrop's explosion
   layer verbatim (inset:0 + center/100% 100% + ab-coreTether), so the blast
   sits in EXACTLY the same place it did in the scene; only its layer changed
   (now above the box, below the controls). It rides the auth-panel-shake clock,
   staying in sync with the scene's shake. .auth-screen's overflow:hidden clips
   the viewport-sized box, so no scrollbars. z-index:1 keeps it above the box. */
.auth-panel-explosion {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 100vw;
  height: 100vh;
  transform: translate(-50%, -50%);
  z-index: 1;
  pointer-events: none;
}
.auth-panel-explosion__core {
  position: absolute;
  inset: 0;
  background: url('/data/animated_screen/layers2/explosion.png') center / 100% 100% no-repeat;
  transform-origin: 52% 42%;
  animation: ab-coreTether 4.8s ease-in-out infinite;
  image-rendering: pixelated;
}

/* 9 ── Main-menu chrome: square the frames/plaques, drop the blur */
.ornate-frame, .ornate-frame::before { border-radius: 0; }
.menu-body, .menu-side-panel {
  border-radius: 0;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: rgba(8,10,18,.93);
}
.menu-title, .menu-subtitle {
  border-radius: 0;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: rgba(8,10,18,.9);
}

/* 12 ── Dithering — a coarse ordered checkerboard tiled over the menu boxes
   (panels/modals/containers) for that hand-shaded 16-bit texture. It's a tiny 2x2 SVG
   (two opaque pixels on the diagonal) scaled up with image-rendering:pixelated
   so the chunky squares stay sharp. Layered as background-IMAGE with
   !important so it rides on top of each element's background-COLOR and survives
   the shorthand `background` resets on hover / inline tints.
   Because it's a *background*, text/numbers (foreground content) always render
   ABOVE it — only the surfaces are dithered, never the glyphs. */
.panel,
.modal,
.menu-body,
.menu-side-panel {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2' height='2'%3E%3Crect width='1' height='1' fill='%23ffffff' fill-opacity='0.10'/%3E%3Crect x='1' y='1' width='1' height='1' fill='%23ffffff' fill-opacity='0.10'/%3E%3C/svg%3E") !important;
  background-size: 8px 8px !important;
  background-repeat: repeat !important;
  image-rendering: pixelated;
}

/* 13 ── Mask the menu-box dithering under content that sits on top of it.
   Buttons / deck cards / leaderboard rows have translucent backgrounds, so the
   dithered panel behind them "shines through". A translucent layer can't hide
   just one layer behind it, so instead we give these elements an OPAQUE surface
   (panel-toned) with their own tint mixed in: the colour reads the same, but the
   solid fill stops the dither at their edges. --menu-surface ≈ the panels'
   apparent colour, so the masked tiles blend into the box. */
:root { --menu-surface: #0b0d16; }

/* Main-menu nav buttons */
.menu-buttons .btn {
  background: color-mix(in srgb, var(--player-color, #00f0ff) 8%, var(--menu-surface)) !important;
}

/* Deck cards (Active Deck + Your Decks), incl. hover / state tints */
.menu-deck-card {
  background: color-mix(in srgb, #ffffff 4%, var(--menu-surface)) !important;
}
.menu-deck-card:hover {
  background: color-mix(in srgb, var(--accent) 10%, var(--menu-surface)) !important;
}
.menu-deck-card.is-active {
  background: color-mix(in srgb, var(--accent) 14%, var(--menu-surface)) !important;
}
.menu-deck-card.is-editable:hover {
  background: color-mix(in srgb, var(--accent) 20%, var(--menu-surface)) !important;
}
.menu-deck-card.is-illegal:hover {
  background: color-mix(in srgb, var(--danger) 10%, var(--menu-surface)) !important;
}

/* Top-players rows (the player-name labels live here) + stat cells */
.menu-lb-row { background: var(--menu-surface) !important; }
.menu-lb-row.is-me  { background: color-mix(in srgb, var(--accent) 12%, var(--menu-surface)) !important; }
.menu-lb-row.rank-1 { background: color-mix(in srgb, #ffd700 16%, var(--menu-surface)) !important; }
.menu-lb-row.rank-2 { background: color-mix(in srgb, #ced2dc 15%, var(--menu-surface)) !important; }
.menu-lb-row.rank-3 { background: color-mix(in srgb, #cd7f32 16%, var(--menu-surface)) !important; }
.menu-record-cell   { background: color-mix(in srgb, #ffffff 4%, var(--menu-surface)) !important; }

/* Login screen: its panel is dithered too, and the buttons are translucent —
   give them the same opaque-surface masking as the menu buttons so the dither
   doesn't shine through. (Inputs are already opaque, so they don't need it.) */
.auth-screen .btn {
  background: color-mix(in srgb, var(--accent) 8%, var(--menu-surface)) !important;
}
.auth-screen .btn:hover {
  background: color-mix(in srgb, var(--accent) 18%, var(--menu-surface)) !important;
}

/* Logout button: same opaque-surface treatment as the menu nav buttons so the
   card wall / dither doesn't show through it (keeps its own accent colour). */
.menu-logout-btn {
  background: color-mix(in srgb, var(--accent) 8%, var(--menu-surface)) !important;
}
.menu-logout-btn:hover {
  background: color-mix(in srgb, var(--accent) 18%, var(--menu-surface)) !important;
}

/* Modal buttons (e.g. the Create Game submenu) sit on the dithered .modal
   surface and are translucent, so the dither shines through and they read as
   "background-less". Give them the same opaque tinted surface treatment as the
   menu buttons — mixed with the modal's own --bg2 so they blend in — per
   button variant. Variant rules come after the base so they win (equal
   specificity, later source). */
.modal .btn {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg2)) !important;
}
.modal .btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 24%, var(--bg2)) !important;
}
.modal .btn-accent2 {
  background: color-mix(in srgb, var(--accent2) 12%, var(--bg2)) !important;
}
.modal .btn-accent2:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent2) 24%, var(--bg2)) !important;
}
.modal .btn-success {
  background: color-mix(in srgb, var(--success) 12%, var(--bg2)) !important;
}
.modal .btn-success:hover:not(:disabled) {
  background: color-mix(in srgb, var(--success) 24%, var(--bg2)) !important;
}
.modal .btn-danger {
  background: color-mix(in srgb, var(--danger) 12%, var(--bg2)) !important;
}
.modal .btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--danger) 24%, var(--bg2)) !important;
}
/* Active format/selection pills keep a stronger gold fill, still opaque. */
.modal .btn-format-active {
  background: color-mix(in srgb, #ffd700 22%, var(--bg2)) !important;
}


/* Als Kosmetik-Ruling: Effekt-Quelle leuchtet auf, bevor ihre
   Abwürfe/Wirkungen laufen (effect_source_glow-Broadcast). */
.effect-source-glow {
  animation: effectSourceGlow 1.0s ease-out;
  z-index: 30;
}
@keyframes effectSourceGlow {
  0%   { box-shadow: 0 0 0 0 rgba(255, 214, 90, 0.0);  filter: brightness(1); }
  25%  { box-shadow: 0 0 18px 6px rgba(255, 214, 90, 0.95); filter: brightness(1.35); }
  60%  { box-shadow: 0 0 14px 4px rgba(255, 214, 90, 0.7);  filter: brightness(1.2); }
  100% { box-shadow: 0 0 0 0 rgba(255, 214, 90, 0.0);  filter: brightness(1); }
}

/* ── GROSSER KARTEN-AUFTRITT (1.8.) ─────────────────────────────────
   Terror beendet den Zug: die Karte erscheint mittig, wächst über die
   volle Dauer und blendet dabei aus. Kurzer Einblendmoment am Anfang,
   damit sie nicht hart aufpoppt. */
@keyframes pp-card-showcase {
  0%   { opacity: 0;    transform: scale(0.75); }
  8%   { opacity: 1;    transform: scale(1);    }
  100% { opacity: 0;    transform: scale(2.1);  }
}

/* ══════════════════════════════════════════════════════════
   Waflav — Evolutions-Animation beim Formwechsel
   Gefeuert vom Server-Event `play_evolution_animation`; die
   Engine wartet die Dauer ab, bevor sie weiterresolvt.
   ══════════════════════════════════════════════════════════ */

@keyframes waflavEvoFlash {
  0%   { filter: brightness(1) saturate(1); transform: scale(1); }
  15%  { filter: brightness(2.6) saturate(2) hue-rotate(60deg); transform: scale(1.08); }
  30%  { filter: brightness(1.2) saturate(1.4); transform: scale(0.97); }
  45%  { filter: brightness(3) saturate(2.4) hue-rotate(120deg); transform: scale(1.12); }
  65%  { filter: brightness(1.4) saturate(1.6); transform: scale(0.99); }
  80%  { filter: brightness(2.2) saturate(1.8) hue-rotate(40deg); transform: scale(1.05); }
  100% { filter: brightness(1) saturate(1); transform: scale(1); }
}

.waflav-evolving {
  animation: waflavEvoFlash 1.6s ease-in-out;
  z-index: 20;
}

.waflav-devolving {
  animation: waflavEvoFlash 1.2s ease-in-out reverse;
  z-index: 20;
}

@keyframes waflavEvoBeam {
  0%   { opacity: 0; transform: translate(-50%, -50%) scaleY(0.1) scaleX(0.6); }
  20%  { opacity: 0.95; transform: translate(-50%, -50%) scaleY(1) scaleX(1); }
  70%  { opacity: 0.75; transform: translate(-50%, -50%) scaleY(1) scaleX(0.8); }
  100% { opacity: 0; transform: translate(-50%, -50%) scaleY(1.2) scaleX(0.2); }
}

.waflav-evo-beam {
  position: fixed;
  width: 120px;
  height: 320px;
  pointer-events: none;
  z-index: 9998;
  background: linear-gradient(to top,
    rgba(47,191,122,0) 0%,
    rgba(111,227,160,0.85) 25%,
    rgba(220,255,235,0.95) 55%,
    rgba(111,227,160,0.6) 80%,
    rgba(47,191,122,0) 100%);
  filter: blur(2px);
  animation: waflavEvoBeam var(--evo-ms, 1600ms) ease-out forwards;
}

@keyframes waflavEvoRing {
  0%   { opacity: 0.9; transform: translate(-50%, -50%) scale(0.2); border-width: 6px; }
  100% { opacity: 0;   transform: translate(-50%, -50%) scale(2.6); border-width: 1px; }
}

.waflav-evo-ring {
  position: fixed;
  width: 130px;
  height: 130px;
  margin: 0;
  border: 4px solid #8fffc4;
  border-radius: 50%;
  pointer-events: none;
  z-index: 9997;
  box-shadow: 0 0 18px #2fbf7a, inset 0 0 18px #2fbf7a;
  animation: waflavEvoRing var(--evo-ms, 1600ms) ease-out forwards;
}

@keyframes waflavEvoParticle {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.4) rotate(0deg); }
  20%  { opacity: 1; }
  100% { opacity: 0;
         transform: translate(calc(-50% + var(--we-x)), calc(-50% + var(--we-y)))
                    scale(1.3) rotate(320deg); }
}

/* Mess-Fenster für Tutorial-Highlights: schaltet Perspektive und
   Ebenen-Transform für einen Frame ab, damit `getBoundingClientRect()`
   Layout- statt Projektionskoordinaten liefert. Spiegelbild der
   Flat-Fenster, die der Board-Scaler ohnehin benutzt. */
.tb-flat-probe { perspective: none !important; }
.tb-flat-probe > .board-plane,
.tb-flat-probe > .pz-board-plane { transform: none !important; }

/* Dauer-Wackeln einzelner Bark-Buchstaben (Waflav).
   Bewusst SCHWAECHER und langsamer als `.cpu-shout-letter`: die
   Shout-Animation muss weiterhin als Bruellen erkennbar bleiben, das
   Bounce-Wackeln ist nur die Grundunruhe der Figur. */
.cpu-bounce-letter {
  display: inline-block;
  animation: cpu-bounce-letter 1.1s ease-in-out infinite;
}
@keyframes cpu-bounce-letter {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-1.5px); }
}

/* ═══════════════════════════════════════════════════════════════════
   KAMPAGNE (Story-Modus)
   ═══════════════════════════════════════════════════════════════════
   Grundsatz: NICHTS wird interpoliert. Jedes Bild der Kampagne ist
   Pixelart und wird ausschliesslich ganzzahlig vergroessert
   (`image-rendering: pixelated`), damit alle Pixel gleich gross
   bleiben. Die Buehne haengt UNTEN — beschnitten wird oben (Himmel,
   Decke), nie unten, wo die Figuren stehen.
   ══════════════════════════════════════════════════════════════════ */

.cmp-root { position: fixed; inset: 0; overflow: hidden; background: #05050a; }

.cmp-stage-clip {
  position: absolute; inset: 0; overflow: hidden;
  display: flex; justify-content: center; align-items: flex-end;
  background: #05050a;
}
.cmp-stage { position: relative; flex: 0 0 auto; }

.cmp-bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  image-rendering: pixelated; image-rendering: crisp-edges;
  user-select: none; -webkit-user-drag: none;
}
.cmp-bg-old { z-index: 1; animation: cmp-bg-fade .45s ease forwards; }
@keyframes cmp-bg-fade { from { opacity: 1; } to { opacity: 0; } }
.cmp-stage > .cmp-bg:not(.cmp-bg-old) { z-index: 2; animation: cmp-bg-in .45s ease; }
@keyframes cmp-bg-in { from { opacity: 0; } to { opacity: 1; } }

/* ── Figuren ─────────────────────────────────────────────────────── */
.cmp-actor { position: absolute; z-index: 20; }
.cmp-actor img {
  image-rendering: pixelated; image-rendering: crisp-edges;
  user-select: none; -webkit-user-drag: none;
  filter: drop-shadow(0 3px 0 rgba(0,0,0,.35));
}
.cmp-actor-in { animation: cmp-actor-in .38s ease; }
@keyframes cmp-actor-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.cmp-actor-out { animation: cmp-actor-out .38s ease forwards; }
@keyframes cmp-actor-out { to { opacity: 0; transform: translateY(6px); } }
.cmp-actor-dim img { filter: brightness(.55) drop-shadow(0 3px 0 rgba(0,0,0,.35)); }

.cmp-actor-click { cursor: pointer; }
.cmp-actor-click:hover img { filter: drop-shadow(0 0 6px var(--accent)) drop-shadow(0 3px 0 rgba(0,0,0,.35)); }
.cmp-actor-label {
  position: absolute; left: 50%; top: -18px; transform: translateX(-50%);
  font-family: 'Pixel Intv', monospace; font-size: 10px; white-space: nowrap;
  color: #fff; background: rgba(10,10,20,.82); border: 1px solid var(--accent);
  padding: 2px 6px; border-radius: 3px; opacity: 0; transition: opacity .15s; pointer-events: none;
}
.cmp-actor-click:hover .cmp-actor-label { opacity: 1; }

/* ── Klickflaechen ohne Bild ─────────────────────────────────────── */
.cmp-hotspot {
  position: absolute; z-index: 15; background: transparent;
  border: 1px dashed transparent; border-radius: 4px; cursor: pointer; padding: 0;
}
.cmp-hotspot:hover { border-color: var(--accent); background: rgba(0,240,255,.10); }
.cmp-hotspot-show { border-color: rgba(255,0,200,.7); background: rgba(255,0,200,.10); }
.cmp-hotspot-label {
  position: absolute; left: 50%; top: -18px; transform: translateX(-50%);
  font-family: 'Pixel Intv', monospace; font-size: 10px; white-space: nowrap;
  color: #fff; background: rgba(10,10,20,.82); border: 1px solid var(--accent);
  padding: 2px 6px; border-radius: 3px; opacity: 0; transition: opacity .15s; pointer-events: none;
}
.cmp-hotspot:hover .cmp-hotspot-label { opacity: 1; }

/* ── Effekte ─────────────────────────────────────────────────────── */
.cmp-fx-shake { animation: cmp-shake .5s; }
@keyframes cmp-shake {
  0%,100% { transform: translate(0,0); } 20% { transform: translate(-6px,3px); }
  40% { transform: translate(5px,-3px); } 60% { transform: translate(-4px,-2px); } 80% { transform: translate(3px,3px); }
}
.cmp-fx-flash::after { content: ''; position: absolute; inset: 0; background: #fff; z-index: 60; animation: cmp-flash .5s ease-out forwards; pointer-events: none; }
@keyframes cmp-flash { from { opacity: .85; } to { opacity: 0; } }
.cmp-fx-fadeout::after { content: ''; position: absolute; inset: 0; background: #000; z-index: 60; animation: cmp-fadeout .5s ease forwards; pointer-events: none; }
@keyframes cmp-fadeout { from { opacity: 0; } to { opacity: 1; } }
.cmp-fx-fadein::after { content: ''; position: absolute; inset: 0; background: #000; z-index: 60; animation: cmp-fadein .5s ease forwards; pointer-events: none; }
@keyframes cmp-fadein { from { opacity: 1; } to { opacity: 0; } }

/* ── Uhr ─────────────────────────────────────────────────────────── */
.cmp-clock {
  position: absolute; left: 16px; top: 14px; z-index: 120;
  font-family: 'Pixel Intv', monospace; text-align: center;
  background: linear-gradient(180deg, rgba(12,12,24,.92), rgba(8,8,16,.92));
  border: 2px solid var(--accent); border-radius: 8px; padding: 6px 14px 7px;
  box-shadow: 0 0 18px rgba(0,240,255,.22), inset 0 0 12px rgba(0,0,0,.6);
  min-width: 104px;
}
.cmp-clock-day  { font-size: 9px;  color: var(--text2); letter-spacing: 2px; }
.cmp-clock-time { font-size: 22px; color: var(--accent); line-height: 1.15; text-shadow: 0 0 12px rgba(0,240,255,.5); }
.cmp-clock-part { font-size: 9px;  color: var(--text2); letter-spacing: 1px; }
.cmp-clock-bump { animation: cmp-clock-bump .7s ease; }
@keyframes cmp-clock-bump {
  0% { transform: scale(1); } 25% { transform: scale(1.13); border-color: #ffd700; }
  100% { transform: scale(1); }
}

/* ── Kopfleiste rechts (Muenzen, Gegenstaende) ───────────────────── */
.cmp-hud { position: absolute; right: 16px; top: 14px; z-index: 120; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.cmp-coins {
  display: flex; align-items: center; gap: 10px;
  font-family: 'Pixel Intv', monospace; font-size: 26px; color: #ffd700;
  background: linear-gradient(180deg, rgba(14,14,28,.94), rgba(8,8,16,.94));
  border: 3px solid #ffd700; border-radius: 10px; padding: 9px 18px;
  text-shadow: 0 0 14px rgba(255,215,0,.45);
  box-shadow: 0 0 20px rgba(255,215,0,.22), inset 0 0 14px rgba(0,0,0,.6);
  line-height: 1;
}
.cmp-coins img { width: 30px; height: 30px; image-rendering: pixelated; }
.cmp-items { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; max-width: 320px; }
.cmp-item {
  font-family: 'Pixel Intv', monospace; font-size: 10px; color: var(--text);
  background: rgba(10,10,20,.85); border: 1px solid var(--text2); border-radius: 4px; padding: 3px 7px;
}

.cmp-locname {
  position: absolute; left: 50%; top: 16px; transform: translateX(-50%); z-index: 115;
  font-family: 'Pixel Intv', monospace; font-size: 13px; letter-spacing: 2px; color: #fff;
  background: rgba(10,10,20,.7); border-bottom: 2px solid var(--accent);
  padding: 6px 20px; border-radius: 4px 4px 0 0; text-shadow: 0 2px 4px #000;
}

/* ── Ortsliste ───────────────────────────────────────────────────── */
.cmp-places { position: absolute; right: 16px; bottom: 16px; z-index: 130; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.cmp-places-toggle {
  font-family: 'Pixel Intv', monospace; font-size: 12px; letter-spacing: 2px; color: var(--text);
  background: linear-gradient(180deg, rgba(20,20,40,.95), rgba(10,10,22,.95));
  border: 2px solid var(--accent); border-radius: 8px; padding: 9px 16px; cursor: pointer;
  box-shadow: 0 0 16px rgba(0,240,255,.2);
}
.cmp-places-toggle:hover { background: rgba(0,240,255,.16); }
.cmp-places-list {
  display: flex; flex-direction: column; gap: 4px; max-height: 56vh; overflow-y: auto;
  background: rgba(8,8,16,.94); border: 2px solid var(--accent); border-radius: 8px; padding: 8px; min-width: 260px;
}
.cmp-place {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  background: rgba(255,255,255,.04); border: 1px solid transparent; border-left: 3px solid var(--accent);
  border-radius: 4px; padding: 7px 10px; cursor: pointer; text-align: left; width: 100%;
}
.cmp-place:hover:not(.is-disabled) { background: rgba(0,240,255,.14); border-color: var(--accent); }
.cmp-place-name { font-family: 'Pixel Intv', monospace; font-size: 12px; color: var(--text); }
.cmp-place-meta { font-size: 10px; color: var(--text2); }
.cmp-place.is-here { border-left-color: #ffd700; }
.cmp-place.is-here .cmp-place-name { color: #ffd700; }
.cmp-place.is-disabled { opacity: .45; cursor: default; border-left-color: var(--text2); }
.cmp-places-empty { font-size: 11px; color: var(--text2); padding: 6px; }

/* ── Dialogbox ───────────────────────────────────────────────────── */
.cmp-dialogue {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 200;
  display: flex; align-items: flex-end; justify-content: center; gap: 0;
  padding: 0 3vw 2.2vh; cursor: pointer;
}
.cmp-portrait { flex: 0 0 auto; align-self: flex-end; margin-bottom: -2px; }
.cmp-portrait img {
  height: min(30vh, 260px); width: auto;
  image-rendering: pixelated; image-rendering: crisp-edges;
  filter: drop-shadow(0 0 12px rgba(0,0,0,.8));
}
.cmp-portrait-right img { transform: scaleX(-1); }
.cmp-dialogue-box {
  position: relative; flex: 1 1 auto; max-width: 1100px; min-height: 132px;
  background: linear-gradient(180deg, rgba(14,14,28,.96), rgba(8,8,18,.97));
  border: 3px solid var(--accent); border-radius: 10px; padding: 20px 22px 18px;
  box-shadow: 0 0 30px rgba(0,240,255,.18), inset 0 0 24px rgba(0,0,0,.7);
}
.cmp-speaker {
  position: absolute; top: -15px; left: 20px;
  font-family: 'Pixel Intv', monospace; font-size: 13px; letter-spacing: 1px; color: var(--accent);
  background: #0a0a14; border: 2px solid var(--accent); border-radius: 5px; padding: 3px 12px;
}
.cmp-dialogue-text { font-size: 17px; line-height: 1.65; color: var(--text); white-space: pre-wrap; }
.cmp-dialogue-narration .cmp-dialogue-text { font-style: italic; color: #c9c9e6; }
.cmp-think { font-style: italic; color: #b9b9dd; }
.cmp-advance { margin-left: 8px; color: var(--accent); animation: pulse 1.1s infinite; }
.cmp-log-btn {
  position: absolute; right: 10px; top: 8px; background: transparent; border: none;
  color: var(--text2); font-size: 14px; cursor: pointer; padding: 2px 6px;
}
.cmp-log-btn:hover { color: var(--accent); }

/* ── Auswahl ─────────────────────────────────────────────────────── */
.cmp-choice-overlay {
  position: absolute; inset: 0; z-index: 210; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 12px; background: rgba(0,0,0,.45);
}
.cmp-choice-prompt { font-family: 'Pixel Intv', monospace; font-size: 14px; color: #fff; text-shadow: 0 2px 6px #000; margin-bottom: 4px; }
.cmp-choice-list { display: flex; flex-direction: column; gap: 10px; width: min(680px, 82vw); }
.cmp-choice {
  position: relative; text-align: left; font-size: 16px; color: var(--text);
  background: linear-gradient(180deg, rgba(18,18,36,.96), rgba(10,10,22,.96));
  border: 2px solid var(--accent); border-left-width: 6px; border-radius: 8px;
  padding: 14px 18px; cursor: pointer; transition: transform .1s, background .15s;
}
.cmp-choice:hover { background: rgba(0,240,255,.16); transform: translateX(6px); }
.cmp-choice-hint { display: block; font-size: 12px; color: var(--text2); margin-top: 3px; }
.cmp-choice-time { position: absolute; right: 14px; top: 14px; font-size: 11px; color: #ffd700; }

/* ── Ende / Rücksetzpunkt ────────────────────────────────────────── */
.cmp-gameover {
  position: absolute; inset: 0; z-index: 300; background: rgba(0,0,0,.88);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; cursor: pointer;
}
.cmp-gameover-title { font-family: 'Pixel Intv', monospace; font-size: 40px; color: var(--danger); text-shadow: 0 0 40px rgba(255,51,102,.55); }
.cmp-gameover-text { font-size: 16px; color: var(--text); max-width: 620px; text-align: center; }
.cmp-gameover-hint { font-size: 12px; color: var(--text2); animation: pulse 1.6s infinite; }

/* ── Fenster (Verlauf, Deck, Menue) ──────────────────────────────── */
.cmp-modal-overlay {
  position: absolute; inset: 0; z-index: 260; background: rgba(0,0,0,.72);
  display: flex; align-items: center; justify-content: center;
}
.cmp-modal {
  background: linear-gradient(180deg, #14142a, #0b0b18);
  border: 3px solid var(--accent); border-radius: 12px; padding: 16px;
  box-shadow: 0 0 40px rgba(0,0,0,.8); display: flex; flex-direction: column; gap: 10px;
}
.cmp-modal-head {
  display: flex; justify-content: space-between; align-items: center;
  font-family: 'Pixel Intv', monospace; font-size: 13px; letter-spacing: 2px; color: var(--accent);
  border-bottom: 1px solid rgba(255,255,255,.12); padding-bottom: 8px;
}
.cmp-modal-foot { display: flex; justify-content: flex-end; gap: 10px; padding-top: 6px; }
.cmp-menu { width: 320px; }
.cmp-menu .btn { width: 100%; }
.cmp-log { width: min(760px, 88vw); max-height: 76vh; }
.cmp-log-list { overflow-y: auto; display: flex; flex-direction: column; gap: 7px; padding-right: 6px; }
.cmp-log-line { font-size: 14px; color: var(--text2); line-height: 1.5; }
.cmp-log-line b { color: var(--accent); }

/* ── Deck-Editor ─────────────────────────────────────────────────── */
.cmp-deck-editor { width: min(1000px, 94vw); height: min(760px, 90vh); }
.cmp-legal { font-family: 'Pixel Intv', monospace; font-size: 10px; }
.cmp-legal.ok  { color: var(--success); }
.cmp-legal.bad { color: var(--danger); }
.cmp-heroes { display: flex; gap: 10px; }
.cmp-hero-slot {
  flex: 1; height: 92px; border: 2px dashed var(--text2); border-radius: 8px;
  display: flex; align-items: center; gap: 8px; padding: 6px; cursor: pointer;
  background: rgba(255,255,255,.03); overflow: hidden;
}
.cmp-hero-slot.filled { border-style: solid; border-color: #ffd700; }
.cmp-hero-slot img { height: 78px; width: auto; image-rendering: pixelated; }
.cmp-hero-slot span { font-size: 12px; font-weight: 700; color: var(--text); }
.cmp-hero-empty { color: var(--text2); font-size: 11px; }
.cmp-deck-cols { display: flex; gap: 12px; flex: 1; min-height: 0; }
.cmp-deck-col { flex: 1; display: flex; flex-direction: column; min-height: 0; border: 1px solid rgba(255,255,255,.1); border-radius: 8px; overflow: hidden; }
.cmp-col-head {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  font-family: 'Pixel Intv', monospace; font-size: 11px; color: var(--accent);
  background: rgba(0,0,0,.35); padding: 8px 10px;
}
.cmp-col-sub { font-family: 'Pixel Intv', monospace; font-size: 10px; color: var(--text2); padding: 8px 4px 2px; }
.cmp-search { background: rgba(0,0,0,.4); border: 1px solid var(--text2); border-radius: 4px; color: var(--text); font-size: 11px; padding: 3px 7px; width: 120px; }
.cmp-card-list { flex: 1; overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 3px; }
/* Feste Spalten statt `space-between`: vorher richtete sich die
   Position der Zahlen nach der LÄNGE DES NAMENS (bei drei Elementen
   verteilt space-between den Rest gleichmäßig), lange Namen schoben
   Kosten und Anzahl also nach rechts. Jetzt bekommen beide Zahlen eine
   feste Breite und stehen rechtsbündig — damit fluchten sie über alle
   Zeilen. Der Name füllt den Rest und wird notfalls mit … gekürzt. */
.cmp-card-row {
  display: flex; align-items: center; gap: 8px;
  background: rgba(255,255,255,.04); border: none; border-left: 3px solid var(--accent);
  border-radius: 3px; padding: 6px 9px; cursor: pointer; text-align: left; color: var(--text); font-size: 12px;
}
.cmp-card-name {
  flex: 1 1 auto; min-width: 0; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmp-card-row:hover:not(.is-out) { background: rgba(0,240,255,.15); }
/* "0/x" bleibt überfahrbar, damit der Tooltip auch dort erscheint —
   nur klicken tut nichts (siehe Kommentar in addCard). */
.cmp-card-row.is-out { opacity: .42; cursor: default; border-left-color: var(--text2); }
.cmp-card-row.is-out:hover { background: rgba(255,255,255,.07); }
.cmp-card-count { color: var(--text2); font-size: 11px; flex: 0 0 46px; width: 46px; text-align: right; }
.cmp-empty { color: var(--text2); font-size: 11px; padding: 8px; }

/* ── Entwicklerhilfen ────────────────────────────────────────────── */
.cmp-dev {
  position: absolute; left: 16px; bottom: 16px; z-index: 280; width: 330px;
  background: rgba(6,6,14,.96); border: 2px solid #ff00c8; border-radius: 8px; padding: 10px;
  display: flex; flex-direction: column; gap: 7px; font-size: 12px; color: var(--text);
}
.cmp-dev-head { display: flex; justify-content: space-between; font-family: 'Pixel Intv', monospace; font-size: 11px; color: #ff00c8; letter-spacing: 2px; }
.cmp-dev-head button { background: none; border: none; color: var(--text2); cursor: pointer; }
.cmp-dev-row { display: flex; align-items: center; gap: 6px; }
.cmp-dev-row label { flex: 0 0 44px; color: var(--text2); font-size: 11px; }
.cmp-dev-row select, .cmp-dev-row input { flex: 1; background: rgba(0,0,0,.5); color: var(--text); border: 1px solid var(--text2); border-radius: 4px; font-size: 11px; padding: 3px 5px; }
.cmp-dev-row button { background: rgba(255,0,200,.18); border: 1px solid #ff00c8; color: var(--text); border-radius: 4px; font-size: 11px; padding: 3px 8px; cursor: pointer; }
.cmp-dev-danger { background: rgba(255,51,102,.2) !important; border-color: var(--danger) !important; }
.cmp-dev-flags { display: flex; flex-wrap: wrap; gap: 4px; max-height: 74px; overflow-y: auto; }
.cmp-dev-flags span { background: rgba(255,0,200,.16); border: 1px solid #ff00c8; border-radius: 3px; padding: 1px 6px; font-size: 10px; cursor: pointer; }
.cmp-dev-flags i { color: var(--text2); font-size: 10px; }
.cmp-dev-errors { background: rgba(255,51,102,.12); border: 1px solid var(--danger); border-radius: 5px; padding: 6px; max-height: 130px; overflow-y: auto; font-size: 10px; color: #ffb3c4; }

/* Waehrend einer Szene keine Erkundungsklicks durchlassen. */
.cmp-scene-guard { position: absolute; inset: 0; z-index: 110; }

/* ═══════════════════════════════════════════════════════════════════
   KAMPAGNE — ÜBERGANG INS DUELL UND KARTEN-AUFTRITT
   ══════════════════════════════════════════════════════════════════ */

/* ── Übergang ────────────────────────────────────────────────────
   Ablauf über 2,1 s: Blenden fahren zu (0–350 ms), Blitz, Portrait
   und Name stehen still (350–1400 ms), Blenden gehen auf (bis
   2100 ms). Das Duell wird bei 800 ms angefordert und baut sich
   hinter der geschlossenen Blende auf. */
.cmp-duel-intro { position: fixed; inset: 0; z-index: 20000; pointer-events: none; overflow: hidden; }

/* Die Blenden sind LEICHT GENEIGT (skewY). Dadurch wandern ihre Ecken
   um tan(2°) x halbe Breite nach oben bzw. unten — bei 1920 px sind das
   gut 35 px. Ohne Überstand blieb an der oberen Bildkante deshalb ein
   keilförmiger Streifen unbedeckt. `top`/`bottom` ziehen die Blenden
   darum um 8% über den Rand hinaus, und sie sind entsprechend höher. */
.cmp-duel-shutter {
  position: absolute; left: -6%; right: -6%; height: 60%;
  background: linear-gradient(180deg, #05050c 60%, #0d0d1e 100%);
  box-shadow: 0 0 40px rgba(0,0,0,.9);
}
.cmp-duel-shutter-top    { top: -8%;    animation: cmp-shutter-top    2.1s cubic-bezier(.16,.9,.3,1) forwards; }
.cmp-duel-shutter-bottom { bottom: -8%; animation: cmp-shutter-bottom 2.1s cubic-bezier(.16,.9,.3,1) forwards; }
@keyframes cmp-shutter-top {
  0% { transform: translateY(-110%) skewY(-2deg); }
  17%, 66% { transform: translateY(0) skewY(-2deg); }
  100% { transform: translateY(-110%) skewY(-2deg); }
}
@keyframes cmp-shutter-bottom {
  0% { transform: translateY(110%) skewY(-2deg); }
  17%, 66% { transform: translateY(0) skewY(-2deg); }
  100% { transform: translateY(110%) skewY(-2deg); }
}

.cmp-duel-flash {
  position: absolute; inset: 0; background: #fff; opacity: 0;
  animation: cmp-duel-flash 2.1s ease-out forwards;
}
@keyframes cmp-duel-flash {
  0% { opacity: 0; } 14% { opacity: .85; } 30% { opacity: 0; } 100% { opacity: 0; }
}

.cmp-duel-core {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px; opacity: 0;
  animation: cmp-duel-core 2.1s ease-out forwards;
}
@keyframes cmp-duel-core {
  0%, 12% { opacity: 0; transform: scale(.82); }
  24% { opacity: 1; transform: scale(1.04); }
  30%, 62% { opacity: 1; transform: scale(1); }
  74%, 100% { opacity: 0; transform: scale(1.08); }
}
.cmp-duel-core img {
  image-rendering: pixelated; image-rendering: crisp-edges;
  filter: drop-shadow(0 0 26px rgba(0,240,255,.55));
}
.cmp-duel-word {
  font-family: 'Pixel Intv', monospace; font-size: 46px; letter-spacing: 14px;
  color: #fff; text-shadow: 0 0 26px rgba(0,240,255,.85), 0 4px 0 #000;
  margin-top: 10px;
}
.cmp-duel-name {
  font-family: 'Pixel Intv', monospace; font-size: 20px; letter-spacing: 3px;
  color: var(--accent); text-shadow: 0 2px 0 #000;
}

/* ── Karten-Auftritt ─────────────────────────────────────────────
   Läuft mit der Siegesfanfare; Klick springt zur nächsten Karte. */
.cmp-card-reveal {
  position: fixed; inset: 0; z-index: 21000; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 50% 45%, rgba(255,215,0,.16), rgba(0,0,0,.86) 62%);
  animation: cmp-fade-in .25s ease;
}
@keyframes cmp-fade-in { from { opacity: 0; } to { opacity: 1; } }
.cmp-card-reveal-inner {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  animation: cmp-card-pop .55s cubic-bezier(.2,1.5,.4,1);
}
@keyframes cmp-card-pop {
  0% { opacity: 0; transform: scale(.35) rotate(-9deg); }
  70% { opacity: 1; transform: scale(1.06) rotate(1.5deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}
.cmp-card-reveal-title {
  font-family: 'Pixel Intv', monospace; font-size: 18px; letter-spacing: 6px;
  color: #ffd700; text-shadow: 0 0 22px rgba(255,215,0,.65), 0 3px 0 #000;
}
.cmp-card-reveal-card { filter: drop-shadow(0 0 46px rgba(255,215,0,.55)); }
.cmp-card-reveal-name { font-size: 18px; color: #fff; text-shadow: 0 2px 6px #000; }
.cmp-card-reveal-count { font-family: 'Pixel Intv', monospace; font-size: 11px; color: var(--text2); }

/* ── Kampagnen-Portrait im Kampffeld ─────────────────────────────
   Die Portraits sind Pixelart; der runde Avatar-Rahmen des Spielfelds
   würde sie sonst weichzeichnen. */
.game-hand-avatar[src^="/campaign/avatars/"] {
  image-rendering: pixelated; image-rendering: crisp-edges;
  object-fit: contain; background: rgba(8,8,18,.85);
}

/* Auswahl kurz nach dem Erscheinen gesperrt (siehe CMP_CHOICE_LOCK):
   ein nachlaufender Klick aus der Dialogbox soll nicht ungelesen die
   erste Möglichkeit wählen. Sichtbar nur als kurzes Abdunkeln. */
.cmp-choice-locked { opacity: .55; cursor: default; transform: none !important; }
.cmp-choice-locked:hover { background: linear-gradient(180deg, rgba(18,18,36,.96), rgba(10,10,22,.96)); transform: none; }

/* ═══════════════════════════════════════════════════════════════════
   KAMPAGNE — ANTE (Kartensatz)
   Liegt über dem Sieg-/Niederlage-Fenster des Spielfelds (z-index
   dort ~1000, Karten-Auftritt 21000) — das Ante muss abgeschlossen
   sein, bevor die Story weitergeht.
   ══════════════════════════════════════════════════════════════════ */

/* Der Grund ist BEWUSST undurchsichtig. Vorher schimmerte das
   Sieg-/Niederlage-Fenster des Spielfelds samt "CONTINUE" und der
   Kopfleiste durch — zwei konkurrierende Handlungsaufforderungen auf
   einem Bild. Jetzt gilt nur noch die Ante-Wahl. */
.cmp-ante {
  position: fixed; inset: 0; z-index: 22000;
  display: flex; flex-direction: column; gap: 14px; padding: 22px 3vw;
  background-color: #05050c;
  background-image: radial-gradient(circle at 50% 22%, rgba(255,215,0,.16), rgba(255,215,0,0) 58%);
  animation: cmp-fade-in .3s ease;
}
.cmp-ante-head { text-align: center; }
.cmp-ante-title {
  font-family: 'Pixel Intv', monospace; font-size: 22px; letter-spacing: 6px; color: #ffd700;
  text-shadow: 0 0 24px rgba(255,215,0,.6), 0 3px 0 #000;
}
.cmp-ante-sub { font-size: 13px; color: var(--text2); margin-top: 6px; }

/* Raster und Tooltip-Spalte nebeneinander; die Spalte ist immer da,
   damit das Raster beim Überfahren nicht springt. Links steht ein
   gleich breiter Platzhalter (::before), damit die Karten nicht nur
   im Restplatz, sondern auf dem BILDSCHIRM mittig sitzen. */
.cmp-ante-body { flex: 1; min-height: 0; display: flex; gap: 16px; }
.cmp-ante-body::before { content: ''; flex: 0 0 340px; }

/* Flexbox statt Grid: `repeat(auto-fill, 140px)` legt so viele Spalten
   an, wie hineinpassen, füllt also die volle Breite mit LEEREN Spalten
   — `justify-content: center` hatte damit nichts zu zentrieren, und
   vier Karten klebten am linken Rand. Umbrechendes Flexbox zentriert
   dagegen genau das, was da ist. */
.cmp-ante-grid {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-wrap: wrap;
  justify-content: center; align-content: flex-start;
  gap: 12px; padding: 6px 4px 10px;
}
.cmp-ante-card { flex: 0 0 140px; width: 140px; }

.cmp-ante-side {
  flex: 0 0 340px; width: 340px; overflow-y: auto;
  background: var(--bg2, #14142a); border: 1px solid var(--accent); border-radius: 10px;
  display: flex; flex-direction: column;
}
.cmp-ante-side-empty {
  margin: auto; text-align: center; font-family: 'Pixel Intv', monospace;
  font-size: 12px; line-height: 1.8; color: var(--text2);
}
@media (max-width: 1100px) {
  .cmp-ante-side { flex-basis: 260px; width: 260px; }
}
.cmp-ante-card {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  background: rgba(255,255,255,.04); border: 2px solid transparent; border-radius: 10px;
  padding: 8px 6px; cursor: pointer; transition: transform .1s, border-color .15s, background .15s;
}
.cmp-ante-card:hover { background: rgba(255,215,0,.12); border-color: rgba(255,215,0,.5); transform: translateY(-3px); }
.cmp-ante-card.is-sel {
  border-color: #ffd700; background: rgba(255,215,0,.2);
  box-shadow: 0 0 26px rgba(255,215,0,.4); transform: translateY(-3px);
}
.cmp-ante-name { font-size: 11px; font-weight: 700; color: var(--text); text-align: center; line-height: 1.25; }
.cmp-ante-foot {
  display: flex; justify-content: center; flex: 0 0 auto;
  padding-top: 4px; position: relative; z-index: 2;
}
.cmp-ante-foot .btn {
  padding: 14px 40px; font-size: 15px; letter-spacing: 1px;
  box-shadow: 0 0 26px rgba(0,0,0,.8);
}
.cmp-ante-foot .btn:not(:disabled) { box-shadow: 0 0 26px rgba(51,255,136,.35); }
@media (max-width: 1100px) { .cmp-ante-body::before { flex-basis: 260px; } }

.cmp-ante-result {
  position: fixed; inset: 0; z-index: 22000; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  animation: cmp-fade-in .3s ease;
}
.cmp-ante-result.is-win  { background: radial-gradient(circle at 50% 45%, rgba(255,215,0,.2), rgba(0,0,0,.9) 62%); }
.cmp-ante-result.is-loss { background: radial-gradient(circle at 50% 45%, rgba(255,51,102,.18), rgba(0,0,0,.92) 62%); }
.cmp-ante-result-inner {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  animation: cmp-card-pop .55s cubic-bezier(.2,1.5,.4,1);
}
.cmp-ante-result-title {
  font-family: 'Pixel Intv', monospace; font-size: 20px; letter-spacing: 6px;
  text-shadow: 0 3px 0 #000;
}
.is-win  .cmp-ante-result-title { color: #ffd700; text-shadow: 0 0 24px rgba(255,215,0,.6), 0 3px 0 #000; }
.is-loss .cmp-ante-result-title { color: var(--danger); text-shadow: 0 0 24px rgba(255,51,102,.6), 0 3px 0 #000; }
.is-win  .cmp-ante-result-card { filter: drop-shadow(0 0 46px rgba(255,215,0,.55)); }
.is-loss .cmp-ante-result-card { filter: drop-shadow(0 0 46px rgba(255,51,102,.5)) grayscale(.45); }
.cmp-ante-result-name { font-size: 18px; color: #fff; text-shadow: 0 2px 6px #000; }
.cmp-ante-result-note { font-size: 13px; color: var(--text2); max-width: 460px; text-align: center; }
.cmp-ante-result-hint { font-size: 12px; color: var(--text2); animation: pulse 1.6s infinite; }

/* Ante im Übergang ankündigen */
.cmp-duel-word-ante { color: #ffd700; text-shadow: 0 0 26px rgba(255,215,0,.9), 0 4px 0 #000; }
.cmp-duel-stake { font-size: 13px; color: #ffd700; letter-spacing: 1px; text-shadow: 0 2px 0 #000; }

/* Warnung, solange das Deck unvollständig ist */
.cmp-deck-warn {
  font-family: 'Pixel Intv', monospace; font-size: 12px; color: #0a0a14;
  background: var(--danger); border-radius: 6px; padding: 6px 12px;
  animation: pulse 1.8s infinite; letter-spacing: 1px;
}

/* ── Ortsgebundene Aktionen (ohne Koordinaten) ───────────────────── */
.cmp-actions {
  position: absolute; left: 16px; top: 112px; z-index: 125;
  display: flex; flex-direction: column; gap: 10px; align-items: stretch; width: 300px; max-width: 34vw;
}
.cmp-action {
  font-family: 'Pixel Intv', monospace; font-size: 14px; line-height: 1.4;
  color: var(--text); text-align: left;
  background: linear-gradient(180deg, rgba(20,20,40,.95), rgba(10,10,22,.95));
  border: 2px solid var(--accent); border-left-width: 7px; border-radius: 9px;
  padding: 15px 18px; cursor: pointer;
  box-shadow: 0 0 16px rgba(0,240,255,.18);
  transition: background .15s, transform .1s;
}
.cmp-action:hover { background: rgba(0,240,255,.18); transform: translateX(5px); }

/* ── Deck-Editor: Vorschauspalte, rote Zähler, fliegende Karten ──── */
.cmp-deck-editor { width: min(1320px, 96vw); }
.cmp-deck-side {
  flex: 0 0 300px; width: 300px; overflow-y: auto;
  background: var(--bg2, #14142a); border: 1px solid var(--accent); border-radius: 8px;
  display: flex; flex-direction: column;
}
@media (max-width: 1200px) { .cmp-deck-side { flex-basis: 240px; width: 240px; } }

/* Zahl wird rot, sobald die Anzahl nicht erlaubt ist (61/60, 3 Tränke …) */
.cmp-count.is-bad { color: var(--danger); font-weight: 700; animation: pulse 1.6s infinite; }
.cmp-col-sub-hint { color: var(--text2); font-size: 9px; }

/* Fliegende Miniatur: startet an der angeklickten Zeile und zieht zur
   Zielspalte. Rein dekorativ, blockiert nichts. */
.cmp-flight {
  position: fixed; z-index: 26000; pointer-events: none;
  width: 64px; margin: 0; transform: translate(-50%, -50%);
  animation: cmp-flight .68s cubic-bezier(.34,.7,.3,1) forwards;
}
.cmp-flight img {
  width: 100%; border-radius: 4px; display: block;
  box-shadow: 0 0 18px rgba(0,240,255,.55), 0 4px 10px rgba(0,0,0,.7);
}
@keyframes cmp-flight {
  0%   { transform: translate(-50%, -50%) scale(.85) rotate(-6deg); opacity: 0; }
  18%  { transform: translate(-50%, -50%) scale(1.08) rotate(0deg); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--fx)), calc(-50% + var(--fy))) scale(.45) rotate(8deg); opacity: 0; }
}

/* ── Deck-Editor: Filter- und Sortierleiste ──────────────────────── */
.cmp-filters {
  display: flex; flex-wrap: wrap; gap: 5px;
  padding: 6px 8px; background: rgba(0,0,0,.28);
  border-bottom: 1px solid rgba(255,255,255,.08);
}
.cmp-filter {
  flex: 1 1 auto; min-width: 92px; max-width: 100%;
  background: rgba(0,0,0,.45); color: var(--text);
  border: 1px solid var(--text2); border-radius: 4px;
  font-size: 11px; padding: 4px 6px; cursor: pointer;
}
.cmp-filter:hover:not(:disabled) { border-color: var(--accent); }
.cmp-filter:disabled { opacity: .4; cursor: default; }
.cmp-sort-dir { flex: 0 0 auto; min-width: 72px; font-family: 'Pixel Intv', monospace; font-size: 10px; }
.cmp-filter-clear { flex: 0 0 auto; min-width: 62px; border-color: var(--danger); color: var(--danger); font-size: 10px; }
.cmp-filter-clear:hover { background: rgba(255,51,102,.15); }

/* Der Wert, nach dem gerade sortiert wird, steht rechts an der Zeile. */
.cmp-card-sortval {
  flex: 0 0 40px; width: 40px; text-align: right;
  font-family: 'Pixel Intv', monospace; font-size: 10px; color: var(--accent);
}

/* Sammlungs-Kopfzeile: "verschiedene | Exemplare" braucht mehr Platz
   als die reine Deckzahl und darf nicht umbrechen. */
.cmp-count-wide { white-space: nowrap; letter-spacing: .5px; }
