← Deckbuilder results

Deckbuilder

Qwen 3.6 27b MTP · composer

Initial view of Deckbuilder

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 56,414 bytes · SHA-256 73323c3bb152
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shadows of Aethelgard</title>
<style>
/* ── css: base ── */
* { margin: 0; padding: 0; box-sizing: border-box; }

@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700;900&family=Raleway:wght@300;400;600&display=swap');

body {
  background: #0a0a12;
  color: #d4c5a9;
  font-family: 'Raleway', sans-serif;
  overflow: hidden;
  height: 100vh;
  width: 100vw;
}

/* Atmospheric background */
body::before {
  content: '';
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: 
    radial-gradient(ellipse at 50% 30%, rgba(40, 20, 60, 0.4) 0%, transparent 70%),
    radial-gradient(ellipse at 20% 80%, rgba(20, 10, 40, 0.3) 0%, transparent 50%),
    radial-gradient(ellipse at 80% 60%, rgba(30, 15, 50, 0.2) 0%, transparent 50%),
    linear-gradient(180deg, #0a0a14 0%, #0d0b18 50%, #0a0a12 100%);
  z-index: -1;
}

/* Floating particles */
body::after {
  content: '';
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background-image: 
    radial-gradient(1px 1px at 20% 30%, rgba(201, 168, 76, 0.15), transparent),
    radial-gradient(1px 1px at 40% 70%, rgba(201, 168, 76, 0.1), transparent),
    radial-gradient(1px 1px at 60% 20%, rgba(201, 168, 76, 0.12), transparent),
    radial-gradient(1px 1px at 80% 50%, rgba(201, 168, 76, 0.08), transparent),
    radial-gradient(1px 1px at 30% 90%, rgba(201, 168, 76, 0.1), transparent);
  z-index: -1;
  animation: particleDrift 20s linear infinite;
}

@keyframes particleDrift {
  0% { transform: translateY(0); }
  100% { transform: translateY(-20px); }
}

.screen {
  display: none;
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.screen.active { display: flex; }

.game-title {
  font-family: 'Cinzel', serif;
  font-size: clamp(1.8em, 4vw, 3em);
  color: #c9a84c;
  text-shadow: 0 0 20px rgba(201, 168, 76, 0.5), 0 2px 4px rgba(0,0,0,0.8);
  margin-bottom: 10px;
  letter-spacing: 3px;
}

.subtitle {
  font-size: clamp(0.8em, 1.5vw, 1.1em);
  color: #8a7e6b;
  margin-bottom: 40px;
  font-style: italic;
}

.action-btn {
  background: linear-gradient(180deg, #3a2f25 0%, #1a1510 100%);
  border: 2px solid #6b5d45;
  color: #c9a84c;
  font-family: 'Cinzel', serif;
  font-size: clamp(0.8em, 1.5vw, 1.1em);
  padding: 12px 32px;
  cursor: pointer;
  transition: all 0.2s;
  text-transform: uppercase;
  letter-spacing: 2px;
}

.action-btn:hover {
  background: linear-gradient(180deg, #4a3f35 0%, #2a2520 100%);
  border-color: #c9a84c;
  box-shadow: 0 0 15px rgba(201, 168, 76, 0.3);
}

.action-btn:active { transform: scale(0.97); }
/* ── css: class-select ── */
#class-cards {
  display: flex;
  gap: clamp(12px, 3vw, 30px);
  justify-content: center;
  flex-wrap: wrap;
}

.class-card {
  width: clamp(200px, 25vw, 260px);
  background: linear-gradient(180deg, #1a1520 0%, #0d0b14 100%);
  border: 2px solid #3a3040;
  padding: clamp(16px, 2vw, 24px);
  cursor: pointer;
  transition: all 0.3s;
  text-align: center;
  position: relative;
}

.class-card:hover {
  transform: translateY(-8px);
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}

.class-card.bastion:hover { border-color: #7a6b4f; box-shadow: 0 10px 30px rgba(122, 107, 79, 0.3); }
.class-card.wither:hover { border-color: #8b4513; box-shadow: 0 10px 30px rgba(139, 69, 19, 0.3); }
.class-card.shroud:hover { border-color: #4a3d7a; box-shadow: 0 10px 30px rgba(74, 61, 122, 0.3); }

.class-icon { font-size: clamp(2em, 5vw, 3em); margin-bottom: 8px; }
.class-name { font-family: 'Cinzel', serif; font-size: clamp(1em, 2vw, 1.5em); color: #c9a84c; margin-bottom: 4px; }
.class-theme { font-size: clamp(0.6em, 1vw, 0.85em); color: #7a6e5b; font-style: italic; margin-bottom: 8px; }
.class-desc { font-size: clamp(0.6em, 1vw, 0.85em); line-height: 1.4; color: #9a8e7b; }

.class-stats {
  display: flex;
  justify-content: space-around;
  margin-top: clamp(8px, 1.5vw, 16px);
  padding-top: clamp(8px, 1vw, 12px);
  border-top: 1px solid #2a2530;
}

.class-stat { text-align: center; }
.class-stat-val { font-family: 'Cinzel', serif; font-size: clamp(0.9em, 1.5vw, 1.2em); color: #c9a84c; }
.class-stat-label { font-size: clamp(0.5em, 0.7vw, 0.7em); color: #6a5e4b; text-transform: uppercase; }

/* Card back preview on class cards */
.card-back-preview {
  position: absolute;
  top: -10px;
  right: -10px;
  width: clamp(40px, 5vw, 60px);
  height: clamp(57px, 7vh, 84px);
  opacity: 0.3;
  transform: rotate(12deg);
  pointer-events: none;
}

.card-back-preview svg { width: 100%; height: 100%; }
/* ── css: combat-layout ── */
#screen-combat { justify-content: flex-start; padding: 0; }

#combat-hud-top {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 20px;
  background: linear-gradient(180deg, rgba(26, 21, 32, 0.95) 0%, transparent);
  font-family: 'Cinzel', serif;
  color: #c9a84c;
  z-index: 10;
  font-size: clamp(0.7em, 1.2vw, 0.95em);
}

#combat-arena {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 5px 20px 15px;
  position: relative;
}

/* Divider line between enemy and player */
#combat-arena::before {
  content: '';
  position: absolute;
  left: 10%; right: 10%;
  top: 48%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(201, 168, 76, 0.2), transparent);
}

#enemy-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 5px;
  position: relative;
}

#enemy-display {
  width: clamp(140px, 20vw, 200px);
  height: clamp(160px, 25vh, 220px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
}

#enemy-name {
  font-family: 'Cinzel', serif;
  color: #c9a84c;
  margin-bottom: 6px;
  font-size: clamp(0.7em, 1.2vw, 1em);
  text-shadow: 0 0 10px rgba(201, 168, 76, 0.3);
}

.enemy-sprite {
  font-size: clamp(3em, 8vw, 5em);
  filter: drop-shadow(0 0 15px rgba(139, 69, 19, 0.4));
  transition: transform 0.2s;
}

.enemy-sprite.hit { animation: enemyHit 0.3s ease-out; }

@keyframes enemyHit {
  0% { transform: scale(1); filter: brightness(3) drop-shadow(0 0 15px red); }
  25% { transform: translateX(-6px) scale(0.95); }
  50% { transform: translateX(4px) scale(0.97); }
  100% { transform: translateX(0) scale(1); filter: none; }
}

#enemy-hp-bar-container {
  width: clamp(120px, 18vw, 200px);
  height: 14px;
  background: #1a1520;
  border: 1px solid #3a3040;
  margin-top: 6px;
  position: relative;
  overflow: hidden;
}

#enemy-hp-bar {
  height: 100%;
  background: linear-gradient(90deg, #8b2500, #cc3300);
  transition: width 0.4s ease-out;
  box-shadow: inset 0 -2px 4px rgba(0,0,0,0.3), 0 0 6px rgba(204, 51, 0, 0.3);
}

#enemy-hp-text {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  font-family: 'Cinzel', serif;
  font-size: 0.6em;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,0.8);
}

#enemy-status-effects {
  display: flex;
  gap: 4px;
  margin-top: 4px;
  min-height: 18px;
  flex-wrap: wrap;
  justify-content: center;
}

.status-badge {
  padding: 2px 6px;
  font-size: 0.6em;
  border-radius: 3px;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.status-burn { background: #4a2500; color: #ff9900; border: 1px solid #6b3d00; }
.status-poison { background: #0a2a0a; color: #66cc66; border: 1px solid #1a4a1a; }
.status-stun { background: #2a2a0a; color: #cccc00; border: 1px solid #4a4a1a; }

#battle-log {
  width: min(90%, 600px);
  height: clamp(50px, 8vh, 80px);
  overflow-y: auto;
  background: rgba(10, 10, 18, 0.7);
  border: 1px solid #2a2530;
  padding: 6px 10px;
  font-size: clamp(0.6em, 1vw, 0.75em);
  color: #8a7e6b;
  margin: 4px 0;
}

#battle-log .log-entry { padding: 2px 0; border-bottom: 1px solid rgba(42, 37, 48, 0.5); }
#battle-log .log-damage { color: #cc6644; }
#battle-log .log-defense { color: #6699cc; }
#battle-log .log-heal { color: #66cc66; }
#battle-log .log-gold { color: #c9a84c; }

#player-area {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

#player-stats {
  display: flex;
  gap: clamp(12px, 3vw, 24px);
  margin-bottom: 6px;
  font-family: 'Cinzel', serif;
  font-size: clamp(0.7em, 1.2vw, 0.95em);
}

.stat-icon { margin-right: 4px; }
.hp-icon { color: #cc3300; }
.energy-icon { color: #6699ff; }
.def-icon { color: #88aacc; }

#hand-container {
  display: flex;
  gap: clamp(6px, 1.5vw, 12px);
  justify-content: center;
  align-items: flex-end;
  min-height: clamp(140px, 25vh, 200px);
  padding: 8px;
  perspective: 800px;
  width: 100%;
  overflow-x: auto;
}

#combat-controls { margin-top: 6px; }

/* Turn indicator */
.turn-indicator {
  font-family: 'Cinzel', serif;
  color: #c9a84c;
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 2px;
  opacity: 0.6;
}

/* Player hit flash */
@keyframes playerHitFlash {
  0%, 100% { background: transparent; }
  50% { background: rgba(255, 0, 0, 0.1); }
}

.player-hit-flash { animation: playerHitFlash 0.3s ease-out; }
/* ── css: cards ── */
.game-card {
  width: clamp(100px, 14vw, 140px);
  height: clamp(145px, 20vh, 200px);
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s ease-out;
  position: relative;
  flex-shrink: 0;
  transform-origin: bottom center;
}

.game-card:hover {
  transform: translateY(-16px) scale(1.05);
  z-index: 100;
}

.game-card.playable { cursor: pointer; }
.game-card.unplayable { opacity: 0.45; cursor: not-allowed; filter: grayscale(0.6); }
.game-card.unplayable:hover { transform: none; }

.card-inner {
  width: 100%;
  height: 100%;
  border-radius: 8px;
  overflow: hidden;
  position: relative;
}

.card-cost {
  position: absolute;
  top: 3px;
  left: 3px;
  width: clamp(20px, 3vw, 26px);
  height: clamp(20px, 3vw, 26px);
  background: #1a3a5c;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Cinzel', serif;
  font-weight: 900;
  font-size: clamp(0.6em, 1vw, 0.85em);
  color: #66ccff;
  z-index: 2;
  border: 1px solid #3a6a9c;
}

.card-type-badge {
  position: absolute;
  top: 3px;
  right: 3px;
  font-size: clamp(0.4em, 0.7vw, 0.55em);
  padding: 1px 4px;
  border-radius: 3px;
  text-transform: uppercase;
  letter-spacing: 1px;
  z-index: 2;
}

.card-type-attack { background: #4a1a00; color: #ff6633; border: 1px solid #7a3a00; }
.card-type-defense { background: #0a1a3a; color: #6699cc; border: 1px solid #1a3a5c; }
.card-type-other { background: #2a0a3a; color: #aa66ff; border: 1px solid #4a1a5c; }

.card-image-area {
  position: absolute;
  top: clamp(28px, 5vh, 34px);
  left: 6px;
  right: 6px;
  height: clamp(50px, 10vh, 70px);
  display: flex;
  align-items: center;
  justify-content: center;
}

.card-image-area .card-emoji { font-size: clamp(1.8em, 4vw, 2.5em); }

.card-name {
  position: absolute;
  top: clamp(80px, 16vh, 110px);
  left: 5px;
  right: 5px;
  text-align: center;
  font-family: 'Cinzel', serif;
  font-size: clamp(0.5em, 0.9vw, 0.7em);
  color: #e8dcc8;
  line-height: 1.2;
}

.card-desc {
  position: absolute;
  bottom: 4px;
  left: 5px;
  right: 5px;
  text-align: center;
  font-size: clamp(0.4em, 0.7vw, 0.55em);
  color: #9a8e7b;
  line-height: 1.3;
}

/* Card frame styles per class */
.card-frame-bastion { border: 2px solid #6b5d45; box-shadow: 0 2px 8px rgba(107, 93, 69, 0.3); }
.card-frame-wither { border: 2px solid #6b3a1f; box-shadow: 0 2px 8px rgba(107, 58, 31, 0.3); }
.card-frame-shroud { border: 2px solid #4a3d7a; box-shadow: 0 2px 8px rgba(74, 61, 122, 0.3); }

/* Playable card glow */
.game-card.playable:hover .card-inner::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  border-radius: 6px;
  pointer-events: none;
}

.card-frame-bastion.playable:hover .card-inner::after { box-shadow: inset 0 0 15px rgba(201, 168, 76, 0.3); }
.card-frame-wither.playable:hover .card-inner::after { box-shadow: inset 0 0 15px rgba(204, 102, 51, 0.3); }
.card-frame-shroud.playable:hover .card-inner::after { box-shadow: inset 0 0 15px rgba(136, 102, 204, 0.3); }

/* Shop */
#screen-shop { justify-content: flex-start; padding-top: 20px; }
#screen-shop h2 { font-family: 'Cinzel', serif; color: #c9a84c; margin-bottom: 10px; font-size: clamp(1.2em, 2.5vw, 1.8em); }
#shop-gold-display { font-size: clamp(0.9em, 1.5vw, 1.2em); color: #c9a84c; margin-bottom: 16px; }

#shop-cards {
  display: flex;
  gap: clamp(8px, 2vw, 16px);
  justify-content: center;
  flex-wrap: wrap;
  max-width: min(95%, 700px);
}

.shop-card-wrapper { text-align: center; }

.shop-card-price {
  font-family: 'Cinzel', serif;
  color: #c9a84c;
  margin-top: 4px;
  font-size: clamp(0.6em, 1vw, 0.85em);
}

.shop-card-count { font-size: clamp(0.5em, 0.8vw, 0.7em); color: #6a5e4b; }

.shop-buy-btn {
  background: linear-gradient(180deg, #2a3f25 0%, #1a2510 100%);
  border: 1px solid #4a7d35;
  color: #66cc66;
  font-family: 'Cinzel', serif;
  font-size: clamp(0.6em, 0.9vw, 0.8em);
  padding: 3px 10px;
  cursor: pointer;
  margin-top: 3px;
}

.shop-buy-btn:hover { border-color: #66cc66; box-shadow: 0 0 10px rgba(102, 204, 102, 0.3); }
.shop-buy-btn.cant-afford { opacity: 0.4; cursor: not-allowed; border-color: #3a3530; color: #6a5e4b; }

/* Floor clear / Game over */
#screen-floor-clear h2, #screen-game-over h2, #screen-victory h2 {
  font-family: 'Cinzel', serif;
  color: #c9a84c;
  font-size: clamp(1.5em, 3vw, 2.5em);
  margin-bottom: 16px;
}

#screen-floor-clear p, #screen-game-over p, #screen-victory p {
  font-size: clamp(0.8em, 1.5vw, 1.1em);
  color: #8a7e6b;
  margin-bottom: 30px;
}

/* Card play animation */
@keyframes cardPlayFlash {
  0% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.5; transform: scale(1.1); }
  100% { opacity: 0; transform: scale(0.8) translateY(-40px); }
}

.card-playing { animation: cardPlayFlash 0.3s ease-out forwards; pointer-events: none; }

/* Damage number popup */
.damage-popup {
  position: absolute;
  font-family: 'Cinzel', serif;
  font-weight: 900;
  font-size: clamp(1em, 2vw, 1.5em);
  color: #ff4422;
  text-shadow: 0 0 6px rgba(255, 68, 34, 0.5);
  pointer-events: none;
  animation: damageFloat 0.8s ease-out forwards;
}

@keyframes damageFloat {
  0% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-40px) scale(0.7); }
}
/* ── css: shop-buttons ── */
#screen-shop .action-btn { margin-top: 20px; }
#btn-skip-shop { opacity: 0.7; font-size: 0.85em !important; padding: 8px 20px !important; }
#btn-skip-shop:hover { opacity: 1; }

/* Scrollbar styling */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #0a0a12; }
::-webkit-scrollbar-thumb { background: #3a3040; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #5a4d60; }

/* Deck info in HUD */
#hud-deck-info {
  font-size: clamp(0.5em, 0.8vw, 0.7em);
  color: #6a5e4b;
}

/* End turn button styling */
#btn-end-turn {
  min-width: clamp(120px, 15vw, 180px);
  font-size: clamp(0.7em, 1.2vw, 0.95em) !important;
}

/* Player stats glow effects */
#hp-val { color: #cc3300; }
#energy-val { color: #6699ff; }
#def-val { color: #88aacc; }

/* Floor/room info styling */
#hud-floor-info span { color: #e8dcc8; }
</style>
</head>
<body>
<div id="screen-class-select" class="screen active">
  <h1 class="game-title">Shadows of Aethelgard</h1>
  <p class="subtitle">Choose your champion to descend into the Void...</p>
  <div id="class-cards"></div>
</div>

<div id="screen-combat" class="screen">
  <div id="combat-hud-top">
    <div id="hud-floor-info">Floor <span id="floor-num">1</span> · Room <span id="room-num">1</span></div>
    <div id="hud-gold">Gold: <span id="gold-val">0</span></div>
  <div id="hud-deck-info">Deck: <span id="deck-count">0</span></div></div>

  <div id="combat-arena">
    <div id="enemy-area">
      <div id="enemy-display"></div>
      <div id="enemy-hp-bar-container"><div id="enemy-hp-bar"></div></div>
      <div id="enemy-status-effects"></div>
    </div>

    <div id="battle-log"></div>

    <div id="player-area">
      <div id="player-stats">
        <div id="hp-display"><span class="stat-icon hp-icon">♥</span> <span id="hp-val">30</span>/<span id="max-hp-val">30</span></div>
        <div id="energy-display"><span class="stat-icon energy-icon">⚡</span> <span id="energy-val">5</span>/<span id="max-energy-val">5</span></div>
        <div id="defense-display"><span class="stat-icon def-icon">🛡</span> <span id="def-val">0</span></div>
      </div>

      <div id="hand-container"></div>

      <div id="combat-controls">
        <button id="btn-end-turn" class="action-btn">End Turn</button>
      </div>
    </div>
  </div>
</div>

<div id="screen-shop" class="screen">
  <h2>The Merchant's Stall</h2>
  <p id="shop-gold-display">Gold: <span id="shop-gold-val">0</span></p>
  <div id="shop-cards"></div>
  <button id="btn-skip-shop" class="action-btn" style="margin-right:10px; font-size:0.8em;">Skip Shop</button><button id="btn-leave-shop" class="action-btn">Next Room →</button>
</div>

<div id="screen-floor-clear" class="screen">
  <h2>Floor Cleared!</h2>
  <p id="floor-clear-text"></p>
  <button id="btn-next-floor" class="action-btn">Descend Deeper</button>
</div>

<div id="screen-game-over" class="screen">
  <h2 id="game-over-title">Game Over</h2>
  <p id="game-over-text"></p>
  <button id="btn-restart" class="action-btn">Return to Main Menu</button>
</div>

<div id="screen-victory" class="screen">
  <h2>Victory!</h2>
  <p>The Void has been vanquished. Aethelgard is saved.</p>
  <button id="btn-play-again" class="action-btn">Play Again</button>
</div>
<script>
'use strict';

// ── const: CARD_DATA ──
const CARD_DATA = {
  // BASTION CARDS
  'BA-A01': { id:'BA-A01', name:'Slam', type:'attack', cost:2, atk:3, def:0, desc:'Deals 3 damage. If you have ≥30 HP, draw 1.', emoji:'🪨' },
  'BA-A02': { id:'BA-A02', name:'Earthquake', type:'attack', cost:3, atk:4, def:0, desc:'Deals 4 damage to all enemies.', aoe:true, emoji:'💥' },
  'BA-A03': { id:'BA-A03', name:'Stone Fist', type:'attack', cost:1, atk:2, def:0, desc:'Deals 2 damage.', emoji:'👊' },
  'BA-A04': { id:'BA-A04', name:'Boulder', type:'attack', cost:4, atk:6, def:0, desc:'Deals 6 damage.', emoji:'⛰️' },
  'BA-D01': { id:'BA-D01', name:'Shield Wall', type:'defense', cost:0, atk:0, def:4, desc:'Gain 4 Defense this turn.', emoji:'🛡️' },
  'BA-D02': { id:'BA-D02', name:'Iron Skin', type:'defense', cost:2, atk:0, def:5, desc:'Gain 5 permanent Defense. Discard 1.', permDef:true, discardAfter:true, emoji:'🔩' },
  'BA-D03': { id:'BA-D03', name:'Stand Firm', type:'defense', cost:1, atk:0, def:2, desc:'Gain 2 Defense. Blocks 1 incoming.', blockNext:true, emoji:'⚔️' },
  'BA-D04': { id:'BA-D04', name:'Taunt', type:'defense', cost:2, atk:0, def:3, desc:'Gain 3 Defense. Stun 1 enemy.', stunEnemy:true, emoji:'📢' },
  'BA-U01': { id:'BA-U01', name:'Search', type:'other', cost:2, atk:0, def:0, desc:'Draw 1 card. Discard if >5.', drawCard:true, discardIfOver5:true, emoji:'🔍' },
  'BA-U02': { id:'BA-U02', name:'Repair', type:'other', cost:3, atk:0, def:0, desc:'Heal 6 HP.', heal:6, emoji:'❤️‍🩹' },
  'BA-U03': { id:'BA-U03', name:'Rally', type:'other', cost:1, atk:0, def:0, desc:'Draw 1 card. Gain 1 Energy.', drawCard:true, gainEnergy:1, emoji:'📯' },
  'BA-U04': { id:'BA-U04', name:'Loot', type:'other', cost:2, atk:0, def:0, desc:'Gain 5 Gold.', goldGain:5, emoji:'💰' },

  // WITHER CARDS
  'WI-A01': { id:'WI-A01', name:'Fireball', type:'attack', cost:2, atk:3, def:0, desc:'Deals 3 damage. Apply 1 Burn.', burn:1, emoji:'🔥' },
  'WI-A02': { id:'WI-A02', name:'Inferno', type:'attack', cost:3, atk:4, def:0, desc:'Deals 4 damage. Applies 3 Burn.', burn:3, aoe:true, emoji:'🌋' },
  'WI-A03': { id:'WI-A03', name:'Ember', type:'attack', cost:1, atk:2, def:0, desc:'Deals 2 damage. 1 Burn/turn.', burn:1, dot:true, emoji:'✨' },
  'WI-A04': { id:'WI-A04', name:'Pyre', type:'attack', cost:4, atk:5, def:0, desc:'Deals 5 dmg. All enemies take 1 next turn.', aoe:true, nextTurnDmg:1, emoji:'🔥' },
  'WI-D01': { id:'WI-D01', name:'Scorch Shield', type:'defense', cost:2, atk:0, def:3, desc:'Gain 3 Defense. Block 1 Burn.', blockBurn:true, emoji:'🛡️' },
  'WI-D02': { id:'WI-D02', name:'Heat Resist', type:'defense', cost:1, atk:0, def:1, desc:'Gain 1 Defense. Gain 1 Energy.', gainEnergy:1, emoji:'♨️' },
  'WI-D03': { id:'WI-D03', name:'Smoke', type:'defense', cost:2, atk:0, def:4, desc:'Gain 4 Defense. Block 1 Burn.', blockBurn:true, emoji:'💨' },
  'WI-D04': { id:'WI-D04', name:'Endure', type:'defense', cost:3, atk:0, def:2, desc:'Gain 2 Defense. Heal 2 HP.', heal:2, emoji:'🌿' },
  'WI-U01': { id:'WI-U01', name:'Poison', type:'other', cost:2, atk:0, def:0, desc:'Apply 1 Poison to target enemy.', poison:1, emoji:'☠️' },
  'WI-U02': { id:'WI-U02', name:'Burn All', type:'other', cost:3, atk:0, def:0, desc:'Deal 3 dmg + 2 Burn to all enemies.', aoe:true, aoeDmg:3, burn:2, emoji:'🔥' },
  'WI-U03': { id:'WI-U03', name:'Slow', type:'other', cost:1, atk:0, def:0, desc:'Draw 1 card. Stun target enemy.', drawCard:true, stunEnemy:true, emoji:'🐌' },
  'WI-U04': { id:'WI-U04', name:'Wither', type:'other', cost:2, atk:0, def:0, desc:'Heal 5 HP. Reduce enemy Atk by 1.', heal:5, reduceAtk:true, emoji:'🥀' },

  // SHROUD CARDS
  'SH-A01': { id:'SH-A01', name:'Backstab', type:'attack', cost:2, atk:4, def:0, desc:'Deals 4 damage. +1 if target has no Armor.', bonusNoArmor:true, emoji:'🗡️' },
  'SH-A02': { id:'SH-A02', name:'Shadow Bolt', type:'attack', cost:3, atk:5, def:0, desc:'Deals 5 damage. Target cannot block.', bypassBlock:true, emoji:'⚡' },
  'SH-A03': { id:'SH-A03', name:'Silence', type:'attack', cost:1, atk:3, def:0, desc:'Deals 3 damage. Silence target.', silenceEnemy:true, emoji:'🤫' },
  'SH-A04': { id:'SH-A04', name:'Vengeance', type:'attack', cost:3, atk:6, def:0, desc:'Deals 6 damage. Gain 2 Energy.', gainEnergy:2, emoji:'⚔️' },
  'SH-D01': { id:'SH-D01', name:'Blur', type:'defense', cost:3, atk:0, def:2, desc:'Gain 2 Defense. Ignore next attack.', dodgeNext:true, emoji:'👻' },
  'SH-D02': { id:'SH-D02', name:'Step', type:'defense', cost:2, atk:0, def:3, desc:'Gain 3 Defense. Gain 1 Energy.', gainEnergy:1, emoji:'👣' },
  'SH-D03': { id:'SH-D03', name:'Evasion', type:'defense', cost:1, atk:0, def:1, desc:'Gain 1 Defense. Skip enemy action.', skipEnemy:true, emoji:'💨' },
  'SH-D04': { id:'SH-D04', name:'Phantom', type:'defense', cost:2, atk:0, def:4, desc:'Gain 4 Defense. Ghost form 1 turn.', ghostForm:true, emoji:'👻' },
  'SH-U01': { id:'SH-U01', name:'Dash', type:'other', cost:1, atk:0, def:0, desc:'Gain 1 Energy. Skip enemy attack.', gainEnergy:1, skipEnemy:true, emoji:'💨' },
  'SH-U02': { id:'SH-U02', name:'Teleport', type:'other', cost:2, atk:0, def:0, desc:'Deal 2 damage to all enemies.', aoeDmg:2, aoe:true, emoji:'🌀' },
  'SH-U03': { id:'SH-U03', name:'Hide', type:'other', cost:2, atk:0, def:1, desc:'Gain 1 Defense. Discard 1. Draw 2.', drawCards:2, discardAfter:true, emoji:'🌑' },
  'SH-U04': { id:'SH-U04', name:'Assassinate', type:'other', cost:3, atk:0, def:0, desc:'Deal 8 damage. Cannot be blocked.', aoeDmg:8, bypassBlock:true, emoji:'💀' }
};

// ── const: CLASS_DATA ──
const CLASS_DATA = {
  bastion: {
    name: 'The Bastion', theme: 'Immovable Object', icon: '🏰',
    desc: 'High Defense and Block. Focuses on durability.',
    startHP: 35, maxEnergy: 3, handSize: 4, gold: 0,
    starterDeck: ['BA-A01','BA-A02','BA-A03','BA-D01','BA-D02','BA-U01','BA-U02','BA-U03'],
    color: '#7a6b4f'
  },
  wither: {
    name: 'The Wither', theme: 'Rot and Burn', icon: '🔥',
    desc: 'Burn damage, debuffs, status effects. Low HP, high AoE.',
    startHP: 28, maxEnergy: 5, handSize: 4, gold: 0,
    starterDeck: ['WI-A01','WI-A02','WI-A03','WI-D01','WI-U01','WI-U02','WI-U03','WI-U04'],
    color: '#8b4513'
  },
  shroud: {
    name: 'The Shroud', theme: 'Shadow and Speed', icon: '🗡️',
    desc: 'High speed, evasion, assassination. High crit, low defense.',
    startHP: 30, maxEnergy: 5, handSize: 4, gold: 0,
    starterDeck: ['SH-A01','SH-A02','SH-A03','SH-D01','SH-U01','SH-U02','SH-U03','SH-U04'],
    color: '#4a3d7a'
  }
};

// ── const: ENEMY_TEMPLATES ──
const ENEMY_TEMPLATES = {
  floor1: [
    { name:'Void Goblin', hp:20, dmg:3, emoji:'👺', goldReward:5 },
    { name:'Shadow Rat', hp:18, dmg:4, emoji:'🐀', goldReward:5 },
    { name:'Dark Imp', hp:22, dmg:3, emoji:'😈', goldReward:5 }
  ],
  floor2: [
    { name:'Vampire Knight', hp:30, dmg:5, emoji:'🧛', goldReward:5 },
    { name:'Ogre Brute', hp:35, dmg:6, emoji:'👹', goldReward:5 },
    { name:'Dark Cultist', hp:28, dmg:7, emoji:'🧙', goldReward:5 }
  ],
  floor3: [
    { name:'Void Warrior', hp:40, dmg:8, emoji:'⚔️', goldReward:5 },
    { name:'Cultist Elite', hp:38, dmg:9, emoji:'🗡️', goldReward:5 },
    { name:'Shadow Wraith', hp:42, dmg:7, emoji:'👻', goldReward:5 }
  ]
};

// ── const: BOSS_TEMPLATES ──
const BOSS_TEMPLATES = [
  { floor:1, name:'The Gatekeeper', hp:80, dmg:15, emoji:'🗿', goldReward:15, mechanic:'block5' },
  { floor:2, name:'The Corrupted', hp:80, dmg:15, emoji:'💀', goldReward:15, mechanic:'burst' },
  { floor:3, name:'The Void Lord', hp:40, maxHp:40, dmg:15, emoji:'🌀', goldReward:15, mechanic:'regen' }
];

// ── function: createCardSVG ──
function createCardSVG(cardId, classType) {
  const card = CARD_DATA[cardId];
  if (!card) return '';
  
  const typeColors = {
    attack: { border:'#7a3a00', bg:'linear-gradient(180deg,#2a1508,#1a0d04)', glow:'rgba(122,58,0,0.4)' },
    defense: { border:'#1a3a5c', bg:'linear-gradient(180deg,#0a1528,#060d1a)', glow:'rgba(26,58,92,0.4)' },
    other:   { border:'#4a1a5c', bg:'linear-gradient(180deg,#1a0a28,#0d061a)', glow:'rgba(74,26,92,0.4)' }
  };
  
  const classBorders = {
    bastion: '#6b5d45', wither: '#6b3a1f', shroud: '#4a3d7a'
  };
  
  const tc = typeColors[card.type];
  const cb = classBorders[classType] || '#555';
  
  return `<svg viewBox="0 0 140 200" xmlns="http://www.w3.org/2000/svg">
    <defs>
      <linearGradient id="cbg_${cardId}" x1="0%" y1="0%" x2="0%" y2="100%">
        <stop offset="0%" style="stop-color:${classType==='bastion'?'#3a2f25':classType==='wither'?'#3a2520':'#252035'}"/>
        <stop offset="100%" style="stop-color:#0d0b14"/>
      </linearGradient>
    </defs>
    <!-- Outer frame -->
    <rect x="1" y="1" width="138" height="198" rx="7" fill="none" stroke="${cb}" stroke-width="2.5"/>
    <!-- Inner bg -->
    <rect x="4" y="4" width="132" height="192" rx="5" fill="url(#cbg_${cardId})"/>
    <!-- Type accent bar -->
    <rect x="4" y="4" width="132" height="6" rx="3" fill="${tc.border}" opacity="0.7"/>
    <!-- Decorative corner elements -->
    ${classType === 'bastion' ? `
      <path d="M8,15 L15,8 M132,15 L125,8 M8,185 L15,192 M132,185 L125,192" stroke="${cb}" stroke-width="1.5" fill="none"/>
      <rect x="60" y="7" width="20" height="4" rx="2" fill="${cb}" opacity="0.5"/>
    ` : classType === 'wither' ? `
      <circle cx="8" cy="12" r="3" fill="none" stroke="#6b3a1f" stroke-width="1"/>
      <circle cx="132" cy="12" r="3" fill="none" stroke="#6b3a1f" stroke-width="1"/>
      <path d="M70,8 Q90,4 110,8" stroke="#6b3a1f" stroke-width="1" fill="none"/>
    ` : `
      <polygon points="8,12 12,8 16,12" fill="none" stroke="#4a3d7a" stroke-width="1"/>
      <polygon points="124,12 128,8 132,12" fill="none" stroke="#4a3d7a" stroke-width="1"/>
      <line x1="65" y1="9" x2="75" y2="9" stroke="#4a3d7a" stroke-width="1"/>
    `}
  </svg>`;
}

// ── function: createCardBackSVG ──
function createCardBackSVG(classType) {
  const colors = {
    bastion: { primary:'#6b5d45', secondary:'#4a3f2e', accent:'#c9a84c' },
    wither:  { primary:'#6b3a1f', secondary:'#4a2510', accent:'#cc6633' },
    shroud:  { primary:'#4a3d7a', secondary:'#2e2550', accent:'#8866cc' }
  };
  const c = colors[classType] || colors.bastion;
  
  return `<svg viewBox="0 0 140 200" xmlns="http://www.w3.org/2000/svg">
    <defs>
      <linearGradient id="cbk_${classType}" x1="0%" y1="0%" x2="0%" y2="100%">
        <stop offset="0%" style="stop-color:#1a1520"/>
        <stop offset="100%" style="stop-color:#0d0b14"/>
      </linearGradient>
    </defs>
    <rect x="1" y="1" width="138" height="198" rx="7" fill="url(#cbk_${classType})" stroke="${c.primary}" stroke-width="2.5"/>
    <rect x="6" y="6" width="128" height="188" rx="4" fill="none" stroke="${c.secondary}" stroke-width="1"/>
    ${classType === 'bastion' ? `
      <!-- Shield pattern -->
      <path d="M70,35 L95,50 L95,85 Q95,110 70,120 Q45,110 45,85 L45,50 Z" fill="none" stroke="${c.primary}" stroke-width="2"/>
      <path d="M70,45 L85,55 L85,80 Q85,95 70,103 Q55,95 55,80 L55,55 Z" fill="${c.primary}" opacity="0.2"/>
      <line x1="70" y1="45" x2="70" y2="103" stroke="${c.accent}" stroke-width="1" opacity="0.5"/>
    ` : classType === 'wither' ? `
      <!-- Flame pattern -->
      <path d="M70,30 Q85,55 90,75 Q95,95 80,100 Q75,102 70,95 Q65,102 60,100 Q45,95 50,75 Q55,55 70,30Z" fill="none" stroke="${c.primary}" stroke-width="2"/>
      <path d="M70,50 Q80,65 82,78 Q84,88 76,91 Q72,92 70,87 Q68,92 64,91 Q56,88 58,78 Q60,65 70,50Z" fill="${c.primary}" opacity="0.3"/>
      <circle cx="70" cy="75" r="5" fill="${c.accent}" opacity="0.4"/>
    ` : `
      <!-- Eye/shadow pattern -->
      <ellipse cx="70" cy="70" rx="28" ry="16" fill="none" stroke="${c.primary}" stroke-width="2"/>
      <circle cx="70" cy="70" r="10" fill="none" stroke="${c.accent}" stroke-width="1.5"/>
      <circle cx="70" cy="70" r="4" fill="${c.accent}" opacity="0.5"/>
      <path d="M42,70 Q70,55 98,70" fill="none" stroke="${c.primary}" stroke-width="1" opacity="0.5"/>
      <path d="M42,70 Q70,85 98,70" fill="none" stroke="${c.primary}" stroke-width="1" opacity="0.5"/>
    `}
    <!-- Border decorations -->
    ${[35,65,95,125].map(y => `<line x1="12" y1="${y}" x2="18" y2="${y}" stroke="${c.secondary}" stroke-width="0.5"/>`).join('')}
    ${[35,65,95,125].map(y => `<line x1="122" y1="${y}" x2="128" y2="${y}" stroke="${c.secondary}" stroke-width="0.5"/>`).join('')}
  </svg>`;
}

// ── function: getCardFrameClass ──
function getCardFrameClass(classType) {
  return `card-frame-${classType}`;
}

// ── let: G ──
let G = {};

// ── function: initGame ──
// Global game state

function initGame(classType) {
  const cls = CLASS_DATA[classType];
  G = {
    classType: classType,
    className: cls.name,
    hp: cls.startHP,
    maxHp: cls.startHP,
    energy: cls.maxEnergy,
    maxEnergy: cls.maxEnergy,
    handSize: cls.handSize,
    gold: 0,
    defense: 0,
    permDefense: 0,
    floor: 1,
    room: 1,
    roomsPerFloor: 3,
    
    // Deck management
    deckPool: [...cls.starterDeck], // All cards in deck (can have duplicates)
    drawPile: [],
    hand: [],
    discardPile: [],
    inventory: {}, // cardId -> count
    
    // Combat state
    enemy: null,
    isBoss: false,
    playerTurn: true,
    turnActive: true,
    
    // Effects
    dodgeNextAttack: false,
    ghostForm: false,
    skipEnemyAction: false,
    blockBurnDamage: 0,
    nextTurnDmgToAll: 0,
    
    // Log
    log: []
  };
  
  // Count inventory
  G.deckPool.forEach(id => {
    G.inventory[id] = (G.inventory[id] || 0) + 1;
  });
  
  // Shuffle draw pile
  shuffleArray(G.drawPile = [...G.deckPool]);
}

// ── function: shuffleArray ──
function shuffleArray(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
}

// ── function: drawCards ──
function drawCards(count) {
  for (let i = 0; i < count; i++) {
    if (G.discardPile.length > 0 && G.drawPile.length === 0) {
      // Reshuffle discard into draw pile
      shuffleArray(G.drawPile = [...G.discardPile]);
      G.discardPile = [];
    }
    if (G.drawPile.length > 0 && G.hand.length < G.handSize + 5) {
      G.hand.push(G.drawPile.pop());
    }
  }
}

// ── function: discardCard ──
function discardCard(index) {
  if (index >= 0 && index < G.hand.length) {
    const cardId = G.hand.splice(index, 1)[0];
    G.discardPile.push(cardId);
  }
}

// ── function: addLog ──
function addLog(msg, cls='') {
  G.log.push({ msg, cls });
  if (G.log.length > 50) G.log.shift();
}

// ── function: playCard ──
function playCard(handIndex) {
  if (!G.playerTurn || !G.turnActive) return false;
  
  const cardId = G.hand[handIndex];
  const card = CARD_DATA[cardId];
  if (!card) return false;
  
  // Check energy cost
  if (card.cost > G.energy) {
    addLog('Not enough Energy!', 'log-damage');
    return false;
  }
  
  // Spend energy
  G.energy -= card.cost;
  
  // Remove from hand
  G.hand.splice(handIndex, 1);
  G.discardPile.push(cardId);
  
  addLog(`Played ${card.name}`, '');
  
  // Resolve card effects based on type
  if (card.type === 'attack') {
    resolveAttackCard(card);
  } else if (card.type === 'defense') {
    resolveDefenseCard(card);
  } else if (card.type === 'other') {
    resolveOtherCard(card);
  }
  
  // Check if enemy died
  if (G.enemy && G.enemy.hp <= 0) {
    onEnemyDefeated();
  }
  
  return true;
}

// ── function: resolveAttackCard ──
function resolveAttackCard(card) {
  const enemy = G.enemy;
  let dmg = card.atk || 0;
  
  // Special: Backstab bonus if no armor
  if (card.bonusNoArmor && (!enemy.defense || enemy.defense === 0)) {
    dmg += 1;
  }
  
  if (card.aoe) {
    // AoE damage to all enemies (single enemy in this prototype)
    let actualDmg = Math.max(0, dmg - (enemy.defense || 0));
    if (!card.bypassBlock) {
      enemy.defense = Math.max(0, (enemy.defense || 0) - dmg);
    }
    applyDamageToEnemy(actualDmg);
    addLog(`${card.name} deals ${actualDmg} damage!`, 'log-damage');
    
    // Apply burn if any
    if (card.burn) {
      enemy.burn = (enemy.burn || 0) + card.burn;
      addLog(`Applied ${card.burn} Burn to ${enemy.name}`, '');
    }
    
    // Next turn damage
    if (card.nextTurnDmg) {
      G.nextTurnDmgToAll += card.nextTurnDmg;
    }
  } else {
    let actualDmg = Math.max(0, dmg - (enemy.defense || 0));
    if (!card.bypassBlock) {
      enemy.defense = Math.max(0, (enemy.defense || 0) - dmg);
    }
    applyDamageToEnemy(actualDmg);
    addLog(`${card.name} deals ${actualDmg} damage!`, 'log-damage');
    
    // Apply burn if any
    if (card.burn) {
      enemy.burn = (enemy.burn || 0) + card.burn;
      addLog(`Applied ${card.burn} Burn to ${enemy.name}`, '');
    }
    
    // Silence enemy
    if (card.silenceEnemy) {
      enemy.silenced = true;
      addLog(`${enemy.name} is Silenced!`, '');
    }
  }
  
  // Gain energy from card effect
  if (card.gainEnergy) {
    G.energy += card.gainEnergy;
    addLog(`Gained ${card.gainEnergy} Energy`, 'log-defense');
  }
  
  // Slam: draw if HP >= 30
  if (card.id === 'BA-A01' && G.hp >= 30) {
    drawCards(1);
    addLog('Slam bonus: Drew 1 card', '');
  }
}

// ── function: resolveDefenseCard ──
function resolveDefenseCard(card) {
  // Gain defense
  if (card.def > 0) {
    if (card.permDef) {
      G.permDefense += card.def;
      addLog(`Gained ${card.def} permanent Defense!`, 'log-defense');
    } else {
      G.defense += card.def;
      addLog(`Gained ${card.def} Defense this turn.`, 'log-defense');
    }
  }
  
  // Block burn damage
  if (card.blockBurn) {
    G.blockBurnDamage++;
    addLog('Will block next Burn damage', '');
  }
  
  // Stun enemy
  if (card.stunEnemy && G.enemy) {
    G.enemy.stunned = true;
    addLog(`${G.enemy.name} is Stunned!`, '');
  }
  
  // Dodge next attack
  if (card.dodgeNext) {
    G.dodgeNextAttack = true;
    addLog('Will dodge next attack!', 'log-defense');
  }
  
  // Ghost form
  if (card.ghostForm) {
    G.ghostForm = true;
    addLog('Ghost Form active this turn', '');
  }
  
  // Skip enemy action
  if (card.skipEnemy) {
    G.skipEnemyAction = true;
    addLog('Enemy will skip their action!', 'log-defense');
  }
  
  // Heal
  if (card.heal) {
    G.hp = Math.min(G.maxHp, G.hp + card.heal);
    addLog(`Healed ${card.heal} HP!`, 'log-heal');
  }
  
  // Gain energy
  if (card.gainEnergy) {
    G.energy += card.gainEnergy;
    addLog(`Gained ${card.gainEnergy} Energy`, 'log-defense');
  }
  
  // Discard after effect
  if (card.discardAfter && G.hand.length > 0) {
    discardCard(G.hand.length - 1);
    addLog('Discarded 1 card', '');
  }
}

// ── function: resolveOtherCard ──
function resolveOtherCard(card) {
  // Draw cards
  if (card.drawCards || card.drawCard) {
    const count = card.drawCards || 1;
    drawCards(count);
    addLog(`Drew ${count} card(s)`, '');
    
    // Discard if over 5
    if (card.discardIfOver5 && G.hand.length > 5) {
      while (G.hand.length > 5) discardCard(G.hand.length - 1);
      addLog('Discarded excess cards', '');
    }
  }
  
  // Heal
  if (card.heal) {
    G.hp = Math.min(G.maxHp, G.hp + card.heal);
    addLog(`Healed ${card.heal} HP!`, 'log-heal');
  }
  
  // Gain energy
  if (card.gainEnergy) {
    G.energy += card.gainEnergy;
    addLog(`Gained ${card.gainEnergy} Energy`, 'log-defense');
  }
  
  // Gold gain
  if (card.goldGain) {
    G.gold += card.goldGain;
    addLog(`Gained ${card.goldGain} Gold!`, 'log-gold');
  }
  
  // AoE damage
  if (card.aoe && card.aoeDmg) {
    let dmg = card.aoeDmg;
    let actualDmg = Math.max(0, dmg - (G.enemy.defense || 0));
    if (!card.bypassBlock) {
      G.enemy.defense = Math.max(0, (G.enemy.defense || 0) - dmg);
    } else {
      actualDmg = dmg;
    }
    applyDamageToEnemy(actualDmg);
    addLog(`${card.name} deals ${actualDmg} damage!`, 'log-damage');
    
    if (card.burn) {
      G.enemy.burn = (G.enemy.burn || 0) + card.burn;
      addLog(`Applied ${card.burn} Burn`, '');
    }
  }
  
  // Single target damage (Assassinate)
  if (!card.aoe && card.aoeDmg) {
    let dmg = card.aoeDmg;
    applyDamageToEnemy(dmg);
    addLog(`${card.name} deals ${dmg} unblockable damage!`, 'log-damage');
  }
  
  // Poison enemy
  if (card.poison && G.enemy) {
    G.enemy.poison = (G.enemy.poison || 0) + card.poison;
    addLog(`Applied ${card.poison} Poison to ${G.enemy.name}`, '');
  }
  
  // Stun enemy
  if (card.stunEnemy && G.enemy) {
    G.enemy.stunned = true;
    addLog(`${G.enemy.name} is Stunned!`, '');
  }
  
  // Skip enemy action
  if (card.skipEnemy) {
    G.skipEnemyAction = true;
    addLog('Enemy will skip their action!', 'log-defense');
  }
  
  // Reduce enemy attack
  if (card.reduceAtk && G.enemy) {
    G.enemy.dmg = Math.max(1, G.enemy.dmg - 1);
    addLog(`${G.enemy.name}'s Attack reduced by 1`, '');
  }
  
  // Discard after effect
  if (card.discardAfter && G.hand.length > 0) {
    discardCard(G.hand.length - 1);
    addLog('Discarded 1 card', '');
  }
}

// ── function: applyDamageToEnemy ──
function applyDamageToEnemy(dmg) {
  if (!G.enemy) return;
  G.enemy.hp -= dmg;
  animateEnemyHit();
}

// ── function: endPlayerTurn ──
function endPlayerTurn() {
  if (!G.playerTurn || !G.turnActive) return;
  
  G.playerTurn = false;
  G.turnActive = false;
  
  // Apply next-turn damage to all enemies
  if (G.nextTurnDmgToAll > 0 && G.enemy) {
    applyDamageToEnemy(G.nextTurnDmgToAll);
    addLog(`Next-turn damage: ${G.nextTurnDmgToAll} to ${G.enemy.name}`, 'log-damage');
    G.nextTurnDmgToAll = 0;
  }
  
  // Apply burn/poison DoT to enemy at end of player turn (before enemy acts)
  applyEnemyDoT();
  
  if (G.enemy && G.enemy.hp <= 0) {
    onEnemyDefeated();
    return;
  }
  
  // Enemy action after short delay
  setTimeout(() => {
    enemyAction();
    
    if (G.enemy && G.enemy.hp <= 0) {
      onEnemyDefeated();
      return;
    }
    
    // Start new player turn
    startPlayerTurn();
  }, 600);
}

// ── function: applyEnemyDoT ──
function applyEnemyDoT() {
  if (!G.enemy) return;
  
  // Burn damage
  if (G.enemy.burn && G.enemy.burn > 0) {
    let burnDmg = G.enemy.burn;
    
    // Check block burn
    if (G.blockBurnDamage > 0) {
      addLog('Blocked Burn damage!', 'log-defense');
      G.blockBurnDamage--;
      return;
    }
    
    applyDamageToEnemy(burnDmg);
    addLog(`Burn deals ${burnDmg} to ${G.enemy.name}`, 'log-damage');
    G.enemy.burn = Math.max(0, G.enemy.burn - 1); // Burn decrements each turn
  }
  
  // Poison damage (doesn't decrement)
  if (G.enemy.poison && G.enemy.poison > 0) {
    applyDamageToEnemy(G.enemy.poison);
    addLog(`Poison deals ${G.enemy.poison} to ${G.enemy.name}`, 'log-damage');
  }
}

// ── function: enemyAction ──
function enemyAction() {
  const enemy = G.enemy;
  if (!enemy || enemy.hp <= 0) return;
  
  // Check stun
  if (enemy.stunned) {
    addLog(`${enemy.name} is Stunned and cannot act!`, '');
    enemy.stunned = false;
    return;
  }
  
  // Check skip enemy action
  if (G.skipEnemyAction) {
    addLog(`${enemy.name} skips their action!`, 'log-defense');
    G.skipEnemyAction = false;
    return;
  }
  
  // Boss-specific mechanics
  let dmg = enemy.dmg;
  let extraMsg = '';
  
  if (G.isBoss) {
    const bossIdx = BOSS_TEMPLATES.findIndex(b => b.floor === G.floor);
    if (bossIdx >= 0) {
      const boss = BOSS_TEMPLATES[bossIdx];
      
      // Boss 1: Gatekeeper - blocks 5 damage per turn until HP < 20
      if (boss.mechanic === 'block5' && enemy.hp > 20) {
        enemy.defense = (enemy.defense || 0) + 5;
        extraMsg += ' Blocks 5!';
      }
      
      // Boss 2: The Corrupted - deals 20 damage if player has < 3 energy
      if (boss.mechanic === 'burst' && G.energy < 3) {
        dmg = 20;
        extraMsg += ' BURST!';
      }
      
      // Boss 3: The Void Lord - regen 10 HP per turn, cannot block
      if (boss.mechanic === 'regen') {
        enemy.hp = Math.min(enemy.maxHp || 50, enemy.hp + 10);
        addLog(`${enemy.name} regenerates 10 HP!`, '');
        enemy.defense = 0; // Cannot block
      }
    }
  }
  
  // Check dodge/ghost form
  if (G.dodgeNextAttack) {
    addLog(`Dodged ${enemy.name}'s attack!`, 'log-defense');
    G.dodgeNextAttack = false;
    return;
  }
  
  if (G.ghostForm) {
    addLog(`Ghost Form absorbed the hit from ${enemy.name}!`, 'log-defense');
    G.ghostForm = false;
    return;
  }
  
  // Calculate damage after player defense
  let totalDefense = G.defense + G.permDefense;
  let actualDmg = Math.max(0, dmg - totalDefense);
  
  if (actualDmg > 0) {
    G.hp -= actualDmg;
    addLog(`${enemy.name} deals ${actualDmg} damage!${extraMsg}`, 'log-damage');
    animatePlayerHit();
  } else {
    addLog(`${enemy.name}'s attack was fully blocked!`, 'log-defense');
  }
  
  // Reset turn-based defense (permDefense stays)
  G.defense = 0;
}

// ── function: startPlayerTurn ──
function startPlayerTurn() {
  // Recover energy
  G.energy = Math.min(G.maxEnergy, G.energy + 1);
  
  // Draw cards up to hand size
  drawCards(1);
  
  G.playerTurn = true;
  G.turnActive = true;
  
  renderCombat();
}

// ── function: onEnemyDefeated ──
function onEnemyDefeated() {
  const enemy = G.enemy;
  if (!enemy) return;
  
  addLog(`${enemy.name} defeated!`, 'log-gold');
  G.gold += enemy.goldReward || 5;
  addLog(`Gained ${enemy.goldReward || 5} Gold!`, 'log-gold');
  
  // Check for boss victory
  if (G.isBoss) {
    setTimeout(() => {
      if (G.floor >= 3) {
        showScreen('screen-victory');
      } else {
        G.floor++;
        G.room = 1;
        document.getElementById('floor-clear-text').textContent = 
          `Floor ${G.floor - 1} cleared! Prepare to descend deeper...`;
        showScreen('screen-floor-clear');
      }
    }, 800);
    return;
  }
  
  // Next room or boss
  G.room++;
  if (G.room > G.roomsPerFloor) {
    // Boss fight next - skip shop before boss
    setTimeout(() => startBossFight(), 500);
  } else {
    // Show shop between rooms
    setTimeout(() => openShop(), 500);
  }
}

// ── function: startNextRoom ──
function startNextRoom() {
  const templates = ENEMY_TEMPLATES[`floor${G.floor}`] || ENEMY_TEMPLATES.floor1;
  const template = templates[Math.floor(Math.random() * templates.length)];
  
  G.enemy = { 
    ...template, 
    maxHp: template.hp, // Set maxHp for HP bar calculation
    defense:0, burn:0, poison:0, stunned:false, silenced:false 
  };
  G.isBoss = false;
  G.playerTurn = true;
  G.turnActive = true;
  G.defense = 0;
  G.dodgeNextAttack = false;
  G.ghostForm = false;
  G.skipEnemyAction = false;
  G.blockBurnDamage = 0;
  
  // Reshuffle discard into draw pile for new room
  if (G.drawPile.length === 0) {
    shuffleArray(G.drawPile = [...G.discardPile]);
    G.discardPile = [];
  }
  
  // Draw initial hand up to handSize
  while (G.hand.length < G.handSize && G.drawPile.length > 0) {
    G.hand.push(G.drawPile.pop());
  }
  
  addLog(`--- Floor ${G.floor}, Room ${G.room} ---`, '');
  addLog(`${template.name} appears!`, 'log-damage');
  
  renderCombat();
}

// ── function: startBossFight ──
function startBossFight() {
  const bossTemplate = BOSS_TEMPLATES.find(b => b.floor === G.floor);
  if (!bossTemplate) return;
  
  G.enemy = { 
    ...bossTemplate, 
    maxHp: bossTemplate.hp,
    defense: 0, burn:0, poison:0, stunned:false, silenced:false
  };
  G.isBoss = true;
  G.playerTurn = true;
  G.turnActive = true;
  G.defense = 0;
  G.dodgeNextAttack = false;
  G.ghostForm = false;
  G.skipEnemyAction = false;
  G.blockBurnDamage = 0;
  
  // Reshuffle discard into draw pile for boss fight
  if (G.drawPile.length === 0) {
    shuffleArray(G.drawPile = [...G.discardPile]);
    G.discardPile = [];
  }
  
  // Draw initial hand up to handSize
  while (G.hand.length < G.handSize && G.drawPile.length > 0) {
    G.hand.push(G.drawPile.pop());
  }
  
  addLog(`⚠️ BOSS: ${bossTemplate.name} appears!`, 'log-damage');
  renderCombat();
}

// ── function: checkGameOver ──
function checkGameOver() {
  if (G.hp <= 0) {
    G.hp = 0;
    document.getElementById('game-over-title').textContent = 'Game Over';
    document.getElementById('game-over-text').textContent = 
      `You fell on Floor ${G.floor}, Room ${G.room}. The Void consumes all...`;
    showScreen('screen-game-over');
    return true;
  }
  return false;
}

// ── function: showScreen ──
function showScreen(screenId) {
  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
  const screen = document.getElementById(screenId);
  if (screen) screen.classList.add('active');
}

// ── function: renderClassSelect ──
function renderClassSelect() {
  const container = document.getElementById('class-cards');
  container.innerHTML = '';
  
  Object.entries(CLASS_DATA).forEach(([key, cls]) => {
    const div = document.createElement('div');
    div.className = `class-card ${key}`;
    
    // Card back preview SVG
    const cardBackSVG = createCardBackSVG(key);
    
    div.innerHTML = `
      <div class="card-back-preview">${cardBackSVG}</div>
      <div class="class-icon">${cls.icon}</div>
      <div class="class-name">${cls.name}</div>
      <div class="class-theme">${cls.theme}</div>
      <div class="class-desc">${cls.desc}</div>
      <div class="class-stats">
        <div class="class-stat"><div class="class-stat-val">${cls.startHP}</div><div class="class-stat-label">HP</div></div>
        <div class="class-stat"><div class="class-stat-val">${cls.maxEnergy}</div><div class="class-stat-label">Energy</div></div>
        <div class="class-stat"><div class="class-stat-val">${cls.starterDeck.length}</div><div class="class-stat-label">Cards</div></div>
      </div>
    `;
    
    div.addEventListener('click', () => {
      initGame(key);
      showScreen('screen-combat');
      startNextRoom();
    });
    
    container.appendChild(div);
  });
}

// ── function: renderCombat ──
function renderCombat() {
  // Update HUD
  document.getElementById('floor-num').textContent = G.floor;
  document.getElementById('room-num').textContent = G.isBoss ? 'BOSS' : G.room;
  document.getElementById('gold-val').textContent = G.gold;
  
  // Deck info
  const deckCountEl = document.getElementById('deck-count');
  if (deckCountEl) {
    deckCountEl.textContent = `${G.drawPile.length} draw / ${G.discardPile.length} discard`;
  }
  
  // Player stats
  document.getElementById('hp-val').textContent = G.hp;
  document.getElementById('max-hp-val').textContent = G.maxHp;
  document.getElementById('energy-val').textContent = G.energy;
  document.getElementById('max-energy-val').textContent = G.maxEnergy;
  document.getElementById('def-val').textContent = G.defense + G.permDefense;
  
  // Enemy display
  if (G.enemy) {
    const enemyDisplay = document.getElementById('enemy-display');
    enemyDisplay.innerHTML = `
      <div id="enemy-name">${G.isBoss ? '⚠️ BOSS: ' : ''}${G.enemy.name}</div>
      <div class="enemy-sprite" id="enemy-sprite">${G.enemy.emoji}</div>
    `;
    
    // HP bar with text overlay
    const hpPercent = Math.max(0, (G.enemy.hp / G.enemy.maxHp) * 100);
    document.getElementById('enemy-hp-bar').style.width = `${hpPercent}%`;
    
    // Update or create HP text
    let hpTextEl = document.getElementById('enemy-hp-text');
    if (!hpTextEl) {
      hpTextEl = document.createElement('span');
      hpTextEl.id = 'enemy-hp-text';
      document.getElementById('enemy-hp-bar-container').appendChild(hpTextEl);
    }
    hpTextEl.textContent = `${Math.max(0, G.enemy.hp)} / ${G.enemy.maxHp}`;
    
    // Status effects
    const statusContainer = document.getElementById('enemy-status-effects');
    let statuses = '';
    if (G.enemy.burn > 0) statuses += `<span class="status-badge status-burn">🔥 Burn ${G.enemy.burn}</span>`;
    if (G.enemy.poison > 0) statuses += `<span class="status-badge status-poison">☠ Poison ${G.enemy.poison}</span>`;
    if (G.enemy.stunned) statuses += `<span class="status-badge status-stun">⚡ Stunned</span>`;
    if (G.enemy.silenced) statuses += `<span class="status-badge" style="background:#2a0a3a;color:#aa66ff;border:1px solid #4a1a5c;">🔇 Silenced</span>`;
    statusContainer.innerHTML = statuses;
  }
  
  // Render hand
  renderHand();
  
  // Battle log
  renderBattleLog();
}

// ── function: renderHand ──
function renderHand() {
  const container = document.getElementById('hand-container');
  container.innerHTML = '';
  
  G.hand.forEach((cardId, index) => {
    const card = CARD_DATA[cardId];
    if (!card) return;
    
    const playable = G.playerTurn && G.turnActive && card.cost <= G.energy;
    const frameClass = getCardFrameClass(G.classType);
    
    const cardEl = document.createElement('div');
    cardEl.className = `game-card ${frameClass} ${playable ? 'playable' : 'unplayable'}`;
    cardEl.innerHTML = `
      <div class="card-inner">
        ${createCardSVG(cardId, G.classType)}
        <div class="card-cost">${card.cost}</div>
        <div class="card-type-badge card-type-${card.type}">${card.type}</div>
        <div class="card-image-area"><span class="card-emoji">${card.emoji}</span></div>
        <div class="card-name">${card.name}</div>
        <div class="card-desc">${card.desc}</div>
      </div>
    `;
    
    if (playable) {
      cardEl.addEventListener('click', () => {
        playCard(index);
        renderCombat();
        
        // Check game over after playing card
        checkGameOver();
      });
    }
    
    container.appendChild(cardEl);
  });
}

// ── function: renderBattleLog ──
function renderBattleLog() {
  const logContainer = document.getElementById('battle-log');
  logContainer.innerHTML = G.log.slice(-8).map(entry => 
    `<div class="log-entry ${entry.cls || ''}">${entry.msg}</div>`
  ).join('');
  logContainer.scrollTop = logContainer.scrollHeight;
}

// ── function: animateEnemyHit ──
function animateEnemyHit() {
  const sprite = document.getElementById('enemy-sprite');
  if (sprite) {
    sprite.classList.remove('hit');
    void sprite.offsetWidth; // Force reflow
    sprite.classList.add('hit');
  }
}

// ── function: animatePlayerHit ──
function animatePlayerHit() {
  const playerArea = document.getElementById('player-area');
  playerArea.classList.remove('player-hit-flash');
  void playerArea.offsetWidth;
  playerArea.classList.add('player-hit-flash');
}

// ── function: openShop ──
function openShop() {
  showScreen('screen-shop');
  renderShop();
}

// ── function: renderShop ──
function renderShop() {
  document.getElementById('shop-gold-val').textContent = G.gold;
  
  const container = document.getElementById('shop-cards');
  container.innerHTML = '';
  
  // Generate random shop cards (3-5 cards)
  const allCardIds = Object.keys(CARD_DATA);
  const shopCount = 3 + Math.floor(Math.random() * 2);
  const shopCards = [];
  
  while (shopCards.length < shopCount && shopCards.length < allCardIds.length) {
    const randomId = allCardIds[Math.floor(Math.random() * allCardIds.length)];
    if (!shopCards.includes(randomId)) {
      shopCards.push(randomId);
    }
  }
  
  shopCards.forEach(cardId => {
    const card = CARD_DATA[cardId];
    const ownedCount = G.inventory[cardId] || 0;
    
    // Calculate cost with scaling
    let cost;
    if (ownedCount >= 3) {
      cost = Math.min(50, 10 + (ownedCount - 2));
    } else {
      cost = card.cost;
    }
    
    const canAfford = G.gold >= cost && ownedCount < 5;
    const frameClass = getCardFrameClass(G.classType);
    
    const wrapper = document.createElement('div');
    wrapper.className = 'shop-card-wrapper';
    wrapper.innerHTML = `
      <div class="game-card ${frameClass}" style="cursor:default;">
        <div class="card-inner">
          ${createCardSVG(cardId, G.classType)}
          <div class="card-cost">${card.cost}</div>
          <div class="card-type-badge card-type-${card.type}">${card.type}</div>
          <div class="card-image-area"><span class="card-emoji">${card.emoji}</span></div>
          <div class="card-name">${card.name}</div>
          <div class="card-desc">${card.desc}</div>
        </div>
      </div>
      <div class="shop-card-price">Cost: ${cost} Gold</div>
      <div class="shop-card-count">Owned: ${ownedCount}/5</div>
      <button class="shop-buy-btn ${canAfford ? '' : 'cant-afford'}" data-card="${cardId}" data-cost="${cost}">${ownedCount >= 5 ? 'Max' : 'Buy'}</button>
    `;
    
    const buyBtn = wrapper.querySelector('.shop-buy-btn');
    if (canAfford) {
      buyBtn.addEventListener('click', () => {
        G.gold -= cost;
        G.inventory[cardId] = ownedCount + 1;
        G.deckPool.push(cardId);
        
        addLog(`Bought ${card.name} for ${cost} Gold!`, 'log-gold');
        renderShop();
      });
    }
    
    container.appendChild(wrapper);
  });
  
  G._shopCards = shopCards;
}

// ── function: leaveShop ──
function leaveShop() {
  showScreen('screen-combat');
  startNextRoom();
}

// ── main ──
// Wire up UI buttons
document.getElementById('btn-end-turn').addEventListener('click', () => {
  endPlayerTurn();
});

document.getElementById('btn-leave-shop').addEventListener('click', () => {
  leaveShop();
});

document.getElementById('btn-skip-shop').addEventListener('click', () => {
  leaveShop();
});

document.getElementById('btn-next-floor').addEventListener('click', () => {
  showScreen('screen-combat');
  startNextRoom();
});

document.getElementById('btn-restart').addEventListener('click', () => {
  showScreen('screen-class-select');
  renderClassSelect();
});

document.getElementById('btn-play-again').addEventListener('click', () => {
  showScreen('screen-class-select');
  renderClassSelect();
});

// Initialize class selection screen
renderClassSelect();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"composer","sessionId":"8e0b1572-5c21-4da5-8ebe-c51e97480f5f","tokensIn":1982598,"tokensOut":39164,"tokensTotal":2021762,"turns":56,"toolCalls":55,"failedToolCalls":2,"timestamp":"2026-07-16T05:09:00.502Z"} -->