← Deckbuilder results

Deckbuilder

thinkingcap qwen3.6 27b · 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 54,695 bytes · SHA-256 4458e11cef96
<!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; }
html, body { width: 100%; height: 100%; overflow: hidden; }
body {
  background: #0a0806;
  color: #d4c5a9;
  font-family: 'Georgia', 'Times New Roman', serif;
}

/* Background atmosphere */
.screen::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 50% 30%, rgba(40,25,15,0.6) 0%, transparent 70%),
              radial-gradient(ellipse at 80% 80%, rgba(60,20,20,0.15) 0%, transparent 50%);
  pointer-events: none;
  z-index: 0;
}

.screen { display: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; }
.screen.active { display: flex; flex-direction: column; align-items: center; }
.screen > * { position: relative; z-index: 1; }

/* ===== TITLE SCREEN ===== */
.game-title {
  font-size: clamp(2em, 5vw, 3.5em);
  color: #c9a84c;
  text-shadow: 0 0 30px rgba(201,168,76,0.4), 0 0 60px rgba(201,168,76,0.15), 0 2px 4px rgba(0,0,0,0.9);
  margin-top: clamp(30px, 8vh, 80px);
  letter-spacing: 4px;
  text-align: center;
}
.subtitle {
  font-size: clamp(0.9em, 2vw, 1.15em);
  color: #7a6d5b;
  margin: 12px 0 35px;
  font-style: italic;
  text-align: center;
}

/* ===== BUTTONS ===== */
.combat-btn {
  background: linear-gradient(180deg, #3a2f25 0%, #1e1812 100%);
  border: 2px solid #6b5d47;
  color: #c9a84c;
  padding: 12px 36px;
  font-size: 1.05em;
  font-family: 'Georgia', serif;
  cursor: pointer;
  border-radius: 4px;
  text-transform: uppercase;
  letter-spacing: 2px;
  transition: all 0.2s;
}
.combat-btn:hover:not(:disabled) {
  background: linear-gradient(180deg, #4a3f35 0%, #2e2822 100%);
  border-color: #c9a84c;
  box-shadow: 0 0 15px rgba(201,168,76,0.3), inset 0 0 10px rgba(201,168,76,0.05);
}
.combat-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* ===== CLASS SELECT ===== */
#screen-class-select { justify-content: flex-start; }
#class-cards { display: flex; gap: 28px; flex-wrap: wrap; justify-content: center; padding: 0 20px; max-width: 1000px; }

.class-card {
  width: 260px;
  background: linear-gradient(180deg, #1a1510 0%, #0d0b08 100%);
  border: 2px solid #3a3025;
  border-radius: 8px;
  padding: 24px 20px;
  cursor: pointer;
  transition: all 0.3s;
  text-align: center;
}
.class-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 12px 35px rgba(0,0,0,0.7), 0 0 25px rgba(201,168,76,0.15);
  border-color: #c9a84c;
}
.class-card h3 { color: #c9a84c; font-size: 1.4em; margin-bottom: 6px; }
.class-theme { color: #8a7d6b; font-style: italic; font-size: 0.9em; margin-bottom: 12px; }
.class-stats { color: #a89880; font-size: 0.85em; line-height: 1.7; }
.class-icon { width: 80px; height: 80px; margin: 0 auto 12px; display: block; }

/* ===== COMBAT SCREEN ===== */
#screen-combat { justify-content: flex-start; padding-top: 6px; }

#hud-top {
  width: 100%; display: flex; justify-content: space-between; align-items: center;
  padding: 8px 24px;
  background: linear-gradient(180deg, rgba(26,21,16,0.95), transparent);
  font-size: 0.95em; color: #c9a84c; letter-spacing: 1px;
}

#combat-area {
  flex: 1; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;
  position: relative; min-height: 200px; overflow: hidden;
}

/* Enemy zone */
#enemy-zone {
  display: flex; gap: 24px; align-items: flex-start; justify-content: center; flex-wrap: wrap; padding: 10px 20px;
  width: 100%; max-width: 800px;
}

.enemy-sprite {
  width: 130px; text-align: center; position: relative; cursor: pointer; transition: transform 0.15s, filter 0.15s;
}
.enemy-sprite:hover { transform: scale(1.06); }
.enemy-sprite.selected { filter: drop-shadow(0 0 14px rgba(201,168,76,0.9)); transform: scale(1.06); }
.enemy-sprite.dead { opacity: 0.3; pointer-events: none; filter: grayscale(1); }

.enemy-hp-bar-bg {
  width: 110px; height: 12px; background: #1a1510; border: 1px solid #4a3d2f; border-radius: 3px; margin: 6px auto 2px; overflow: hidden;
}
.enemy-hp-bar {
  height: 100%; background: linear-gradient(90deg, #8b2020, #cc4444); transition: width 0.4s ease; border-radius: 2px;
}
.enemy-name { color: #a89880; font-size: 0.8em; margin-top: 2px; }
.enemy-statuses { display: flex; gap: 3px; justify-content: center; margin-top: 3px; min-height: 16px; flex-wrap: wrap; }
.status-badge {
  font-size: 0.7em; padding: 1px 5px; border-radius: 3px; color: #fff; white-space: nowrap;
}
.status-burn { background: rgba(204,100,20,0.8); }
.status-poison { background: rgba(100,160,50,0.8); }
.status-stun { background: rgba(160,130,200,0.8); }

/* Battlefield divider */
#battlefield-divider {
  width: 70%; height: 2px; margin: 8px 0;
  background: linear-gradient(90deg, transparent, #4a3d2f 30%, #6b5d47 50%, #4a3d2f 70%, transparent);
}

/* Player stats */
#player-stats-bar { display: flex; gap: 20px; padding: 6px 0; align-items: center; }
.stat-item { color: #c9a84c; font-size: 0.9em; letter-spacing: 1px; white-space: nowrap; }
.stat-hp { color: #cc6655 !important; }
.stat-energy { color: #5599cc !important; }
.stat-defense { color: #88aacc !important; }

/* Combat log */
#combat-log-area {
  width: 100%; max-width: 700px; height: 60px; overflow-y: auto; margin: 4px 0;
  padding: 4px 12px; background: rgba(15,12,8,0.8); border: 1px solid #2a2318; border-radius: 4px;
}
#combat-log-area::-webkit-scrollbar { width: 4px; }
#combat-log-area::-webkit-scrollbar-thumb { background: #3a3025; border-radius: 2px; }
.log-entry { font-size: 0.78em; color: #9a8d7b; padding: 1px 0; line-height: 1.4; }
.log-damage { color: #cc6655; }
.log-heal { color: #66aa66; }
.log-gold { color: #c9a84c; }

/* Hand area */
#hand-area {
  width: 100%; min-height: 200px; display: flex; gap: 6px; justify-content: center; align-items: flex-end;
  padding: 8px 15px 4px; flex-wrap: wrap;
}

.hand-card {
  cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; position: relative;
}
.hand-card:hover:not(.unplayable) {
  transform: translateY(-18px);
  z-index: 10;
  box-shadow: 0 8px 30px rgba(0,0,0,0.6), 0 0 15px rgba(201,168,76,0.2);
}
.hand-card.unplayable { opacity: 0.45; cursor: not-allowed; filter: saturate(0.3); }

/* End turn button */
#btn-end-turn { margin: 6px auto 12px; }

/* ===== SHOP SCREEN ===== */
#screen-shop, #screen-victory, #screen-defeat { justify-content: center; align-items: center; padding-top: 40px; }
#screen-shop h2, #screen-victory h1, #screen-defeat h1 {
  color: #c9a84c; font-size: clamp(1.8em, 4vw, 2.5em); margin-bottom: 8px;
  text-shadow: 0 0 20px rgba(201,168,76,0.3);
}
#shop-gold-display { color: #c9a84c; font-size: 1.1em; margin-bottom: 15px; }

#shop-cards { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; max-width: 900px; margin: 15px 0; padding: 0 20px; }
.shop-card-item { cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; position: relative; }
.shop-card-item:hover { transform: translateY(-6px); box-shadow: 0 8px 25px rgba(0,0,0,0.5), 0 0 12px rgba(201,168,76,0.2); }
.shop-cost-badge {
  position: absolute; bottom: -8px; right: -8px; background: #c9a84c; color: #0d0b08; font-size: 0.75em;
  padding: 2px 6px; border-radius: 3px; font-weight: bold; z-index: 5;
}

/* ===== TRANSITION SCREEN ===== */
#screen-transition { justify-content: center; align-items: center; }
.transition-text { color: #c9a84c; font-size: 1.6em; margin-bottom: 20px; text-shadow: 0 0 20px rgba(201,168,76,0.3); }

/* ===== ANIMATIONS ===== */
.damage-flash { animation: damageFlash 0.35s ease-out; }
@keyframes damageFlash {
  0% { filter: brightness(4) saturate(0); transform: scale(1.08); }
  100% { filter: none; transform: none; }
}

.card-play-anim { animation: cardPlay 0.35s ease-out; }
@keyframes cardPlay {
  0% { transform: scale(1.4) translateY(-30px); opacity: 0.3; }
  60% { transform: scale(0.95) translateY(2px); }
  100% { transform: none; opacity: 1; }
}

.enemy-sprite svg { width: 100%; height: auto; display: block; margin: 0 auto; }

/* Card tooltip */
.card-tooltip {
  position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  background: rgba(15,12,8,0.96); border: 1px solid #4a3d2f; border-radius: 5px;
  padding: 8px 12px; width: 200px; z-index: 100; pointer-events: none;
  font-size: 0.78em; color: #c9b896; line-height: 1.4; text-align: left;
  box-shadow: 0 4px 20px rgba(0,0,0,0.7);
}
.card-tooltip .tt-name { font-weight: bold; color: #e8d5a3; margin-bottom: 3px; }
.card-tooltip .tt-type { font-size: 0.9em; opacity: 0.7; }

/* Deck info in shop */
#deck-info { color: #8a7d6b; font-size: 0.9em; margin-bottom: 15px; text-align: center; }
/* ── css: animations ── */
/* Card entrance animation */
@keyframes cardEnter {
  from { transform: translateY(30px) scale(0.8); opacity: 0; }
  to { transform: none; opacity: 1; }
}

.hand-card { animation: cardEnter 0.3s ease-out backwards; }
.hand-card:nth-child(1) { animation-delay: 0.05s; }
.hand-card:nth-child(2) { animation-delay: 0.1s; }
.hand-card:nth-child(3) { animation-delay: 0.15s; }
.hand-card:nth-child(4) { animation-delay: 0.2s; }

/* Damage number popup */
@keyframes damagePopup {
  0% { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(-30px); opacity: 0; }
}

.damage-number {
  position: absolute; top: -20px; left: 50%; transform: translateX(-50%);
  color: #ff4444; font-size: 1.2em; font-weight: bold; pointer-events: none;
  animation: damagePopup 0.8s ease-out forwards; z-index: 50;
}

/* Enemy hit flash */
@keyframes enemyHit {
  0% { filter: brightness(3) saturate(0); }
  100% { filter: none; }
}

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

/* Screen transition fade */
.screen.active { animation: fadeIn 0.4s ease-out; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* HP bar critical color */
.enemy-hp-bar.critical { background: linear-gradient(90deg, #aa2222, #cc3333) !important; }
.enemy-hp-bar.low { background: linear-gradient(90deg, #aa6622, #cc8833) !important; }

/* Player HP bar */
#player-hp-bar-bg {
  width: 150px; height: 10px; background: #1a1510; border: 1px solid #4a3d2f; 
  border-radius: 3px; overflow: hidden; display: inline-block; vertical-align: middle; margin-left: 8px;
}
#player-hp-bar {
  height: 100%; background: linear-gradient(90deg, #44aa44, #66cc66); transition: width 0.4s ease; border-radius: 2px;
}

/* Energy pips */
.energy-pip {
  display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 3px;
  background: #334466; border: 1px solid #5577aa; transition: all 0.2s;
}
.energy-pip.filled { background: #5599cc; box-shadow: 0 0 6px rgba(85,153,204,0.5); }

/* Card type glow on hover */
.hand-card:hover:not(.unplayable) svg rect:first-child {
  stroke-width: 4 !important;
}

/* Shop card purchased animation */
@keyframes purchaseFlash {
  0% { box-shadow: 0 0 0 rgba(201,168,76,0); }
  50% { box-shadow: 0 0 30px rgba(201,168,76,0.6); }
  100% { box-shadow: 0 0 0 rgba(201,168,76,0); }
}

.shop-card-item.purchased { animation: purchaseFlash 0.5s ease-out; }

/* Boss warning pulse */
@keyframes bossPulse {
  0%, 100% { text-shadow: 0 0 10px rgba(204,68,68,0.3); }
  50% { text-shadow: 0 0 25px rgba(204,68,68,0.7); }
}

.boss-warning { animation: bossPulse 1.5s ease-in-out infinite; color: #cc4433 !important; }
</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-transition" class="screen"><div class="transition-text"></div><button id="btn-continue" class="combat-btn">Continue</button></div><div id="screen-combat" class="screen">
  <div id="hud-top">
    <span id="hud-floor">Floor 1 - Room 1</span>
    <span id="hud-gold">Gold: 0</span>
  </div>
  <div id="combat-area">
    <div id="enemy-zone"></div>
    <div id="battlefield-divider"></div>
    <div id="player-stats-bar">
      <span id="stat-hp">HP: 30/30</span>
      <span id="stat-energy">Energy: 5/5</span>
      <span id="stat-defense">Defense: 0</span>
    </div>
  </div>
  <div id="combat-log-area"></div><div id="hand-area"></div>
  <button id="btn-end-turn" class="combat-btn">End Turn</button>
</div>
<div id="screen-shop" class="screen">
  <h2>Card Shop</h2>
  <p id="shop-gold-display">Gold: 0</p>
  <div id="shop-cards"></div>
  <button id="btn-leave-shop" class="combat-btn">Leave Shop</button>
</div>
<div id="screen-victory" class="screen">
  <h1>VICTORY</h1>
  <p>The Void has been vanquished. Aethelgard is free.</p>
  <button id="btn-restart" class="combat-btn">Play Again</button>
</div>
<div id="screen-defeat" class="screen">
  <h1>DEFEAT</h1>
  <p>The Void consumes all...</p>
  <button id="btn-restart2" class="combat-btn">Try 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:4, def:0, desc:"Deals 4 damage. If you have ≥30 HP, draw 1 card.", classId:"bastion" },
  "BA-A02": { id:"BA-A02", name:"Earthquake", type:"Attack", cost:3, atk:5, def:0, desc:"Deals 5 damage to all enemies.", aoe:true, classId:"bastion" },
  "BA-A03": { id:"BA-A03", name:"Stone Fist", type:"Attack", cost:1, atk:3, def:0, desc:"Deals 3 damage.", classId:"bastion" },
  "BA-A04": { id:"BA-A04", name:"Boulder", type:"Attack", cost:4, atk:7, def:0, desc:"Deals 7 damage.", classId:"bastion" },
  "BA-D01": { id:"BA-D01", name:"Shield Wall", type:"Defense", cost:0, atk:0, def:5, desc:"Gain 5 Defense this turn.", classId:"bastion" },
  "BA-D02": { id:"BA-D02", name:"Iron Skin", type:"Defense", cost:2, atk:0, def:6, desc:"Gain 6 permanent Defense. Discard 1 card.", permDef:true, discardAfter:true, classId:"bastion" },
  "BA-D03": { id:"BA-D03", name:"Stand Firm", type:"Defense", cost:1, atk:0, def:3, desc:"Gain 3 Defense. Blocks 1 incoming damage.", blockExtra:true, classId:"bastion" },
  "BA-D04": { id:"BA-D04", name:"Taunt", type:"Defense", cost:2, atk:0, def:4, desc:"Gain 4 Defense. Stun 1 enemy for 1 turn.", stunEnemy:true, classId:"bastion" },
  "BA-U01": { id:"BA-U01", name:"Search", type:"Other", cost:2, atk:0, def:0, desc:"Draw 1 card. Discard hand if >5 cards.", drawCard:true, discardIfOver5:true, classId:"bastion" },
  "BA-U02": { id:"BA-U02", name:"Repair", type:"Other", cost:3, atk:0, def:0, desc:"Heal 6 HP.", heal:6, classId:"bastion" },
  "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, classId:"bastion" },
  "BA-U04": { id:"BA-U04", name:"Loot", type:"Other", cost:2, atk:0, def:0, desc:"Gain 5 Gold.", gold:5, classId:"bastion" },

  // WITHER cards
  "WI-A01": { id:"WI-A01", name:"Fireball", type:"Attack", cost:2, atk:4, def:0, desc:"Deals 4 damage. Apply 1 Burn.", burn:1, classId:"wither" },
  "WI-A02": { id:"WI-A02", name:"Inferno", type:"Attack", cost:3, atk:5, def:0, desc:"Deals 5 damage to all. Applies 3 Burn.", aoe:true, burnAll:3, classId:"wither" },
  "WI-A03": { id:"WI-A03", name:"Ember", type:"Attack", cost:1, atk:3, def:0, desc:"Deals 3 damage. Deals 1 Burn/turn to target.", burn:1, classId:"wither" },
  "WI-A04": { id:"WI-A04", name:"Pyre", type:"Attack", cost:4, atk:6, def:0, desc:"Deals 6 damage. All enemies take 2 Burn next turn.", aoe:true, burnAll:2, classId:"wither" },
  "WI-D01": { id:"WI-D01", name:"Scorch Shield", type:"Defense", cost:2, atk:0, def:4, desc:"Gain 4 Defense. Block 1 Burn damage.", blockBurn:true, classId:"wither" },
  "WI-D02": { id:"WI-D02", name:"Heat Resist", type:"Defense", cost:1, atk:0, def:2, desc:"Gain 2 Defense. Gain 1 Energy.", gainEnergy:1, classId:"wither" },
  "WI-D03": { id:"WI-D03", name:"Smoke", type:"Defense", cost:2, atk:0, def:5, desc:"Gain 5 Defense. Block 1 Burn damage.", blockBurn:true, classId:"wither" },
  "WI-D04": { id:"WI-D04", name:"Endure", type:"Defense", cost:3, atk:0, def:3, desc:"Gain 3 Defense. Heal 2 HP.", heal:2, classId:"wither" },
  "WI-U01": { id:"WI-U01", name:"Poison", type:"Other", cost:2, atk:0, def:0, desc:"Apply 1 Poison to target enemy.", poison:1, classId:"wither" },
  "WI-U02": { id:"WI-U02", name:"Burn", type:"Other", cost:3, atk:0, def:0, desc:"Deal 4 damage + 2 Burn to all enemies.", aoe:true, atkVal:4, burnAll:2, classId:"wither" },
  "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, classId:"wither" },
  "WI-U04": { id:"WI-U04", name:"Wither", type:"Other", cost:2, atk:0, def:0, desc:"Heal 5 HP. Reduce enemy Attack by 1.", heal:5, debuffAtk:true, classId:"wither" },

  // SHROUD cards
  "SH-A01": { id:"SH-A01", name:"Backstab", type:"Attack", cost:2, atk:5, def:0, desc:"Deals 5 damage. +1 if target has no Armor.", bonusNoArmor:true, classId:"shroud" },
  "SH-A02": { id:"SH-A02", name:"Shadow Bolt", type:"Attack", cost:3, atk:6, def:0, desc:"Deals 6 damage. Target cannot block.", unblockable:true, classId:"shroud" },
  "SH-A03": { id:"SH-A03", name:"Silence", type:"Attack", cost:1, atk:4, def:0, desc:"Deals 4 damage. Silence target.", silenceEnemy:true, classId:"shroud" },
  "SH-A04": { id:"SH-A04", name:"Vengeance", type:"Attack", cost:3, atk:7, def:0, desc:"Deals 7 damage. Gain 2 Energy.", gainEnergy:2, classId:"shroud" },
  "SH-D01": { id:"SH-D01", name:"Blur", type:"Defense", cost:3, atk:0, def:3, desc:"Gain 3 Defense. Ignore next attack.", dodgeNext:true, classId:"shroud" },
  "SH-D02": { id:"SH-D02", name:"Step", type:"Defense", cost:2, atk:0, def:4, desc:"Gain 4 Defense. Gain 1 Energy.", gainEnergy:1, classId:"shroud" },
  "SH-D03": { id:"SH-D03", name:"Evasion", type:"Defense", cost:1, atk:0, def:2, desc:"Gain 2 Defense. Skip enemy action.", skipEnemy:true, classId:"shroud" },
  "SH-D04": { id:"SH-D04", name:"Phantom", type:"Defense", cost:2, atk:0, def:5, desc:"Gain 5 Defense. Ghost form for 1 turn.", ghostForm:true, classId:"shroud" },
  "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, classId:"shroud" },
  "SH-U02": { id:"SH-U02", name:"Teleport", type:"Other", cost:2, atk:0, def:0, desc:"Deal 3 damage to all enemies.", aoe:true, atkVal:3, classId:"shroud" },
  "SH-U03": { id:"SH-U03", name:"Hide", type:"Other", cost:2, atk:0, def:1, desc:"Gain 1 Defense. Discard 1 card. Draw 2 cards.", drawCard:true, drawCount:2, discardAfter:true, classId:"shroud" },
  "SH-U04": { id:"SH-U04", name:"Assassinate", type:"Other", cost:3, atk:0, def:0, desc:"Deal 10 damage. Cannot be blocked.", atkVal:10, unblockable:true, classId:"shroud" }
};

// ── function: startRoom ──
function startRoom() {
  G.enemies = [];
  G.selectedEnemyIdx = -1;
  G.hero.defense = 0;
  G.hero.dodgeNext = false;
  G.hero.ghostForm = false;
  G.hero.blockBurn = false;
  G.hero.skipEnemy = false;
  G.hand = [];
  G.combatLog = [];

  if (G.isBossRoom) {
    const bossTpl = BOSS_TEMPLATES[G.floor - 1];
    G.enemies.push({
      name: bossTpl.name, hp: bossTpl.hp, maxHp: bossTpl.maxHp || bossTpl.hp,
      dmg: bossTpl.dmg, defense: 0, burn: 0, poison: 0, stun: 0, silenced: false,
      isBoss: true, floor: G.floor, id: 'boss' + G.floor
    });
    addLog(`⚠ BOSS: ${bossTpl.name} appears!`, 'damage');
  } else {
    // Floor 1: 1 enemy, Floor 2: 2 enemies, Floor 3: 2-3 enemies
    const count = G.floor === 1 ? 1 : (G.floor === 2 ? 2 : (2 + Math.floor(Math.random() * 2)));
    const tpl = ENEMY_TEMPLATES[G.floor - 1];
    for (let i = 0; i < count; i++) {
      G.enemies.push({
        name: tpl.names[Math.floor(Math.random() * tpl.names.length)],
        hp: tpl.hp, maxHp: tpl.hp, dmg: tpl.dmg, defense: 0, burn: 0, poison: 0, stun: 0, silenced: false,
        isBoss: false, floor: G.floor, id: 'e' + i
      });
    }
  }

  // Draw starting hand
  drawCards(G.hero.handSize);
  addLog(`--- Floor ${G.floor}, Room ${G.isBossRoom ? 'BOSS' : G.room} ---`);
  
  showScreen('combat');
  renderCombat();
}

// ── const: CLASS_DATA ──
const CLASS_DATA = {
  bastion: { name:"The Bastion", theme:"Immovable Object", hp:45, energy:3, handSize:4 },
  wither:  { name:"The Wither", theme:"Rot and Burn", hp:38, energy:5, handSize:4 },
  shroud:  { name:"The Shroud", theme:"Shadow and Speed", hp:36, energy:5, handSize:4 }
};

// ── const: ENEMY_TEMPLATES ──
const ENEMY_TEMPLATES = [
  { floor:1, names:["Goblin","Rat Swarm","Bandit"], hp:6, dmg:2 },
  { floor:2, names:["Vampire Knight","Ogre","Dark Priest"], hp:20, dmg:4 },
  { floor:3, names:["Void Warrior","Cultist","Shadow Stalker"], hp:30, dmg:7 }
];

// ── const: BOSS_TEMPLATES ──
const BOSS_TEMPLATES = [
  { name:"The Gatekeeper", type:'tank', hp:45, dmg:8 },
  { name:"The Corrupted", type:'burst', hp:55, dmg:10 },
  { name:"The Void Lord", type:'regen', hp:30, maxHp:30, dmg:12 }
];

// ── function: createCardSVG ──
function createCardSVG(cardData, isBack) {
  const classColors = {
    bastion: { primary:"#7a6540", secondary:"#C9A84C", accent:"#D4AF37", bg1:"#2a2015", bg2:"#0d0b08" },
    wither:  { primary:"#6B2D2D", secondary:"#CC6644", accent:"#FF6B35", bg1:"#2a1515", bg2:"#0d0808" },
    shroud:  { primary:"#2D2D6B", secondary:"#7B7BDD", accent:"#9B59B6", bg1:"#15152a", bg2:"#08080d" }
  };
  const typeColors = { Attack:"#cc4433", Defense:"#4488bb", Other:"#aa66cc" };

  if (isBack) {
    const c = classColors[cardData.classId] || classColors.bastion;
    let centerArt = '';
    if (cardData.classId === 'bastion') {
      centerArt = `<polygon points="90,50 128,78 114,120 66,120 52,78" fill="none" stroke="${c.secondary}" stroke-width="2.5" opacity="0.7"/>
        <polygon points="90,65 115,83 105,110 75,110 65,83" fill="${c.primary}" opacity="0.3"/>`;
    } else if (cardData.classId === 'wither') {
      centerArt = `<circle cx="90" cy="85" r="32" fill="none" stroke="${c.secondary}" stroke-width="2" opacity="0.7"/>
        <circle cx="90" cy="85" r="18" fill="none" stroke="${c.accent}" stroke-width="1.5" opacity="0.6"/>
        <circle cx="90" cy="85" r="6" fill="${c.accent}" opacity="0.4"/>`;
    } else {
      centerArt = `<path d="M55 72 Q90 38 125 72 Q90 118 55 72Z" fill="none" stroke="${c.secondary}" stroke-width="2.5" opacity="0.7"/>
        <circle cx="90" cy="85" r="6" fill="${c.accent}" opacity="0.5"/>`;
    }
    return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 250" width="140" height="196">
      <defs>
        <linearGradient id="backGrad_${cardData.classId}" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" style="stop-color:${c.bg1}"/>
          <stop offset="100%" style="stop-color:${c.bg2}"/>
        </linearGradient>
      </defs>
      <rect x="2" y="2" width="176" height="246" rx="8" fill="#0a0806" stroke="${c.secondary}" stroke-width="2.5"/>
      <rect x="6" y="6" width="168" height="238" rx="5" fill="url(#backGrad_${cardData.classId})"/>
      <pattern id="bp_${cardData.classId}" patternUnits="userSpaceOnUse" width="40" height="40">
        <path d="M20 0 L40 20 L20 40 L0 20Z" fill="none" stroke="${c.secondary}" stroke-width="0.5" opacity="0.15"/>
      </pattern>
      <rect x="6" y="6" width="168" height="238" rx="5" fill="url(#bp_${cardData.classId})"/>
      ${centerArt}
      <text x="90" y="148" text-anchor="middle" font-size="9" fill="${c.secondary}" opacity="0.5" font-family="Georgia,serif" letter-spacing="3">AETHELGARD</text>
    </svg>`;
  }

  const c = classColors[cardData.classId] || classColors.bastion;
  const tc = typeColors[cardData.type];
  
  // Split description into lines
  let descLines = [];
  const words = cardData.desc.split(' ');
  let line = '';
  words.forEach(w => {
    if ((line + ' ' + w).length > 32) { descLines.push(line); line = w; }
    else line = line ? line + ' ' + w : w;
  });
  if (line) descLines.push(line);

  let statsHTML = '';
  if (cardData.atk > 0 && cardData.def > 0) {
    statsHTML = `<rect x="14" y="172" width="56" height="24" rx="3" fill="#1a0a0a" stroke="${tc}" stroke-width="1.5"/>
      <text x="42" y="189" text-anchor="middle" font-size="13" fill="#e44" font-weight="bold">⚔${cardData.atk}</text>
      <rect x="100" y="172" width="56" height="24" rx="3" fill="#0a0a1a" stroke="${tc}" stroke-width="1.5"/>
      <text x="128" y="189" text-anchor="middle" font-size="13" fill="#48e" font-weight="bold">🛡${cardData.def}</text>`;
  } else if (cardData.atk > 0) {
    statsHTML = `<rect x="52" y="172" width="64" height="24" rx="3" fill="#1a0a0a" stroke="${tc}" stroke-width="1.5"/>
      <text x="84" y="189" text-anchor="middle" font-size="13" fill="#e44" font-weight="bold">⚔${cardData.atk}</text>`;
  } else if (cardData.def > 0) {
    statsHTML = `<rect x="52" y="172" width="64" height="24" rx="3" fill="#0a0a1a" stroke="${tc}" stroke-width="1.5"/>
      <text x="84" y="189" text-anchor="middle" font-size="13" fill="#48e" font-weight="bold">🛡${cardData.def}</text>`;
  } else {
    statsHTML = `<rect x="52" y="172" width="64" height="24" rx="3" fill="#0a0a1a" stroke="${tc}" stroke-width="1.5"/>
      <text x="84" y="189" text-anchor="middle" font-size="13" fill="${c.secondary}">✦</text>`;
  }

  let typeIcon = cardData.type === 'Attack' ? '⚔' : cardData.type === 'Defense' ? '🛡' : '✦';

  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 250" width="140" height="196">
    <defs>
      <linearGradient id="cf_${cardData.id}" x1="0%" y1="0%" x2="0%" y2="100%">
        <stop offset="0%" style="stop-color:${c.bg1}"/>
        <stop offset="100%" style="stop-color:${c.bg2}"/>
      </linearGradient>
    </defs>
    <!-- Outer border with type color -->
    <rect x="1" y="1" width="178" height="248" rx="9" fill="#0a0806" stroke="${tc}" stroke-width="3"/>
    <!-- Inner card body -->
    <rect x="5" y="5" width="170" height="240" rx="7" fill="url(#cf_${cardData.id})"/>
    
    <!-- Header bar -->
    <rect x="8" y="8" width="164" height="32" rx="4" fill="#0a0806" opacity="0.85"/>
    <text x="90" y="29" text-anchor="middle" font-size="14" fill="${c.secondary}" font-weight="bold" font-family="Georgia,serif">${cardData.name}</text>
    
    <!-- Cost circle -->
    <circle cx="26" cy="27" r="11" fill="#1a1510" stroke="${tc}" stroke-width="2"/>
    <text x="26" y="32" text-anchor="middle" font-size="13" fill="${c.secondary}" font-weight="bold">${cardData.cost}</text>
    
    <!-- Image placeholder area -->
    <rect x="8" y="44" width="164" height="70" rx="4" fill="#0a0806" opacity="0.5"/>
    <rect x="12" y="48" width="156" height="62" rx="3" fill="${c.bg1}" opacity="0.3" stroke="${tc}" stroke-width="0.5" stroke-dasharray="3,3"/>
    <text x="90" y="84" text-anchor="middle" font-size="30" opacity="0.25">${typeIcon}</text>
    
    <!-- Description area -->
    <rect x="8" y="118" width="164" height="52" rx="4" fill="#0a0806" opacity="0.7"/>
    ${descLines.map((l, i) => `<text x="90" y="${134 + i * 13}" text-anchor="middle" font-size="8.5" fill="${c.secondary}" opacity="0.8">${l}</text>`).join('')}
    
    <!-- Stats -->
    ${statsHTML}
    
    <!-- Footer -->
    <rect x="8" y="218" width="164" height="22" rx="3" fill="#0a0806" opacity="0.7"/>
    <text x="90" y="233" text-anchor="middle" font-size="8" fill="${c.secondary}" opacity="0.5">${cardData.type} • ${cardData.classId.toUpperCase()}</text>
  </svg>`;
}

// ── function: createEnemySVG ──
function createEnemySVG(enemy) {
  const isBoss = enemy.isBoss;
  const w = isBoss ? 130 : 100;
  const h = isBoss ? 140 : 110;
  
  let bodyColor, accentColor, eyeColor;
  if (enemy.floor === 1) {
    bodyColor = '#5a7a3a'; accentColor = '#8aaa5a'; eyeColor = '#ffcc00'; // Goblin green
  } else if (enemy.floor === 2) {
    bodyColor = '#6a2a4a'; accentColor = '#9a4a6a'; eyeColor = '#ff3333'; // Vampire purple-red
  } else if (enemy.floor === 3) {
    bodyColor = '#2a1a5a'; accentColor = '#5a3aaa'; eyeColor = '#cc00ff'; // Void purple
  } else {
    bodyColor = '#4a4a4a'; accentColor = '#7a7a7a'; eyeColor = '#ffffff';
  }

  if (isBoss) {
    return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" width="${w*1.1}" height="${h*1.1}">
      <defs>
        <radialGradient id="bossGlow_${enemy.id || 'x'}" cx="50%" cy="40%">
          <stop offset="0%" style="stop-color:${accentColor};stop-opacity:0.3"/>
          <stop offset="100%" style="stop-color:transparent"/>
        </radialGradient>
      </defs>
      <!-- Aura -->
      <ellipse cx="${w/2}" cy="${h*0.5}" rx="${w*0.45}" ry="${h*0.45}" fill="url(#bossGlow_${enemy.id || 'x'})"/>
      <!-- Body -->
      <path d="M${w*0.3} ${h*0.85} L${w*0.2} ${h*0.5} Q${w*0.15} ${h*0.35} ${w*0.3} ${h*0.25} L${w*0.4} ${h*0.15} L${w*0.6} ${h*0.15} L${w*0.7} ${h*0.25} Q${w*0.85} ${h*0.35} ${w*0.8} ${h*0.5} L${w*0.7} ${h*0.85}" fill="${bodyColor}" stroke="${accentColor}" stroke-width="2"/>
      <!-- Head -->
      <circle cx="${w/2}" cy="${h*0.18}" r="${w*0.14}" fill="${bodyColor}" stroke="${accentColor}" stroke-width="2"/>
      <!-- Eyes -->
      <ellipse cx="${w*0.43}" cy="${h*0.17}" rx="5" ry="6" fill="${eyeColor}"/>
      <ellipse cx="${w*0.57}" cy="${h*0.17}" rx="5" ry="6" fill="${eyeColor}"/>
      <!-- Crown/horns -->
      <path d="M${w*0.38} ${h*0.08} L${w*0.42} ${h*0.02} L${w*0.5} ${h*0.06} L${w*0.58} ${h*0.02} L${w*0.62} ${h*0.08}" fill="none" stroke="${accentColor}" stroke-width="2"/>
      <!-- Arms -->
      <path d="M${w*0.25} ${h*0.45} Q${w*0.1} ${h*0.55} ${w*0.15} ${h*0.7}" fill="none" stroke="${bodyColor}" stroke-width="6" stroke-linecap="round"/>
      <path d="M${w*0.75} ${h*0.45} Q${w*0.9} ${h*0.55} ${w*0.85} ${h*0.7}" fill="none" stroke="${bodyColor}" stroke-width="6" stroke-linecap="round"/>
      <!-- Boss label -->
      <text x="${w/2}" y="${h*0.95}" text-anchor="middle" font-size="10" fill="${accentColor}" font-family="Georgia,serif" opacity="0.7">BOSS</text>
    </svg>`;
  }

  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" width="${w*1.1}" height="${h*1.1}">
    <!-- Body -->
    <path d="M${w*0.35} ${h*0.85} L${w*0.28} ${h*0.5} Q${w*0.25} ${h*0.38} ${w*0.35} ${h*0.3} L${w*0.42} ${h*0.2} L${w*0.58} ${h*0.2} L${w*0.65} ${h*0.3} Q${w*0.75} ${h*0.38} ${w*0.72} ${h*0.5} L${w*0.65} ${h*0.85}" fill="${bodyColor}" stroke="${accentColor}" stroke-width="1.5"/>
    <!-- Head -->
    <circle cx="${w/2}" cy="${h*0.22}" r="${w*0.13}" fill="${bodyColor}" stroke="${accentColor}" stroke-width="1.5"/>
    <!-- Eyes -->
    <ellipse cx="${w*0.44}" cy="${h*0.21}" rx="3" ry="4" fill="${eyeColor}"/>
    <ellipse cx="${w*0.56}" cy="${h*0.21}" rx="3" ry="4" fill="${eyeColor}"/>
    <!-- Arms -->
    <path d="M${w*0.3} ${h*0.42} Q${w*0.18} ${h*0.5} ${w*0.22} ${h*0.6}" fill="none" stroke="${bodyColor}" stroke-width="4" stroke-linecap="round"/>
    <path d="M${w*0.7} ${h*0.42} Q${w*0.82} ${h*0.5} ${w*0.78} ${h*0.6}" fill="none" stroke="${bodyColor}" stroke-width="4" stroke-linecap="round"/>
  </svg>`;
}

// ── function: createClassIconSVG ──
function createClassIconSVG(classId) {
  const colors = { bastion: '#C9A84C', wither: '#CC6644', shroud: '#7B7BDD' };
  const c = colors[classId] || '#c9a84c';
  let art = '';
  if (classId === 'bastion') {
    art = `<polygon points="40,12 68,28 58,58 22,58 12,28" fill="none" stroke="${c}" stroke-width="3"/>
      <rect x="30" y="60" width="20" height="12" rx="2" fill="${c}" opacity="0.4"/>`;
  } else if (classId === 'wither') {
    art = `<circle cx="40" cy="35" r="22" fill="none" stroke="${c}" stroke-width="3"/>
      <path d="M30 35 Q40 18 50 35 Q40 52 30 35Z" fill="${c}" opacity="0.3"/>`;
  } else {
    art = `<path d="M18 30 Q40 8 62 30 Q40 60 18 30Z" fill="none" stroke="${c}" stroke-width="3"/>
      <circle cx="40" cy="35" r="6" fill="${c}" opacity="0.5"/>`;
  }
  return `<svg viewBox="0 0 80 80" width="80" height="80">${art}</svg>`;
}

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

// ── function: initGame ──
function initGame(classId) {
  const cls = CLASS_DATA[classId];
  G = {
    classId,
    hero: {
      hp: cls.hp, maxHp: cls.hp,
      energy: cls.energy, maxEnergy: cls.energy,
      defense: 0, permDefense: 0,
      handSize: cls.handSize, gold: 0,
      dodgeNext: false, ghostForm: false, blockBurn: false, skipEnemy: false
    },
    deck: [], discardPile: [], hand: [],
    inventory: {}, // cardId -> count
    enemies: [], selectedEnemyIdx: -1,
    floor: 1, room: 0, isBossRoom: false,
    combatLog: [], turnCount: 0,
    phase: 'combat' // combat | shop | transition | victory | defeat
  };

  // Build starter deck (3 copies of each class card)
  const classCards = Object.values(CARD_DATA).filter(c => c.classId === classId);
  classCards.forEach(card => {
    G.inventory[card.id] = 3;
    for (let i = 0; i < 3; i++) G.deck.push({...card});
  });

  shuffle(G.deck);
  startRoom();
}

// ── function: shuffle ──
function shuffle(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.hand.length >= G.hero.handSize + 10) break; // soft cap
    if (G.deck.length === 0) {
      if (G.discardPile.length === 0) break;
      G.deck = [...G.discardPile];
      G.discardPile = [];
      shuffle(G.deck);
      addLog('Deck reshuffled from discard pile');
    }
    if (G.deck.length > 0 && G.hand.length < G.hero.handSize + 5) {
      G.hand.push(G.deck.pop());
    }
  }
}

// ── function: discardHand ──
function discardHand() {
  while (G.hand.length > 0) {
    G.discardPile.push(G.hand.pop());
  }
}

// ── function: playCard ──
function playCard(handIdx) {
  const card = G.hand[handIdx];
  if (!card || G.hero.energy < card.cost) return false;

  G.hero.energy -= card.cost;
  let logMsg = `▶ ${card.name}`;

  // === ATTACK CARDS ===
  if (card.type === 'Attack') {
    const aliveEnemies = getAliveEnemies();
    const targets = card.aoe ? [...aliveEnemies] : 
      (G.selectedEnemyIdx >= 0 && G.enemies[G.selectedEnemyIdx] && G.enemies[G.selectedEnemyIdx].hp > 0 
        ? [G.enemies[G.selectedEnemyIdx]] : []);

    if (targets.length === 0) {
      addLog('No valid target!');
      return false;
    }

    let dmg = card.atk;

    // Backstab bonus: +1 if target has no defense
    if (card.bonusNoArmor && targets[0] && targets[0].defense === 0) {
      dmg += 1;
      logMsg += ' (+1 crit)';
    }

    targets.forEach(enemy => {
      if (!enemy || enemy.hp <= 0) return;
      
      let actualDmg = Math.max(0, dmg - enemy.defense);
      if (card.unblockable) actualDmg = dmg;
      
      enemy.hp -= actualDmg;
      logMsg += ` → ${actualDmg} to ${enemy.name}`;

      // Apply burn
      if (card.burn && !card.aoe) {
        enemy.burn += card.burn;
        addLog(`${enemy.name} burned (${card.burn})`, 'damage');
      }
    });

    if (card.burnAll) {
      aliveEnemies.forEach(e => { e.burn += card.burnAll; });
      addLog(`All enemies burned (+${card.burnAll})`, 'damage');
    }

    // Slam: draw if >=30 HP
    if (card.id === 'BA-A01' && G.hero.hp >= 30) {
      drawCards(1);
      logMsg += ' (+draw)';
    }

    // Silence enemy
    if (card.silenceEnemy && targets[0] && targets[0].hp > 0) {
      targets[0].silenced = true;
      addLog(`${targets[0].name} silenced!`);
    }

    // Vengeance: gain energy
    if (card.gainEnergy) {
      G.hero.energy = Math.min(G.hero.maxEnergy, G.hero.energy + card.gainEnergy);
      logMsg += ` (+${card.gainEnergy}⚡)`;
    }

    addLog(logMsg, 'damage');
  }

  // === DEFENSE CARDS ===
  if (card.type === 'Defense') {
    G.hero.defense += card.def;
    logMsg += ` +${card.def}🛡`;
    
    if (card.permDef) {
      G.hero.permDefense += card.def;
      logMsg += ' (perm)';
    }

    // Taunt: stun enemy
    if (card.stunEnemy && G.selectedEnemyIdx >= 0 && G.enemies[G.selectedEnemyIdx] && G.enemies[G.selectedEnemyIdx].hp > 0) {
      G.enemies[G.selectedEnemyIdx].stun = Math.max(G.enemies[G.selectedEnemyIdx].stun, 1);
      addLog(`${G.enemies[G.selectedEnemyIdx].name} stunned!`);
    }

    // Blur: dodge next attack
    if (card.dodgeNext) G.hero.dodgeNext = true;
    
    // Phantom: ghost form
    if (card.ghostForm) G.hero.ghostForm = true;
    
    // Scorch Shield / Smoke: block burn damage
    if (card.blockBurn) G.hero.blockBurn = true;

    // Endure: heal
    if (card.heal) {
      G.hero.hp = Math.min(G.hero.maxHp, G.hero.hp + card.heal);
      logMsg += ` (+${card.heal}❤)`;
    }

    // Heat Resist / Step: gain energy
    if (card.gainEnergy) {
      G.hero.energy = Math.min(G.hero.maxEnergy, G.hero.energy + card.gainEnergy);
      logMsg += ` (+${card.gainEnergy}⚡)`;
    }

    addLog(logMsg);
  }

  // === OTHER CARDS ===
  if (card.type === 'Other') {
    // Heal effects
    if (card.heal) {
      G.hero.hp = Math.min(G.hero.maxHp, G.hero.hp + card.heal);
      logMsg += ` (+${card.heal}❤)`;
      addLog(logMsg, 'heal');
    }

    // Draw cards
    if (card.drawCard) {
      const count = card.drawCount || 1;
      drawCards(count);
      
      // Search: discard if over 5
      if (card.discardIfOver5 && G.hand.length > 5) {
        while (G.hand.length > 5) G.discardPile.push(G.hand.pop());
        addLog('Hand discarded to 5');
      }
    }

    // Gain energy
    if (card.gainEnergy) {
      G.hero.energy = Math.min(G.hero.maxEnergy, G.hero.energy + card.gainEnergy);
      logMsg += ` (+${card.gainEnergy}⚡)`;
    }

    // Gold
    if (card.gold) {
      G.hero.gold += card.gold;
      addLog(`+${card.gold} gold`, 'gold');
    }

    // Poison target enemy
    if (card.poison && G.selectedEnemyIdx >= 0 && G.enemies[G.selectedEnemyIdx] && G.enemies[G.selectedEnemyIdx].hp > 0) {
      G.enemies[G.selectedEnemyIdx].poison += card.poison;
      addLog(`${G.enemies[G.selectedEnemyIdx].name} poisoned!`, 'damage');
    }

    // Debuff attack
    if (card.debuffAtk && G.selectedEnemyIdx >= 0 && G.enemies[G.selectedEnemyIdx]) {
      G.enemies[G.selectedEnemyIdx].dmg = Math.max(1, G.enemies[G.selectedEnemyIdx].dmg - 1);
      addLog(`${G.enemies[G.selectedEnemyIdx].name} weakened!`);
    }

    // AoE damage (Burn card, Teleport)
    if (card.atkVal && card.aoe) {
      getAliveEnemies().forEach(enemy => {
        let actualDmg = Math.max(0, card.atkVal - enemy.defense);
        if (card.unblockable) actualDmg = card.atkVal;
        enemy.hp -= actualDmg;
      });
      addLog(`${card.name}: ${card.atkVal} dmg to all!`, 'damage');
    }

    // Single target damage (Assassinate)
    if (card.atkVal && !card.aoe) {
      const aliveEnemies = getAliveEnemies();
      const target = G.selectedEnemyIdx >= 0 && G.enemies[G.selectedEnemyIdx] && G.enemies[G.selectedEnemyIdx].hp > 0 
        ? G.enemies[G.selectedEnemyIdx] : (aliveEnemies[0] || null);
      if (target) {
        let actualDmg = Math.max(0, card.atkVal - target.defense);
        if (card.unblockable) actualDmg = card.atkVal;
        target.hp -= actualDmg;
        addLog(`${card.name}: ${actualDmg} dmg to ${target.name}!`, 'damage');
      }
    }

    // Stun enemy
    if (card.stunEnemy && G.selectedEnemyIdx >= 0 && G.enemies[G.selectedEnemyIdx] && G.enemies[G.selectedEnemyIdx].hp > 0) {
      G.enemies[G.selectedEnemyIdx].stun = Math.max(G.enemies[G.selectedEnemyIdx].stun, 1);
      addLog(`${G.enemies[G.selectedEnemyIdx].name} stunned!`);
    }

    // Skip enemy turn flag
    if (card.skipEnemy) G.hero.skipEnemy = true;

    // Defense from Hide card
    if (card.def && !card.type === 'Defense') {
      G.hero.defense += card.def;
    }

    if (!card.heal && !card.atkVal) addLog(logMsg);
  }

  // Discard after effects (Iron Skin, Hide)
  if (card.discardAfter && G.hand.length > 0) {
    const ri = Math.floor(Math.random() * G.hand.length);
    G.discardPile.push(G.hand.splice(ri, 1)[0]);
    addLog('Discarded 1 card');
  }

  // Remove played card from hand
  G.hand.splice(handIdx, 1);
  
  return true;
}

// ── function: endTurn ──
function endTurn() {
  G.turnCount++;
  
  // Discard hand to deck
  discardHand();

  addLog('--- Enemy Turn ---');

  processEnemyTurn();

  if (G.hero.hp <= 0) {
    G.hero.hp = 0;
    showScreen('defeat');
    return;
  }

  // Check if all enemies dead
  const aliveEnemies = getAliveEnemies();
  if (aliveEnemies.length === 0) {
    onEnemyDefeated();
    return;
  }

  // Reset temporary defense AFTER enemy turn
  G.hero.defense = 0;
  G.hero.dodgeNext = false;
  G.hero.ghostForm = false;
  G.hero.blockBurn = false;

  // Start new player turn: recover energy and draw
  G.hero.energy = Math.min(G.hero.maxEnergy, G.hero.energy + 1);
  drawCards(G.hero.handSize - G.hand.length);
  
  addLog(`--- Your Turn (${G.hero.energy}⚡) ---`);
  renderCombat();
}

// ── function: processEnemyTurn ──
function processEnemyTurn() {
  const aliveEnemies = getAliveEnemies();
  if (aliveEnemies.length === 0) return;

  // Boss special mechanics at start of enemy turn
  if (G.isBossRoom && G.enemies[0]) {
    const boss = G.enemies[0];
    const bossTpl = BOSS_TEMPLATES[G.floor - 1];

    // Boss 1: The Gatekeeper - blocks 5 dmg until HP < 20
    if (bossTpl.type === 'tank' && boss.hp >= 20) {
      boss.defense = Math.max(boss.defense, 5);
    }

    // Boss 3: The Void Lord - regen 10 HP per turn
    if (bossTpl.type === 'regen') {
      const oldHp = boss.hp;
      boss.hp = Math.min((boss.maxHp || boss.hp) + 20, boss.hp + 10);
      addLog(`${boss.name} regenerates ${boss.hp - oldHp} HP`, 'heal');
    }
  }

  // Skip enemy turn if flagged
  if (G.hero.skipEnemy) {
    G.hero.skipEnemy = false;
    addLog('Enemies skip their turn!');
    return;
  }

  aliveEnemies.forEach(enemy => {
    // Status effects on enemy at start of their action
    if (enemy.burn > 0 && !G.hero.blockBurn) {
      enemy.hp -= enemy.burn;
      addLog(`${enemy.name} takes ${enemy.burn} burn dmg`, 'damage');
      enemy.burn = 0;
    } else if (enemy.burn > 0 && G.hero.blockBurn) {
      addLog(`Burn blocked by shield!`);
      enemy.burn = 0;
    }

    if (enemy.poison > 0) {
      enemy.hp -= enemy.poison;
      addLog(`${enemy.name} takes ${enemy.poison} poison dmg`, 'damage');
      enemy.poison = 0;
    }

    if (enemy.hp <= 0) return;

    // Stun check
    if (enemy.stun > 0) {
      enemy.stun--;
      addLog(`${enemy.name} is stunned`);
      return;
    }

    // Enemy attacks
    let dmg = enemy.dmg;

    // Boss 2: The Corrupted - deals 20 if player has <3 energy
    if (G.isBossRoom && BOSS_TEMPLATES[G.floor-1].type === 'burst' && G.hero.energy < 3) {
      dmg = 20;
      addLog(`${enemy.name} uses CORRUPTED STRIKE!`, 'damage');
    }

    // Dodge/ghost form check
    if (G.hero.dodgeNext || G.hero.ghostForm) {
      addLog(`${enemy.name}'s attack misses!`);
      return;
    }

    // Apply defense
    let totalDefense = G.hero.defense + G.hero.permDefense;
    let actualDmg = Math.max(0, dmg - totalDefense);
    
    if (actualDmg > 0) {
      G.hero.hp -= actualDmg;
      addLog(`${enemy.name} deals ${actualDmg} damage`, 'damage');
    } else {
      addLog(`${enemy.name}'s attack blocked (${totalDefense}🛡)`);
    }

    // Reset enemy defense for next round
    enemy.defense = 0;
  });

  // Check player death
  if (G.hero.hp <= 0) {
    G.hero.hp = 0;
    addLog('You have fallen...', 'damage');
  }
}

// ── function: onEnemyDefeated ──
function onEnemyDefeated() {
  // Gold reward
  const goldReward = G.isBossRoom ? 15 : 5;
  G.hero.gold += goldReward;
  addLog(`+${goldReward} gold!`, 'gold');

  if (G.isBossRoom) {
    // Boss defeated - advance floor or win
    if (G.floor >= 3) {
      showScreen('victory');
      return;
    }
    G.floor++;
    G.room = 0;
    G.isBossRoom = false;
    
    // Show transition then next room
    showTransition(`Floor ${G.floor - 1} cleared! Descending to Floor ${G.floor}...`, () => {
      startRoom();
    });
  } else {
    G.room++;
    if (G.room >= 3) {
      // Boss time after 3 rooms
      G.isBossRoom = true;
      showTransition(`Floor ${G.floor} cleared! The boss awaits...`, () => {
        startRoom();
      });
    } else {
      // Shop between rooms
      renderShop();
      showScreen('shop');
    }
  }
}

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

// ── function: renderCombat ──
function renderCombat() {
  // HUD
  const floorEl = document.getElementById('hud-floor');
  const goldEl = document.getElementById('hud-gold');
  if (floorEl) {
    floorEl.textContent = `Floor ${G.floor}${G.isBossRoom ? ' - BOSS' : ' - Room ' + G.room}`;
    floorEl.className = G.isBossRoom ? 'boss-warning' : '';
  }
  if (goldEl) goldEl.textContent = `Gold: ${G.hero.gold}`;

  // Player stats with HP bar and energy pips
  const hpEl = document.getElementById('stat-hp');
  const energyEl = document.getElementById('stat-energy');
  const defEl = document.getElementById('stat-defense');
  
  if (hpEl) {
    const hpPct = Math.max(0, G.hero.hp / G.hero.maxHp * 100);
    let hpBarHTML = `<span id="player-hp-bar-bg"><span id="player-hp-bar" style="width:${hpPct}%"></span></span>`;
    hpEl.innerHTML = `HP: ${G.hero.hp}/${G.hero.maxHp}${hpBarHTML}`;
  }
  
  if (energyEl) {
    let pipsHTML = '';
    for (let i = 0; i < G.hero.maxEnergy; i++) {
      pipsHTML += `<span class="energy-pip${i < G.hero.energy ? ' filled' : ''}"></span>`;
    }
    energyEl.innerHTML = `Energy: ${pipsHTML}`;
  }
  
  if (defEl) defEl.textContent = `Defense: ${G.hero.defense + G.hero.permDefense}`;

  // Enemies
  const enemyZone = document.getElementById('enemy-zone');
  if (enemyZone) {
    enemyZone.innerHTML = '';
    G.enemies.forEach((enemy, idx) => {
      const div = document.createElement('div');
      div.className = 'enemy-sprite' + (idx === G.selectedEnemyIdx ? ' selected' : '') + (enemy.hp <= 0 ? ' dead' : '');
      
      const hpPct = Math.max(0, enemy.hp / enemy.maxHp * 100);
      let barClass = '';
      if (hpPct < 25) barClass = ' critical';
      else if (hpPct < 50) barClass = ' low';
      
      let statusesHTML = '';
      if (enemy.burn > 0) statusesHTML += `<span class="status-badge status-burn">🔥${enemy.burn}</span>`;
      if (enemy.poison > 0) statusesHTML += `<span class="status-badge status-poison">☠${enemy.poison}</span>`;
      if (enemy.stun > 0) statusesHTML += `<span class="status-badge status-stun">💫${enemy.stun}</span>`;

      div.innerHTML = `
        ${createEnemySVG(enemy)}
        <div class="enemy-hp-bar-bg"><div class="enemy-hp-bar${barClass}" style="width:${hpPct}%"></div></div>
        <div class="enemy-name">${enemy.name} (${enemy.hp}/${enemy.maxHp})</div>
        <div class="enemy-statuses">${statusesHTML}</div>
      `;
      
      div.addEventListener('click', () => selectEnemy(idx));
      enemyZone.appendChild(div);
    });
  }

  // Hand
  const handArea = document.getElementById('hand-area');
  if (handArea) {
    handArea.innerHTML = '';
    G.hand.forEach((card, idx) => {
      const div = document.createElement('div');
      div.className = 'hand-card' + (G.hero.energy < card.cost ? ' unplayable' : '');
      div.innerHTML = createCardSVG(card);
      
      // Tooltip on hover
      div.addEventListener('mouseenter', () => {
        if (div.querySelector('.card-tooltip')) return;
        const tt = document.createElement('div');
        tt.className = 'card-tooltip';
        tt.innerHTML = `<div class="tt-name">${card.name}</div><div class="tt-type">${card.type} • Cost: ${card.cost}</div>${card.desc}`;
        div.style.position = 'relative';
        div.appendChild(tt);
      });
      div.addEventListener('mouseleave', () => {
        const tt = div.querySelector('.card-tooltip');
        if (tt) tt.remove();
      });

      div.addEventListener('click', () => {
        if (G.hero.energy >= card.cost) {
          playCard(idx);
          
          // Check for enemy deaths after playing card
          const alive = getAliveEnemies();
          if (alive.length === 0) {
            onEnemyDefeated();
            return;
          }
          
          renderCombat();
        }
      });

      handArea.appendChild(div);
    });
  }

  // Combat log
  const logArea = document.getElementById('combat-log-area');
  if (logArea) {
    logArea.innerHTML = G.combatLog.slice(-10).map(entry => 
      `<div class="log-entry ${entry.cls}">${entry.msg}</div>`
    ).join('');
    logArea.scrollTop = logArea.scrollHeight;
  }

  // End turn button state
  const btnEndTurn = document.getElementById('btn-end-turn');
  if (btnEndTurn) {
    btnEndTurn.disabled = false;
  }
}

// ── function: renderShop ──
function renderShop() {
  const goldDisplay = document.getElementById('shop-gold-display');
  if (goldDisplay) goldDisplay.textContent = `Gold: ${G.hero.gold}`;

  const shopCards = document.getElementById('shop-cards');
  if (!shopCards) return;
  
  shopCards.innerHTML = '';
  
  // Show random cards from all classes
  const allCards = Object.values(CARD_DATA);
  const shuffled = [...allCards].sort(() => Math.random() - 0.5).slice(0, 6);

  shuffled.forEach(card => {
    const cost = getShopCost(card.id);
    const canBuy = G.hero.gold >= cost && G.inventory[card.id] < 5;
    
    const div = document.createElement('div');
    div.className = 'shop-card-item';
    if (!canBuy) div.style.opacity = '0.4';
    
    let copyCount = G.inventory[card.id] || 0;
    div.innerHTML = createCardSVG(card);
    
    // Cost badge
    const badge = document.createElement('div');
    badge.className = 'shop-cost-badge';
    badge.textContent = copyCount >= 5 ? 'MAX' : `${cost}g`;
    div.appendChild(badge);

    // Copy count indicator
    if (copyCount > 0) {
      const countBadge = document.createElement('div');
      countBadge.style.cssText = 'position:absolute;top:-8px;left:-8px;background:#5a4a30;color:#c9a84c;font-size:0.7em;padding:2px 5px;border-radius:3px;z-index:5;';
      countBadge.textContent = `×${copyCount}`;
      div.appendChild(countBadge);
    }

    // Tooltip
    div.addEventListener('mouseenter', () => {
      if (div.querySelector('.card-tooltip')) return;
      const tt = document.createElement('div');
      tt.className = 'card-tooltip';
      tt.innerHTML = `<div class="tt-name">${card.name}</div><div class="tt-type">${card.type} • Cost: ${card.cost}</div>${card.desc}<br>Owned: ${copyCount}/5`;
      div.style.position = 'relative';
      div.appendChild(tt);
    });
    div.addEventListener('mouseleave', () => {
      const tt = div.querySelector('.card-tooltip');
      if (tt) tt.remove();
    });

    div.addEventListener('click', () => {
      buyCard(card.id);
    });

    shopCards.appendChild(div);
  });

  // Deck info
  let deckInfo = document.getElementById('deck-info');
  if (!deckInfo) {
    deckInfo = document.createElement('div');
    deckInfo.id = 'deck-info';
    const leaveBtn = document.getElementById('btn-leave-shop');
    if (leaveBtn) leaveBtn.parentNode.insertBefore(deckInfo, leaveBtn);
  }
  const totalCards = G.deck.length;
  deckInfo.textContent = `Deck: ${totalCards} cards | Hand Size: ${G.hero.handSize}`;
}

// ── function: renderClassSelect ──
function renderClassSelect() {
  const container = document.getElementById('class-cards');
  if (!container) return;
  
  container.innerHTML = '';
  
  Object.entries(CLASS_DATA).forEach(([id, cls]) => {
    const div = document.createElement('div');
    div.className = 'class-card';
    div.innerHTML = `
      <div class="class-icon">${createClassIconSVG(id)}</div>
      <h3>${cls.name}</h3>
      <div class="class-theme">"${cls.theme}"</div>
      <div class="class-stats">HP: ${cls.hp} | Energy: ${cls.energy}/turn<br>Hand Size: ${cls.handSize}</div>
    `;
    div.addEventListener('click', () => {
      initGame(id);
    });
    container.appendChild(div);
  });
}

// ── function: addLog ──
function addLog(msg, cls) {
  G.combatLog.push({ msg, cls: cls || '' });
}

// ── function: getAliveEnemies ──
function getAliveEnemies() {
  return G.enemies.filter(e => e.hp > 0);
}

// ── function: selectEnemy ──
function selectEnemy(idx) {
  if (idx >= 0 && idx < G.enemies.length && G.enemies[idx].hp > 0) {
    G.selectedEnemyIdx = idx;
  }
}

// ── function: showTransition ──
function showTransition(text, callback) {
  const transEl = document.querySelector('.transition-text');
  if (transEl) transEl.textContent = text;
  const btn = document.getElementById('btn-continue');
  if (btn) {
    btn.onclick = () => {
      showScreen('combat');
      callback();
    };
  }
  showScreen('transition');
}

// ── function: getShopCost ──
function getShopCost(cardId) {
  const count = G.inventory[cardId] || 0;
  if (count >= 5) return 999; // max copies reached
  if (count >= 3) return 10 + (count - 2); // scaling cost after 3 copies
  return 1; // base cost for first 3 copies
}

// ── function: buyCard ──
function buyCard(cardId) {
  const cost = getShopCost(cardId);
  if (G.hero.gold < cost || G.inventory[cardId] >= 5) return false;
  
  G.hero.gold -= cost;
  G.inventory[cardId]++;
  const cardTpl = CARD_DATA[cardId];
  if (cardTpl) {
    G.deck.push({...cardTpl});
    addLog(`Bought ${cardTpl.name}!`);
  }
  
  renderShop();
  return true;
}

// ── function: leaveShop ──
function leaveShop() {
  startRoom();
}

// ── main ──
// Wire up buttons and initial render
document.getElementById('btn-end-turn').addEventListener('click', () => endTurn());
document.getElementById('btn-leave-shop').addEventListener('click', leaveShop);
document.getElementById('btn-restart').addEventListener('click', () => showScreen('class-select'));
document.getElementById('btn-restart2').addEventListener('click', () => showScreen('class-select'));

renderClassSelect();
showScreen('class-select');
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"composer","sessionId":"98686f68-5168-4c8e-9b77-79e384abea0a","tokensIn":3552409,"tokensOut":43937,"tokensTotal":3596346,"turns":79,"toolCalls":78,"failedToolCalls":0,"timestamp":"2026-07-18T04:46:45.716Z"} -->