← Shadows results

Shadows

Qwen 3.6 35b a3b · composer

Initial view of Shadows

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 121,800 bytes · SHA-256 670974de01db
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shadows of the Abyss</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0a0f; color: #c8c0b8; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; overflow: hidden; }
#app { width: 100vw; height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.screen { display: none; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; }
.screen.active { display: flex; }

/* Class Selection */
#class-select { background: radial-gradient(ellipse at center, #1a1020 0%, #0a0a0f 70%); }
#class-select h1 { font-size: 3rem; color: #d4a050; text-shadow: 0 0 30px #8b4513; margin-bottom: 2rem; letter-spacing: 4px; }
.class-cards { display: flex; gap: 2rem; margin-top: 2rem; }
.class-card { background: linear-gradient(145deg, #1a1520, #0f0a15); border: 2px solid #2a2030; border-radius: 12px; padding: 2rem; width: 280px; cursor: pointer; transition: all 0.3s; text-align: center; }
.class-card:hover { border-color: #d4a050; transform: translateY(-8px); box-shadow: 0 12px 40px rgba(212,160,80,0.2); }
.class-card h2 { font-size: 1.5rem; margin-bottom: 0.5rem; }
.class-card .theme { color: #887766; font-style: italic; margin-bottom: 1rem; }
.class-card .stats { font-size: 0.85rem; color: #665544; line-height: 1.6; }
.class-card .cards-preview { margin-top: 1rem; font-size: 0.8rem; color: #554433; }

/* Map Screen */
#map-screen { background: #0a0a0f; }
#map-container { width: 900px; height: 600px; position: relative; overflow: hidden; }
#map-canvas { width: 100%; height: 100%; }
.map-info { position: absolute; top: 10px; left: 10px; background: rgba(10,10,15,0.9); padding: 8px 16px; border-radius: 8px; font-size: 0.9rem; border: 1px solid #2a2030; }
.map-info span { margin-right: 16px; }
.map-info .gold { color: #d4a050; }
.map-info .hp { color: #c04040; }
.map-info .act { color: #d4a050; }
.map-back { position: absolute; bottom: 10px; left: 10px; background: #1a1520; border: 1px solid #2a2030; color: #887766; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; }
.map-back:hover { border-color: #d4a050; color: #d4a050; }

/* Combat Screen */
#combat-screen { background: radial-gradient(ellipse at bottom, #15101a 0%, #0a0a0f 60%); }
#combat-canvas { width: 1000px; height: 500px; }
#combat-hud { width: 1000px; height: 200px; position: relative; }
#end-turn-btn { position: absolute; right: 10px; top: 10px; background: linear-gradient(145deg, #2a1a10, #1a0f08); border: 2px solid #5a3a20; color: #d4a050; padding: 8px 20px; border-radius: 8px; cursor: pointer; font-size: 1rem; font-weight: bold; transition: all 0.2s; }
#end-turn-btn:hover { border-color: #d4a050; box-shadow: 0 0 15px rgba(212,160,80,0.3); }
#end-turn-btn:disabled { opacity: 0.4; cursor: default; }
.energy-display { position: absolute; right: 10px; top: 50px; font-size: 1.2rem; color: #d4a050; }
.turn-display { position: absolute; right: 10px; top: 75px; font-size: 0.9rem; color: #887766; }

/* Card Hand */
#card-hand { position: absolute; bottom: 0; left: 0; right: 0; height: 200px; display: flex; justify-content: center; align-items: flex-end; gap: 8px; padding: 0 20px; }
.card { width: 140px; height: 190px; background: linear-gradient(145deg, #1e1828, #15101e); border: 2px solid #2a2035; border-radius: 10px; cursor: pointer; transition: all 0.2s; position: relative; flex-shrink: 0; display: flex; flex-direction: column; user-select: none; }
.card:hover { transform: translateY(-20px); z-index: 10; border-color: #d4a050; box-shadow: 0 8px 30px rgba(212,160,80,0.25); }
.card.playable { border-color: #3a5a3a; }
.card.playable:hover { border-color: #5a8a5a; }
.card.unplayable { opacity: 0.5; }
.card .card-cost { position: absolute; top: -8px; left: -8px; width: 30px; height: 30px; background: #1a3a5a; border: 2px solid #3a6a9a; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #6ab0e0; font-size: 1rem; }
.card .card-type { position: absolute; top: -8px; right: -8px; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.65rem; font-weight: bold; }
.card .card-type.attack { background: #5a2020; color: #e06060; }
.card .card-type.skill { background: #203050; color: #6090d0; }
.card .card-type.power { background: #302050; color: #a080d0; }
.card .card-name { text-align: center; font-size: 0.7rem; font-weight: bold; color: #d4a050; padding: 16px 6px 4px; line-height: 1.1; }
.card .card-desc { text-align: center; font-size: 0.6rem; color: #887766; padding: 4px 8px; flex-grow: 1; display: flex; align-items: center; justify-content: center; line-height: 1.3; }
.card .card-rarity { text-align: center; font-size: 0.55rem; color: #554433; padding-bottom: 6px; }
.card-rarity.common { color: #668866; }
.card-rarity.uncommon { color: #6666aa; }
.card-rarity.rare { color: #aa8833; }

/* Enemies */
#enemy-zone { width: 1000px; display: flex; justify-content: center; gap: 40px; }
.enemy { text-align: center; padding: 10px; }
.enemy-sprite { width: 80px; height: 100px; margin: 0 auto 8px; background: #1a1520; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 2.5rem; border: 2px solid #2a2030; }
.enemy .intent-icon { font-size: 1.2rem; margin-bottom: 4px; }
.enemy .hp-bar { width: 100px; height: 8px; background: #1a1010; border-radius: 4px; overflow: hidden; margin: 0 auto; }
.enemy .hp-fill { height: 100%; background: linear-gradient(90deg, #8b2020, #c04040); transition: width 0.3s; }
.enemy .block-indicator { font-size: 0.75rem; color: #6090d0; }
.enemy .intent-text { font-size: 0.7rem; color: #887766; margin-top: 2px; }
.enemy .name { font-size: 0.85rem; color: #d4a050; margin-bottom: 4px; }

/* Player Zone */
#player-zone { display: flex; align-items: center; gap: 20px; margin-bottom: 8px; }
.player-sprite { width: 70px; height: 90px; background: #1a1520; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 2rem; border: 2px solid #2a2030; }
.player-stats { font-size: 0.85rem; }
.player-stats .hp { color: #c04040; }
.player-stats .block { color: #6090d0; }
.player-stats .strength { color: #e0a040; }

/* Power Zone */
#power-zone { display: flex; gap: 8px; margin-bottom: 4px; min-height: 30px; }
.power-card { background: #1a1528; border: 1px solid #3a2a50; border-radius: 6px; padding: 4px 10px; font-size: 0.7rem; color: #a080d0; }

/* Status effects */
#status-zone { display: flex; gap: 6px; margin-bottom: 4px; min-height: 24px; }
.status-badge { background: #1a1520; border: 1px solid #2a2030; border-radius: 4px; padding: 2px 8px; font-size: 0.65rem; }
.status-badge.debuff { color: #c06060; border-color: #4a2020; }
.status-badge.buff { color: #60c060; border-color: #204a20; }
.status-badge.neutral { color: #a0a060; border-color: #3a3a20; }

/* Card Reward */
#reward-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(5,5,10,0.85); display: none; align-items: center; justify-content: center; z-index: 100; }
#reward-overlay.active { display: flex; }
#reward-container { background: linear-gradient(145deg, #1a1525, #0f0a18); border: 2px solid #2a2035; border-radius: 16px; padding: 2rem; text-align: center; }
#reward-container h2 { color: #d4a050; margin-bottom: 1.5rem; }
.reward-cards { display: flex; gap: 1.5rem; justify-content: center; margin-bottom: 1.5rem; }
.reward-card { width: 160px; height: 210px; background: linear-gradient(145deg, #1e1828, #15101e); border: 2px solid #2a2035; border-radius: 10px; cursor: pointer; transition: all 0.2s; display: flex; flex-direction: column; padding: 10px; }
.reward-card:hover { border-color: #d4a050; transform: translateY(-10px); box-shadow: 0 8px 30px rgba(212,160,80,0.2); }
.reward-card .card-cost { position: absolute; top: -8px; left: -8px; width: 30px; height: 30px; background: #1a3a5a; border: 2px solid #3a6a9a; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #6ab0e0; font-size: 1rem; }
.reward-card .card-name { text-align: center; font-size: 0.8rem; font-weight: bold; color: #d4a050; padding: 24px 4px 4px; }
.reward-card .card-desc { text-align: center; font-size: 0.65rem; color: #887766; padding: 4px; flex-grow: 1; display: flex; align-items: center; justify-content: center; line-height: 1.3; }
.reward-card .card-rarity { text-align: center; font-size: 0.6rem; color: #554433; padding-bottom: 4px; }
.skip-btn { background: #1a1520; border: 1px solid #2a2030; color: #887766; padding: 8px 24px; border-radius: 6px; cursor: pointer; font-size: 0.9rem; }
.skip-btn:hover { border-color: #d4a050; color: #d4a050; }

/* Shop */
#shop-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(5,5,10,0.85); display: none; align-items: center; justify-content: center; z-index: 100; }
#shop-overlay.active { display: flex; }
#shop-container { background: linear-gradient(145deg, #1a1525, #0f0a18); border: 2px solid #2a2035; border-radius: 16px; padding: 2rem; text-align: center; max-width: 900px; }
#shop-container h2 { color: #d4a050; margin-bottom: 1.5rem; }
.shop-section { margin-bottom: 1.5rem; }
.shop-section h3 { color: #887766; font-size: 0.9rem; margin-bottom: 0.5rem; }
.shop-items { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
.shop-item { width: 140px; background: #1a1520; border: 2px solid #2a2030; border-radius: 10px; padding: 10px; cursor: pointer; transition: all 0.2s; text-align: center; }
.shop-item:hover { border-color: #d4a050; }
.shop-item .price { color: #d4a050; font-size: 0.8rem; margin-top: 4px; }
.shop-item .item-name { font-size: 0.75rem; color: #d4a050; margin-bottom: 4px; }
.shop-item .item-desc { font-size: 0.6rem; color: #887766; line-height: 1.2; }
.shop-item.disabled { opacity: 0.4; cursor: default; }
.remove-card-btn { background: #1a1520; border: 1px solid #2a2030; color: #887766; padding: 8px 20px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; margin-top: 1rem; }
.remove-card-btn:hover { border-color: #d4a050; color: #d4a050; }
.shop-close { background: #1a1520; border: 1px solid #2a2030; color: #887766; padding: 8px 24px; border-radius: 6px; cursor: pointer; font-size: 0.9rem; margin-top: 1rem; }
.shop-close:hover { border-color: #d4a050; color: #d4a050; }

/* Event */
#event-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(5,5,10,0.85); display: none; align-items: center; justify-content: center; z-index: 100; }
#event-overlay.active { display: flex; }
#event-container { background: linear-gradient(145deg, #1a1525, #0f0a18); border: 2px solid #2a2035; border-radius: 16px; padding: 2rem; text-align: center; max-width: 600px; }
#event-container h2 { color: #d4a050; margin-bottom: 1rem; }
#event-text { color: #c8c0b8; font-size: 0.95rem; line-height: 1.6; margin-bottom: 1.5rem; }
.event-choices { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
.event-choice { background: #1a1520; border: 2px solid #2a2030; color: #c8c0b8; padding: 10px 16px; border-radius: 8px; cursor: pointer; font-size: 0.85rem; text-align: left; min-width: 200px; transition: all 0.2s; }
.event-choice:hover { border-color: #d4a050; color: #d4a050; }
.event-choice .choice-label { font-weight: bold; color: #d4a050; margin-bottom: 4px; }
.event-choice .choice-desc { color: #887766; font-size: 0.75rem; }

/* Rest Site */
#rest-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(5,5,10,0.85); display: none; align-items: center; justify-content: center; z-index: 100; }
#rest-overlay.active { display: flex; }
#rest-container { background: linear-gradient(145deg, #1a1525, #0f0a18); border: 2px solid #2a2035; border-radius: 16px; padding: 2rem; text-align: center; max-width: 500px; }
#rest-container h2 { color: #d4a050; margin-bottom: 1rem; }
.rest-choices { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
.rest-choice { background: #1a1520; border: 2px solid #2a2030; color: #c8c0b8; padding: 14px 20px; border-radius: 8px; cursor: pointer; font-size: 0.85rem; text-align: left; min-width: 150px; transition: all 0.2s; }
.rest-choice:hover { border-color: #d4a050; color: #d4a050; }
.rest-choice .choice-label { font-weight: bold; color: #d4a050; margin-bottom: 4px; }
.rest-choice .choice-desc { color: #887766; font-size: 0.75rem; }

/* Card removal */
#remove-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(5,5,10,0.85); display: none; align-items: center; justify-content: center; z-index: 100; }
#remove-overlay.active { display: flex; }
#remove-container { background: linear-gradient(145deg, #1a1525, #0f0a18); border: 2px solid #2a2035; border-radius: 16px; padding: 2rem; text-align: center; max-width: 700px; }
#remove-container h2 { color: #d4a050; margin-bottom: 1rem; }
#remove-list { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-bottom: 1rem; }
.remove-item { background: #1a1520; border: 1px solid #2a2030; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.75rem; color: #c8c0b8; transition: all 0.2s; }
.remove-item:hover { border-color: #d4a050; color: #d4a050; }

/* Potion bar */
#potion-bar { display: flex; gap: 6px; margin-bottom: 4px; }
.potion-slot { width: 36px; height: 36px; background: #1a1520; border: 1px solid #2a2030; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 1.1rem; cursor: pointer; position: relative; }
.potion-slot:hover { border-color: #d4a050; }
.potion-slot .potion-tooltip { display: none; position: absolute; bottom: 40px; left: 50%; transform: translateX(-50%); background: #0f0a18; border: 1px solid #2a2030; padding: 4px 8px; border-radius: 4px; font-size: 0.6rem; color: #c8c0b8; white-space: nowrap; z-index: 50; }
.potion-slot:hover .potion-tooltip { display: block; }

/* Game Over / Victory */
#gameover-screen, #victory-screen { background: rgba(5,5,10,0.95); }
#gameover-screen h1 { font-size: 3rem; color: #c04040; text-shadow: 0 0 30px #8b2020; }
#victory-screen h1 { font-size: 3rem; color: #d4a050; text-shadow: 0 0 30px #8b6513; }
.restart-btn { background: linear-gradient(145deg, #2a1a10, #1a0f08); border: 2px solid #5a3a20; color: #d4a050; padding: 12px 30px; border-radius: 8px; cursor: pointer; font-size: 1.1rem; margin-top: 2rem; }
.restart-btn:hover { border-color: #d4a050; box-shadow: 0 0 20px rgba(212,160,80,0.3); }

/* Deck info */
#deck-info { position: absolute; top: 10px; right: 10px; background: rgba(10,10,15,0.9); padding: 8px 16px; border-radius: 8px; font-size: 0.8rem; border: 1px solid #2a2030; }
#deck-info span { margin-right: 12px; color: #887766; }

/* Combat log */
#combat-log { position: absolute; top: 10px; left: 10px; width: 250px; max-height: 200px; overflow-y: auto; background: rgba(10,10,15,0.85); padding: 8px; border-radius: 8px; font-size: 0.7rem; color: #887766; border: 1px solid #2a2030; }
#combat-log .log-entry { margin-bottom: 2px; }
#combat-log .log-damage { color: #c06060; }
#combat-log .log-heal { color: #60c060; }
#combat-log .log-block { color: #6090d0; }
#combat-log .log-gold { color: #d4a050; }

/* Map node styles */
.map-node { position: absolute; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.2rem; cursor: pointer; transition: all 0.3s; border: 2px solid #2a2030; z-index: 10; }
.map-node:hover { transform: scale(1.2); }
.map-node.player { background: #d4a050; border-color: #e8b860; box-shadow: 0 0 15px rgba(212,160,80,0.5); }
.map-node.enemy { background: #2a1a10; border-color: #5a3a20; color: #c06060; }
.map-node.elite { background: #3a1a10; border-color: #8a3a20; color: #e06040; box-shadow: 0 0 8px rgba(140,60,20,0.3); }
.map-node.boss { background: #2a0a20; border-color: #6a2060; color: #a040a0; box-shadow: 0 0 12px rgba(120,40,120,0.4); }
.map-node.rest { background: #1a2a10; border-color: #3a5a20; color: #60a040; }
.map-node.shop { background: #2a2a10; border-color: #5a5a20; color: #d4a050; }
.map-node.event { background: #1a1a2a; border-color: #3a3a5a; color: #8080c0; }
.map-node.start { background: #1a2a2a; border-color: #3a5a5a; color: #60a0a0; }
.map-node.visited { opacity: 0.4; }
.map-node.current { border-color: #d4a050; box-shadow: 0 0 10px rgba(212,160,80,0.4); }
.map-node.locked { opacity: 0.2; cursor: default; }
.map-node.locked:hover { transform: none; }

/* Paths between nodes */
.map-path { position: absolute; height: 2px; background: #2a2030; z-index: 5; transform-origin: left center; }
</style>
</head>
<body>
<div id="app"></div>
<script>
'use strict';

// ── const: CARD_TYPES ──
// ===================== CARD DEFINITIONS =====================

const CARD_TYPES = { ATTACK: "attack", SKILL: "skill", POWER: "power" };

// ── const: RARITY ──
const RARITY = { COMMON: "common", UNCOMMON: "uncommon", RARE: "rare" };

// ── const: BASIC_CARDS ──
// Basic cards (all classes)
const BASIC_CARDS = {
  strike: { name: "Strike", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.COMMON, desc: "Deal 6 damage.", upgDesc: "Deal 8 damage.", upgraded: false, exhaust: false },
  defend: { name: "Defend", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Gain 5 Block.", upgDesc: "Gain 8 Block.", upgraded: false, exhaust: false }
};

// ── const: BLOOD_REAPER_CARDS ──
// Blood Reaper cards
const BLOOD_REAPER_CARDS = {
  sanguine_strike: { name: "Sanguine Strike", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.COMMON, desc: "Deal 8 damage. Heal 2 HP.", upgDesc: "Deal 10 damage. Heal 3 HP.", upgraded: false, exhaust: false, heal: 2, dmg: 8 },
  hemorrhage: { name: "Hemorrhage", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.COMMON, desc: "Deal 5 damage. Apply 3 Bleed.", upgDesc: "Deal 6 damage. Apply 4 Bleed.", upgraded: false, exhaust: false, dmg: 5, bleed: 3 },
  bloodletting: { name: "Bloodletting", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.UNCOMMON, desc: "Lose 3 HP. Deal 12 damage.", upgDesc: "Lose 2 HP. Deal 15 damage.", upgraded: false, exhaust: false, dmg: 12, selfDmg: 3 },
  crimson_eruption: { name: "Crimson Eruption", type: CARD_TYPES.ATTACK, cost: 2, rarity: RARITY.RARE, desc: "Deal 8 damage to ALL enemies. Lose 2 HP.", upgDesc: "Deal 10 damage to ALL enemies. Lose 2 HP.", upgraded: false, exhaust: false, dmg: 8, selfDmg: 2, allEnemies: true },
  crimson_shield: { name: "Crimson Shield", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Gain 6 Block. Lose 2 HP.", upgDesc: "Gain 8 Block. Lose 2 HP.", upgraded: false, exhaust: false, block: 6, selfDmg: 2 },
  blood_ward: { name: "Blood Ward", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Gain 3 Block. Gain 1 Regeneration.", upgDesc: "Gain 4 Block. Gain 2 Regeneration.", upgraded: false, exhaust: false, block: 3, regen: 1 },
  sacrificial_barrier: { name: "Sacrificial Barrier", type: CARD_TYPES.SKILL, cost: 2, rarity: RARITY.UNCOMMON, desc: "Gain 10 Block. Lose 5 HP.", upgDesc: "Gain 13 Block. Lose 5 HP.", upgraded: false, exhaust: false, block: 10, selfDmg: 5 },
  vital_surge: { name: "Vital Surge", type: CARD_TYPES.SKILL, cost: 0, rarity: RARITY.RARE, desc: "Gain Block equal to missing HP. Exhaust.", upgDesc: "Gain Block = missing HP + 3. Exhaust.", upgraded: false, exhaust: true, block: 0 },
  life_tap: { name: "Life Tap", type: CARD_TYPES.SKILL, cost: 0, rarity: RARITY.COMMON, desc: "Draw 2 cards. Lose 3 HP.", upgDesc: "Draw 3 cards. Lose 3 HP.", upgraded: false, exhaust: false, draw: 2, selfDmg: 3 },
  rend_flesh: { name: "Rend Flesh", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.UNCOMMON, desc: "Apply 5 Bleed to ALL enemies. Lose 2 HP.", upgDesc: "Apply 7 Bleed to ALL enemies. Lose 2 HP.", upgraded: false, exhaust: false, bleed: 5, selfDmg: 2, allEnemies: true },
  soul_siphon: { name: "Soul Siphon", type: CARD_TYPES.POWER, cost: 2, rarity: RARITY.UNCOMMON, desc: "At start of turn: gain 1 Energy, lose 1 HP.", upgDesc: "At start of turn: gain 1 Energy, lose 1 HP. (Cost 1)", upgraded: false, exhaust: false, power: "soul_siphon", energyGain: 1, selfDmg: 1 },
  blood_pact: { name: "Blood Pact", type: CARD_TYPES.POWER, cost: 1, rarity: RARITY.RARE, desc: "Whenever you lose HP, gain 1 Strength.", upgDesc: "Whenever you lose HP, gain 2 Strength.", upgraded: false, exhaust: false, power: "blood_pact", strengthGain: 1 }
};

// ── const: SHADOWMANCER_CARDS ──
// Shadowmancer cards
const SHADOWMANCER_CARDS = {
  shadow_bolt: { name: "Shadow Bolt", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.COMMON, desc: "Deal 6 damage. Apply 1 Weak.", upgDesc: "Deal 8 damage. Apply 2 Weak.", upgraded: false, exhaust: false, dmg: 6, weak: 1 },
  grasp_of_despair: { name: "Grasp of Despair", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.COMMON, desc: "Deal 4 damage. Apply 1 Vulnerable.", upgDesc: "Deal 5 damage. Apply 2 Vulnerable.", upgraded: false, exhaust: false, dmg: 4, vulnerable: 1 },
  nightmare_lash: { name: "Nightmare Lash", type: CARD_TYPES.ATTACK, cost: 2, rarity: RARITY.UNCOMMON, desc: "Deal 7 damage. If enemy has Weak, deal +7.", upgDesc: "Deal 9 damage. If Weak, deal +9.", upgraded: false, exhaust: false, dmg: 7, weakBonus: 7 },
  eternal_darkness: { name: "Eternal Darkness", type: CARD_TYPES.ATTACK, cost: 2, rarity: RARITY.RARE, desc: "Deal 3 damage to random enemy 3 times.", upgDesc: "Deal 4 damage to random enemy 3 times.", upgraded: false, exhaust: false, dmg: 3, multiHit: 3 },
  cloak_of_shadows: { name: "Cloak of Shadows", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Gain 5 Block. Draw 1 card.", upgDesc: "Gain 7 Block. Draw 1 card.", upgraded: false, exhaust: false, block: 5, draw: 1 },
  haunting_mist: { name: "Haunting Mist", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Gain 7 Block. Apply 1 Weak to ALL enemies.", upgDesc: "Gain 9 Block. Apply 1 Weak to ALL enemies.", upgraded: false, exhaust: false, block: 7, weak: 1, allEnemies: true },
  phantom_armor: { name: "Phantom Armor", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.UNCOMMON, desc: "Gain 8 Block. Next turn gain 2 Dexterity.", upgDesc: "Gain 10 Block. Next turn gain 3 Dexterity.", upgraded: false, exhaust: false, block: 8, dexterityNext: 2 },
  whispering_shroud: { name: "Whispering Shroud", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.RARE, desc: "Gain 4 Block. Next turn gain 4 Block.", upgDesc: "Gain 5 Block. Next turn gain 5 Block.", upgraded: false, exhaust: false, block: 4, blockNextTurn: 4 },
  shadowform: { name: "Shadowform", type: CARD_TYPES.SKILL, cost: 2, rarity: RARITY.UNCOMMON, desc: "Gain 2 Intangible. Exhaust.", upgDesc: "Gain 3 Intangible. Exhaust.", upgraded: false, exhaust: true, intangible: 2 },
  curse_of_misfortune: { name: "Curse of Misfortune", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Enemy loses 3 Strength. Apply 1 Vulnerable.", upgDesc: "Enemy loses 4 Strength. Apply 2 Vulnerable.", upgraded: false, exhaust: false, strengthLoss: 3, vulnerable: 1 },
  dark_ritual: { name: "Dark Ritual", type: CARD_TYPES.SKILL, cost: 0, rarity: RARITY.UNCOMMON, desc: "Gain 2 Energy. Shuffle Dazed into draw pile.", upgDesc: "Gain 2 Energy. Shuffle Dazed into discard.", upgraded: false, exhaust: false, energyGain: 2 },
  umbral_mastery: { name: "Umbral Mastery", type: CARD_TYPES.POWER, cost: 2, rarity: RARITY.RARE, desc: "At start of turn: apply 1 Weak to ALL enemies.", upgDesc: "At start of turn: apply 2 Weak to ALL enemies.", upgraded: false, exhaust: false, power: "umbral_mastery", weak: 1, allEnemies: true }
};

// ── const: HELLFORGED_CARDS ──
// Hellforged cards
const HELLFORGED_CARDS = {
  firebolt: { name: "Firebolt", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.COMMON, desc: "Deal 7 damage. Apply 2 Burn.", upgDesc: "Deal 9 damage. Apply 3 Burn.", upgraded: false, exhaust: false, dmg: 7, burn: 2 },
  hellfire_blast: { name: "Hellfire Blast", type: CARD_TYPES.ATTACK, cost: 2, rarity: RARITY.UNCOMMON, desc: "Deal 9 damage to ALL enemies.", upgDesc: "Deal 12 damage to ALL enemies.", upgraded: false, exhaust: false, dmg: 9, allEnemies: true },
  searing_strike: { name: "Searing Strike", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.COMMON, desc: "Deal 6 damage. Gain 1 Energy next turn.", upgDesc: "Deal 8 damage. Gain 1 Energy next turn.", upgraded: false, exhaust: false, dmg: 6, energyNext: 1 },
  immolate: { name: "Immolate", type: CARD_TYPES.ATTACK, cost: 2, rarity: RARITY.RARE, desc: "Deal 12 damage. Exhaust.", upgDesc: "Deal 16 damage. Exhaust.", upgraded: false, exhaust: true, dmg: 12 },
  obsidian_skin: { name: "Obsidian Skin", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Gain 7 Block.", upgDesc: "Gain 10 Block.", upgraded: false, exhaust: false, block: 7 },
  flame_ward: { name: "Flame Ward", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Gain 5 Block. Apply 2 Burn to random enemy.", upgDesc: "Gain 6 Block. Apply 3 Burn to random enemy.", upgraded: false, exhaust: false, block: 5, burn: 2 },
  demonic_resilience: { name: "Demonic Resilience", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.UNCOMMON, desc: "Gain 4 Block. Heal 2 HP.", upgDesc: "Gain 5 Block. Heal 3 HP.", upgraded: false, exhaust: false, block: 4, heal: 2 },
  brimstone_shield: { name: "Brimstone Shield", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.RARE, desc: "Gain 3 Block. Gain 2 Strength this turn.", upgDesc: "Gain 4 Block. Gain 3 Strength this turn.", upgraded: false, exhaust: false, block: 3, strength: 2 },
  summon_imp: { name: "Summon Imp", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Add Imp Claw to hand. Exhaust.", upgDesc: "Add Imp Claw+ to hand. Exhaust.", upgraded: false, exhaust: true, impClaw: true },
  infernal_pact: { name: "Infernal Pact", type: CARD_TYPES.SKILL, cost: 0, rarity: RARITY.UNCOMMON, desc: "Lose 2 HP. Gain 2 Energy. Add Imp Claw.", upgDesc: "Lose 1 HP. Gain 2 Energy. Add Imp Claw+.", upgraded: false, exhaust: false, selfDmg: 2, energyGain: 2, impClaw: true },
  hellfire_aura: { name: "Hellfire Aura", type: CARD_TYPES.POWER, cost: 2, rarity: RARITY.RARE, desc: "At start of turn: deal 3 damage to ALL enemies.", upgDesc: "At start of turn: deal 5 damage to ALL enemies.", upgraded: false, exhaust: false, power: "hellfire_aura", dmg: 3, allEnemies: true },
  soul_burn: { name: "Soul Burn", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.UNCOMMON, desc: "Apply 8 Burn to enemy. Draw 1 card.", upgDesc: "Apply 10 Burn to enemy. Draw 1 card.", upgraded: false, exhaust: false, burn: 8, draw: 1 }
};

// ── const: COLORLESS_CARDS ──
// Colorless cards (shop)
const COLORLESS_CARDS = {
  flash_of_insight: { name: "Flash of Insight", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Draw 2 cards.", upgDesc: "Draw 3 cards.", upgraded: false, exhaust: false, draw: 2 },
  iron_will: { name: "Iron Will", type: CARD_TYPES.SKILL, cost: 1, rarity: RARITY.COMMON, desc: "Gain 8 Block.", upgDesc: "Gain 11 Block.", upgraded: false, exhaust: false, block: 8 },
  quick_slash: { name: "Quick Slash", type: CARD_TYPES.ATTACK, cost: 1, rarity: RARITY.COMMON, desc: "Deal 7 damage.", upgDesc: "Deal 9 damage.", upgraded: false, exhaust: false, dmg: 7 },
  potent_brew: { name: "Potent Brew", type: CARD_TYPES.SKILL, cost: 0, rarity: RARITY.UNCOMMON, desc: "Gain 1 Energy. Exhaust.", upgDesc: "Gain 2 Energy. Exhaust.", upgraded: false, exhaust: true, energyGain: 1 }
};

// ── const: CURSE_CARDS ──
// Curses
const CURSE_CARDS = {
  wound: { name: "Wound", type: CARD_TYPES.SKILL, cost: 0, rarity: RARITY.COMMON, desc: "Ethereal. Exhaust. Curses clog your deck.", upgDesc: "", upgraded: false, exhaust: true, curse: true, ethereal: true },
  doubt: { name: "Doubt", type: CARD_TYPES.SKILL, cost: 0, rarity: RARITY.COMMON, desc: "At end of turn, lose 1 HP.", upgDesc: "", upgraded: false, exhaust: false, curse: true, endTurnDmg: 1 },
  dazed: { name: "Dazed", type: CARD_TYPES.SKILL, cost: 0, rarity: RARITY.COMMON, desc: "Ethereal. Exhaust. Shuffled into draw pile.", upgDesc: "", upgraded: false, exhaust: true, curse: true, ethereal: true }
};

// ── const: IMP_CLAW ──
// Imp Claw token
const IMP_CLAW = { name: "Imp Claw", type: CARD_TYPES.ATTACK, cost: 0, rarity: RARITY.COMMON, desc: "Deal 4 damage.", upgDesc: "", upgraded: false, exhaust: true, dmg: 4, token: true };

// ── const: IMP_CLAW_PLUS ──
const IMP_CLAW_PLUS = { name: "Imp Claw+", type: CARD_TYPES.ATTACK, cost: 0, rarity: RARITY.COMMON, desc: "Deal 6 damage.", upgDesc: "", upgraded: true, exhaust: true, dmg: 6, token: true };

// ── const: CLASS_CARD_POOLS ──
// All card pools by class
const CLASS_CARD_POOLS = {
  blood_reaper: { ...BASIC_CARDS, ...BLOOD_REAPER_CARDS },
  shadowmancer: { ...BASIC_CARDS, ...SHADOWMANCER_CARDS },
  hellforged: { ...BASIC_CARDS, ...HELLFORGED_CARDS }
};

// ── const: STARTING_DECKS ──
// Starting decks
const STARTING_DECKS = {
  blood_reaper: [
    { id: "strike" }, { id: "strike" }, { id: "strike" }, { id: "strike" }, { id: "strike" },
    { id: "defend" }, { id: "defend" }, { id: "defend" }, { id: "defend" },
    { id: "sanguine_strike" }
  ],
  shadowmancer: [
    { id: "strike" }, { id: "strike" }, { id: "strike" }, { id: "strike" }, { id: "strike" },
    { id: "defend" }, { id: "defend" }, { id: "defend" }, { id: "defend" },
    { id: "shadow_bolt" }
  ],
  hellforged: [
    { id: "strike" }, { id: "strike" }, { id: "strike" }, { id: "strike" }, { id: "strike" },
    { id: "defend" }, { id: "defend" }, { id: "defend" }, { id: "defend" },
    { id: "firebolt" }
  ]
};

// ── function: getCardPool ──
// Card pool for rewards by class (excluding curses and tokens)
function getCardPool(cls) {
  const pool = CLASS_CARD_POOLS[cls];
  return Object.entries(pool).filter(([k, v]) => !v.curse && !v.token).map(([k, v]) => ({ id: k, ...v }));
}

// ── const: ENEMIES ──
// ===================== ENEMY DEFINITIONS =====================

const ENEMIES = {
  // Act 1
  skeleton: { name: "Skeleton Warrior", hp: 22, maxHp: 22, sprite: "💀", act: 1,
    intents: [
      { type: "attack", value: 6 }, { type: "attack", value: 6 }, { type: "defend", value: 8 }
    ] },
  zombie_brute: { name: "Zombie Brute", hp: 28, maxHp: 28, sprite: "🧟", act: 1,
    intents: [
      { type: "attack", value: 8 }, { type: "debuff", debuff: "weak", value: 2 }, { type: "attack", value: 8 }
    ] },
  dark_cultist: { name: "Dark Cultist", hp: 20, maxHp: 20, sprite: "🔮", act: 1,
    intents: [
      { type: "buff", buff: "strength", value: 3 }, { type: "attack", value: 5 }, { type: "attack", value: 5 }
    ] },
  rat_swarm: { name: "Rat Swarm", hp: 15, maxHp: 15, sprite: "🐀", act: 1,
    intents: [
      { type: "multiAttack", hits: 2, value: 3 }, { type: "multiAttack", hits: 2, value: 3 }
    ] },
  tomb_guardian: { name: "Tomb Guardian", hp: 42, maxHp: 42, sprite: "🗿", act: 1, elite: true,
    intents: [
      { type: "shieldBash", value: 6, block: 8 }, { type: "multiAttack", hits: 3, value: 4 }, { type: "attack", value: 12 }
    ] },
  blood_witch: { name: "Blood Witch", hp: 36, maxHp: 36, sprite: "🧙‍♀️", act: 1, elite: true,
    intents: [
      { type: "drainLife", value: 5, heal: 4 }, { type: "debuff", debuff: "vulnerable", value: 1 }, { type: "bloodRitual", value: 7, heal: 4 }
    ] },
  lich_king: { name: "Lich King", hp: 65, maxHp: 65, sprite: "👑", act: 1, boss: true,
    phase1: true, summonMinions: true, maxMinions: 2,
    intents: [
      { type: "summon" }, { type: "attack", value: 8 }, { type: "defend", value: 12 }
    ] },
  // Act 2
  wraith: { name: "Wraith", hp: 30, maxHp: 30, sprite: "👻", act: 2,
    intents: [
      { type: "attackApply", value: 7, debuff: "weak", debuffValue: 1 }, { type: "attack", value: 7 }, { type: "defend", value: 10 }
    ] },
  corrupted_beast: { name: "Corrupted Beast", hp: 38, maxHp: 38, sprite: "🐺", act: 2,
    intents: [
      { type: "attack", value: 12 }, { type: "selfBuff", buff: "block", value: 5, debuff: "vulnerable" }, { type: "attack", value: 12 }
    ] },
  thorned_horror: { name: "Thorned Horror", hp: 25, maxHp: 25, sprite: "🌹", act: 2,
    intents: [
      { type: "multiAttack", hits: 3, value: 4 }, { type: "debuff", debuff: "bleed", value: 3 }, { type: "attack", value: 8 }
    ] },
  witch_doctor: { name: "Witch Doctor", hp: 22, maxHp: 22, sprite: "🧙", act: 2,
    intents: [
      { type: "buffAlly", block: 4, regen: 2 }, { type: "attackApply", value: 6, debuff: "poison", debuffValue: 2 }, { type: "attack", value: 6 }
    ] },
  treant_protector: { name: "Treant Protector", hp: 50, maxHp: 50, sprite: "🌳", act: 2, elite: true,
    intents: [
      { type: "attackBlock", value: 9, block: 6 }, { type: "debuff", debuff: "vulnerable", value: 2 }, { type: "multiAttack", hits: 2, value: 10 }
    ] },
  plague_shaman: { name: "Plague Shaman", hp: 42, maxHp: 42, sprite: "🦠", act: 2, elite: true,
    intents: [
      { type: "debuff", debuff: "poison", value: 4 }, { type: "summonPlagueRat" }, { type: "attack", value: 8 }
    ] },
  eldritch_treant: { name: "Eldritch Treant", hp: 75, maxHp: 75, sprite: "🌿", act: 2, boss: true,
    phase1: true,
    intents: [
      { type: "attackApply", value: 10, debuff: "weak", debuffValue: 2 }, { type: "defendRegen", value: 15, regen: 2 }, { type: "multiAttack", hits: 2, value: 8 }
    ] },
  // Act 3
  hellhound: { name: "Hellhound", hp: 26, maxHp: 26, sprite: "🐕", act: 3,
    intents: [
      { type: "attackApply", value: 8, debuff: "burn", debuffValue: 2 }, { type: "attack", value: 8 }, { type: "defend", value: 9 }
    ] },
  shadow_demon: { name: "Shadow Demon", hp: 30, maxHp: 30, sprite: "😈", act: 3,
    intents: [
      { type: "attackApply", value: 7, debuff: "weak", debuffValue: 1 }, { type: "attack", value: 7 }, { type: "buffSelf", buff: "intangible" }
    ] },
  infernal_imp: { name: "Infernal Imp", hp: 18, maxHp: 18, sprite: "👹", act: 3,
    intents: [
      { type: "attack", value: 5 }, { type: "attack", value: 5 }, { type: "summonImp" }
    ] },
  abyssal_cultist: { name: "Abyssal Cultist", hp: 24, maxHp: 24, sprite: "🕯️", act: 3,
    intents: [
      { type: "buffAllEnemies", buff: "strength", value: 2 }, { type: "attack", value: 7 }, { type: "attack", value: 7 }
    ] },
  flamespawn: { name: "Flamespawn", hp: 48, maxHp: 48, sprite: "🔥", act: 3, elite: true,
    intents: [
      { type: "attackApply", value: 9, debuff: "burn", debuffValue: 3 }, { type: "defendBuff", value: 10, buff: "strength", buffValue: 2 }, { type: "multiAttack", hits: 2, value: 8 }
    ] },
  dread_knight: { name: "Dread Knight", hp: 55, maxHp: 55, sprite: "⚔️", act: 3, elite: true,
    intents: [
      { type: "attack", value: 12 }, { type: "debuff", debuff: "bleed", value: 3 }, { type: "defendHeal", value: 12, heal: 4 }
    ] },
  archdemon: { name: "Archdemon", hp: 85, maxHp: 85, sprite: "👿", act: 3, boss: true,
    phase1: true, summonHellhound: true,
    intents: [
      { type: "summonHellhound" }, { type: "attackApply", value: 12, debuff: "burn", debuffValue: 2 }, { type: "defendBuff", value: 15, buff: "strength", buffValue: 3 }
    ] }
};

// ── const: FLOOR_GROUPS ──
// Enemy groups per floor
const FLOOR_GROUPS = {
  1: {
    2: [{ type: "skeleton" }, { type: "zombie_brute" }],
    3: [{ type: "dark_cultist" }],
    4: "event",
    5: "shop",
    6: [{ type: "rat_swarm" }],
    7: "rest",
    8: [{ type: "skeleton" }, { type: "zombie_brute" }],
    9: [{ type: "tomb_guardian" }, { type: "skeleton" }],
    10: "event",
    11: [{ type: "dark_cultist" }, { type: "rat_swarm" }],
    12: [{ type: "blood_witch" }, { type: "zombie_brute" }],
    13: [{ type: "dark_cultist" }, { type: "dark_cultist" }],
    14: "rest",
    15: [{ type: "lich_king" }]
  },
  2: {
    2: [{ type: "wraith" }, { type: "corrupted_beast" }],
    3: [{ type: "thorned_horror" }],
    4: "event",
    5: "shop",
    6: [{ type: "witch_doctor" }],
    7: "rest",
    8: [{ type: "wraith" }, { type: "thorned_horror" }],
    9: [{ type: "treant_protector" }, { type: "wraith" }],
    10: "event",
    11: [{ type: "corrupted_beast" }, { type: "thorned_horror" }],
    12: [{ type: "plague_shaman" }, { type: "corrupted_beast" }],
    13: [{ type: "wraith" }, { type: "wraith" }],
    14: "rest",
    15: [{ type: "eldritch_treant" }]
  },
  3: {
    2: [{ type: "hellhound" }, { type: "shadow_demon" }],
    3: [{ type: "infernal_imp" }],
    4: "event",
    5: "shop",
    6: [{ type: "abyssal_cultist" }],
    7: "rest",
    8: [{ type: "hellhound" }, { type: "infernal_imp" }],
    9: [{ type: "flamespawn" }, { type: "hellhound" }],
    10: "event",
    11: [{ type: "shadow_demon" }, { type: "infernal_imp" }],
    12: [{ type: "dread_knight" }, { type: "shadow_demon" }],
    13: [{ type: "abyssal_cultist" }, { type: "abyssal_cultist" }],
    14: "rest",
    15: [{ type: "archdemon" }]
  }
};

// ── const: BOSS_RELICS ──
// Boss relics
const BOSS_RELICS = [
  { name: "Cursed Crown", desc: "Gain 1 Energy at start of each turn. Curses can no longer be removed.", effect: "cursed_crown" },
  { name: "Ectoplasm", desc: "Gain 1 Energy. Lose all Gold and gain no more Gold this run.", effect: "ectoplasm" },
  { name: "Philosopher's Stone", desc: "Gain 1 Energy. All enemies start combat with 1 Strength.", effect: "philosophers_stone" },
  { name: "Black Blood", desc: "At start of combat, lose 10% max HP. Whenever you play an Attack, heal 1 HP.", effect: "black_blood" },
  { name: "Shadow Amulet", desc: "Start each turn with 1 Intangible. Reduce max HP by 15.", effect: "shadow_amulet" }
];

// ── const: RELIC_POOL ──
// Relic pool
const RELIC_POOL = [
  { name: "Bloodstone", desc: "Whenever you lose HP from a card, heal 1 HP.", rarity: "common", effect: "bloodstone" },
  { name: "Demon Horns", desc: "At start of combat, add random Imp Claw to hand.", rarity: "common", effect: "demon_horns" },
  { name: "Shadow Cloak", desc: "At start of combat, gain 1 Intangible.", rarity: "rare", effect: "shadow_cloak" },
  { name: "Cursed Amulet", desc: "At start of combat, apply 1 Weak to ALL enemies.", rarity: "common", effect: "cursed_amulet" },
  { name: "Tome of Dark Rituals", desc: "Whenever you play a Power card, draw 1 card.", rarity: "uncommon", effect: "tome_dark_rituals" },
  { name: "Bleeding Edge", desc: "At start of your turn, apply 1 Bleed to a random enemy.", rarity: "common", effect: "bleeding_edge" },
  { name: "Soul Ember", desc: "Whenever you apply Burn to an enemy, deal 2 additional damage.", rarity: "rare", effect: "soul_ember" },
  { name: "Eternal Hourglass", desc: "At start of your turn, if no cards in hand, gain 1 Energy.", rarity: "uncommon", effect: "eternal_hourglass" },
  { name: "Ashen Armor", desc: "At end of combat, heal 6 HP.", rarity: "common", effect: "ashen_armor" },
  { name: "Greedy Imp", desc: "Enemies drop 10 extra Gold.", rarity: "common", effect: "greedy_imp" }
];

// ── const: POTION_DEFS ──
// Potions
const POTION_DEFS = {
  blood_vial: { name: "Blood Vial", icon: "🩸", desc: "Heal 15 HP." },
  firebomb: { name: "Firebomb", icon: "💣", desc: "Deal 10 damage to ALL enemies." },
  dex_tonic: { name: "Dex Tonic", icon: "💪", desc: "Gain 3 Dexterity for 1 turn." },
  energy_elixir: { name: "Energy Elixir", icon: "⚡", desc: "Gain 2 Energy this turn." },
  cursed_brew: { name: "Cursed Brew", icon: "🍷", desc: "Apply 3 Vulnerable to all enemies." },
  bleeding_flask: { name: "Bleeding Flask", icon: "🧪", desc: "Apply 6 Bleed to an enemy." },
  imp_bottle: { name: "Imp-in-a-Bottle", icon: "🍾", desc: "Add Imp Claw+ to hand." },
  smoke_bomb: { name: "Smoke Bomb", icon: "💨", desc: "Escape from non-boss combat." }
};

// ── const: EVENTS ──
// Events
const EVENTS = [
  {
    name: "The Forgotten Altar",
    text: "A blood-stained altar hums with forbidden power.",
    choices: [
      { label: "Offer 15 HP", desc: "Obtain a random Rare relic.", effect: "offer_hp" },
      { label: "Pray", desc: "Heal 10 HP, gain Doubt.", effect: "pray" },
      { label: "Leave", desc: "Nothing.", effect: "leave" }
    ]
  },
  {
    name: "Shadowy Peddler",
    text: "A hooded figure offers you a trade.",
    choices: [
      { label: "Trade a card", desc: "Remove a card, gain 100 Gold.", effect: "trade_card" },
      { label: "Buy wares", desc: "Lose 50 Gold, get Potion + Common relic.", effect: "buy_wares" },
      { label: "Attack", desc: "Fight Shadow Thief, get 75 Gold.", effect: "attack_peddler" }
    ]
  },
  {
    name: "Cursed Spring",
    text: "A spring bubbles with dark, swirling water.",
    choices: [
      { label: "Drink", desc: "Lose 10 HP, remove all Curses.", effect: "drink" },
      { label: "Bottle it", desc: "Obtain Cursed Brew potion.", effect: "bottle" },
      { label: "Leave", desc: "Nothing.", effect: "leave" }
    ]
  },
  {
    name: "Restless Dead",
    text: "Ghosts offer you a bargain in exchange for power.",
    choices: [
      { label: "Accept", desc: "Lose 20 HP, upgrade 2 random cards.", effect: "accept" },
      { label: "Refuse", desc: "Nothing.", effect: "leave" }
    ]
  },
  {
    name: "Impish Gambler",
    text: "A grinning imp challenges you to a game of chance.",
    choices: [
      { label: "Bet 30 Gold", desc: "50% to double or lose it all.", effect: "bet_gold" },
      { label: "Wager a card", desc: "Remove a card; if Curse, gain 100 Gold.", effect: "bet_card" },
      { label: "Leave", desc: "Nothing.", effect: "leave" }
    ]
  },
  {
    name: "Ancient Library",
    text: "Dusty tomes contain forgotten knowledge.",
    choices: [
      { label: "Study", desc: "Gain 1 Energy next combat, lose 5 HP.", effect: "study" },
      { label: "Read forbidden page", desc: "Add random Power card, gain Doubt.", effect: "read_forbidden" },
      { label: "Leave", desc: "Nothing.", effect: "leave" }
    ]
  }
];

// ── const: ACT_NAMES ──
// Act names
const ACT_NAMES = { 1: "The Crypts", 2: "Blighted Woods", 3: "Abyssal Spire" };

// ── function: createPlayer ──
// ===================== GAME ENGINE =====================

function createPlayer(cls) {
  return {
    class: cls,
    hp: 70, maxHp: 70,
    energy: 3, maxEnergy: 3,
    block: 0,
    strength: 0, dexterity: 0,
    deck: [],
    drawPile: [],
    discardPile: [],
    hand: [],
    exhausted: [],
    potions: [],
    relics: [],
    gold: 99,
    // Temporary buffs
    energyNext: 0,
    intangible: 0,
    intangibleStacks: 0,
    // Class-specific
    bloodReaperFirstAttackDmg: false,
    // Combat flags
    firstAttackCostsHp: true
  };
}

// ── function: createEnemy ──
function createEnemy(type, isMinion) {
  const base = ENEMIES[type];
  if (!base) return null;
  const e = {
    type: type,
    name: base.name,
    hp: base.hp,
    maxHp: base.maxHp,
    sprite: base.sprite,
    block: 0,
    intentIndex: 0,
    intent: null,
    isMinion: isMinion || false,
    isElite: base.elite || false,
    isBoss: base.boss || false,
    strength: 0,
    weak: 0,
    vulnerable: 0,
    bleed: 0,
    burn: 0,
    poison: 0,
    regen: 0,
    intangible: 0,
    act: base.act,
    phase1: base.phase1 || false,
    summonMinions: base.summonMinions || false,
    maxMinions: base.maxMinions || 0,
    minionCount: 0,
    summonHellhound: base.summonHellhound || false,
    intents: base.intents || [],
    buffStrength: 0,
    buffBlock: 0,
    buffRegen: 0,
    buffIntangible: 0,
    selfVulnerable: 0
  };
  return e;
}

// ── function: deepCopy ──
function deepCopy(obj) {
  return JSON.parse(JSON.stringify(obj));
}

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

// ── function: getCardDef ──
function getCardDef(id) {
  // Check all card pools
  for (const pool of [BASIC_CARDS, BLOOD_REAPER_CARDS, SHADOWMANCER_CARDS, HELLFORGED_CARDS, COLORLESS_CARDS, CURSE_CARDS]) {
    if (pool[id]) return { ...pool[id], id };
  }
  return null;
}

// ── function: createCombatState ──
function createCombatState(player, enemies) {
  const combat = {
    player: deepCopy(player),
    enemies: enemies.map(e => deepCopy(e)),
    turn: 1,
    phase: "player", // player, enemy, combat_end, combat_start
    log: [],
    selectedEnemy: null,
    pendingEnergyNext: 0,
    pendingBlockNext: 0,
    tempStrength: 0,
    tempDexterity: 0,
    goldReward: 0,
    rewardType: null,
    endedTurn: false
  };
  return combat;
}

// ── function: addLog ──
function addLog(combat, msg, cls) {
  combat.log.push({ msg, cls: cls || "" });
}

// ── function: getEffectiveDmg ──
function getEffectiveDmg(baseDmg, player) {
  let dmg = baseDmg + player.strength;
  // Apply intangible check (for player taking damage)
  return Math.max(0, dmg);
}

// ── function: getEffectiveBlock ──
function getEffectiveBlock(baseBlock, player) {
  return baseBlock + player.dexterity;
}

// ── function: applyWeakToEnemy ──
function applyWeakToEnemy(enemy, stacks) {
  enemy.weak = Math.max(enemy.weak, stacks);
}

// ── function: applyVulnerableToEnemy ──
function applyVulnerableToEnemy(enemy, stacks) {
  enemy.vulnerable = Math.max(enemy.vulnerable, stacks);
}

// ── function: applyBleedToEnemy ──
function applyBleedToEnemy(enemy, stacks) {
  enemy.bleed += stacks;
}

// ── function: applyBurnToEnemy ──
function applyBurnToEnemy(enemy, stacks) {
  enemy.burn += stacks;
}

// ── function: applyPoisonToPlayer ──
function applyPoisonToPlayer(player, stacks) {
  if (!player.poison) player.poison = 0;
  player.poison = Math.max(player.poison || 0, stacks);
}

// ── function: applyWeakToPlayer ──
function applyWeakToPlayer(player, stacks) {
  if (!player.weak) player.weak = 0;
  player.weak = Math.max(player.weak || 0, stacks);
}

// ── function: applyVulnerableToPlayer ──
function applyVulnerableToPlayer(player, stacks) {
  if (!player.vulnerable) player.vulnerable = 0;
  player.vulnerable = Math.max(player.vulnerable || 0, stacks);
}

// ── function: applyBleedToPlayer ──
function applyBleedToPlayer(player, stacks) {
  if (!player.bleed) player.bleed = 0;
  player.bleed += stacks;
}

// ── function: applyBurnToPlayer ──
function applyBurnToPlayer(player, stacks) {
  if (!player.burn) player.burn = 0;
  player.burn += stacks;
}

// ── function: applyRegenToPlayer ──
function applyRegenToPlayer(player, stacks) {
  if (!player.regen) player.regen = 0;
  player.regen += stacks;
}

// ── function: applyIntangibleToPlayer ──
function applyIntangibleToPlayer(player, stacks) {
  if (!player.intangible) player.intangible = 0;
  player.intangible += stacks;
}

// ── function: reduceDebuffDuration ──
function reduceDebuffDuration(debuff, amount) {
  if (debuff && debuff > 0) {
    debuff = Math.max(0, debuff - amount);
  }
  return debuff;
}

// ── function: getCardName ──
function getCardName(card) {
  return card.upgraded ? card.name + "+" : card.name;
}

// ── function: getCardDesc ──
function getCardDesc(card) {
  return card.upgraded ? card.upgDesc : card.desc;
}

// ── function: isCardPlayable ──
function isCardPlayable(card, energy) {
  if (card.exhaust) return card.cost <= energy;
  return card.cost <= energy;
}

// ── function: hasCurse ──
function hasCurse(deck) {
  return deck.some(c => c.curse);
}

// ── function: removeCurseFromDeck ──
function removeCurseFromDeck(combat) {
  const idx = combat.player.deck.findIndex(c => c.curse);
  if (idx >= 0) {
    combat.player.deck.splice(idx, 1);
    return true;
  }
  return false;
}

// ── function: upgradeRandomCards ──
function upgradeRandomCards(combat, count) {
  let upgraded = 0;
  for (let i = 0; i < count && upgraded < count; i++) {
    const nonUpgraded = combat.player.deck.filter(c => !c.curse && !c.upgraded);
    if (nonUpgraded.length === 0) break;
    const card = nonUpgraded[Math.floor(Math.random() * nonUpgraded.length)];
    card.upgraded = true;
    upgraded++;
  }
  return upgraded;
}

// ── function: getRandomPotion ──
function getRandomPotion() {
  const keys = Object.keys(POTION_DEFS);
  return keys[Math.floor(Math.random() * keys.length)];
}

// ── function: getRandomRelic ──
function getRandomRelic() {
  return RELIC_POOL[Math.floor(Math.random() * RELIC_POOL.length)];
}

// ── function: getRandomRareRelic ──
function getRandomRareRelic() {
  const rares = RELIC_POOL.filter(r => r.rarity === "rare");
  if (rares.length > 0) return rares[Math.floor(Math.random() * rares.length)];
  return getRandomRelic();
}

// ── function: getRandomUncommonRelic ──
function getRandomUncommonRelic() {
  const uncommons = RELIC_POOL.filter(r => r.rarity === "uncommon");
  if (uncommons.length > 0) return uncommons[Math.floor(Math.random() * uncommons.length)];
  return getRandomRelic();
}

// ── function: getRandomCommonRelic ──
function getRandomCommonRelic() {
  const commons = RELIC_POOL.filter(r => r.rarity === "common");
  if (commons.length > 0) return commons[Math.floor(Math.random() * commons.length)];
  return getRandomRelic();
}

// ── function: getRandomEvent ──
function getRandomEvent() {
  return EVENTS[Math.floor(Math.random() * EVENTS.length)];
}

// ── function: pickBossRelics ──
function pickBossRelics() {
  return shuffleArray(BOSS_RELICS).slice(0, 3);
}

// ── function: weightedCardPick ──
function weightedCardPick(pool) {
  const common = pool.filter(c => c.rarity === "common");
  const uncommon = pool.filter(c => c.rarity === "uncommon");
  const rare = pool.filter(c => c.rarity === "rare");
  const roll = Math.random();
  let chosen;
  if (roll < 0.10) {
    chosen = rare.length > 0 ? rare[Math.floor(Math.random() * rare.length)] : common[Math.floor(Math.random() * common.length)];
  } else if (roll < 0.40) {
    chosen = uncommon.length > 0 ? uncommon[Math.floor(Math.random() * uncommon.length)] : common[Math.floor(Math.random() * common.length)];
  } else {
    chosen = common[Math.floor(Math.random() * common.length)];
  }
  return { ...chosen };
}

// ── function: pickCardReward ──
function pickCardReward(pool, isElite, isBoss) {
  let options;
  if (isBoss) {
    // At least one rare
    const rares = pool.filter(c => c.rarity === "rare");
    const commons = pool.filter(c => c.rarity === "common");
    const uncommons = pool.filter(c => c.rarity === "uncommon");
    options = [];
    if (rares.length > 0) options.push({ ...rares[Math.floor(Math.random() * rares.length)] });
    const remaining = 2;
    for (let i = 0; i < remaining; i++) {
      const r = Math.random();
      if (r < 0.6) {
        if (commons.length > 0) options.push({ ...commons[Math.floor(Math.random() * commons.length)] });
      } else if (r < 0.9) {
        if (uncommons.length > 0) options.push({ ...uncommons[Math.floor(Math.random() * uncommons.length)] });
      } else {
        if (rares.length > 0) options.push({ ...rares[Math.floor(Math.random() * rares.length)] });
      }
    }
  } else if (isElite) {
    options = [];
    for (let i = 0; i < 3; i++) {
      const r = Math.random();
      if (r < 0.20) {
        const rares = pool.filter(c => c.rarity === "rare");
        if (rares.length > 0) { options.push({ ...rares[Math.floor(Math.random() * rares.length)] }); continue; }
      }
      options.push(weightedCardPick(pool));
    }
  } else {
    options = [];
    for (let i = 0; i < 3; i++) {
      options.push(weightedCardPick(pool));
    }
  }
  return options.slice(0, 3);
}

// ── function: getShopCards ──
function getShopCards(cls) {
  const pool = getCardPool(cls);
  const cards = [];
  for (let i = 0; i < 3; i++) {
    cards.push(weightedCardPick(pool));
  }
  // Add colorless
  const colorless = Object.entries(COLORLESS_CARDS).map(([k, v]) => ({ id: k, ...v }));
  cards.push({ ...colorless[Math.floor(Math.random() * colorless.length)] });
  return cards;
}

// ── function: getShopRelics ──
function getShopRelics() {
  return [getRandomRelic(), getRandomRelic()];
}

// ── function: getShopPotions ──
function getShopPotions() {
  return [getRandomPotion(), getRandomPotion()];
}

// ── function: getShopColorless ──
function getShopColorless() {
  const colorless = Object.entries(COLORLESS_CARDS).map(([k, v]) => ({ id: k, ...v }));
  return colorless[Math.floor(Math.random() * colorless.length)];
}

// ── function: getGoldReward ──
function getGoldReward(isElite, isBoss) {
  if (isBoss) return 50;
  if (isElite) return 25 + Math.floor(Math.random() * 11);
  return 10 + Math.floor(Math.random() * 11);
}

// ── function: getPotionDropChance ──
function getPotionDropChance(isElite) {
  if (isBoss) return 2;
  if (isElite) return Math.random() < 0.7 ? 1 : 0;
  return Math.random() < 0.4 ? 1 : 0;
}

// ── function: getRelicDrop ──
function getRelicDrop(isElite) {
  if (isElite) return getRandomRelic();
  return null;
}

// ── function: getPlayerBlock ──
function getPlayerBlock(combat) {
  return combat.player.block;
}

// ── function: getEnemyBlock ──
function getEnemyBlock(enemy) {
  return enemy.block || 0;
}

// ── function: calcDamage ──
function calcDamage(dmg, enemy) {
  let d = dmg;
  if (enemy.weak > 0) d = Math.floor(d * 0.75);
  return Math.max(0, d);
}

// ── function: calcDamageTaken ──
function calcDamageTaken(dmg, enemy) {
  let d = dmg;
  if (enemy.vulnerable > 0) d = Math.floor(d * 1.5);
  return Math.max(0, d);
}

// ── function: applyDamageToEnemy ──
function applyDamageToEnemy(combat, enemyIdx, dmg, isBleed, isBurn) {
  const enemy = combat.enemies[enemyIdx];
  if (!enemy) return 0;
  
  // Check intangible
  if (enemy.intangible > 0) {
    return 0;
  }
  
  let effectiveDmg = calcDamageTaken(dmg, enemy);
  
  // Soul Ember relic bonus for burn
  if (isBurn && combat.player.relics.some(r => r.effect === "soul_ember")) {
    effectiveDmg += 2;
  }
  
  // Apply block first
  const block = getEnemyBlock(enemy);
  if (block > 0) {
    if (effectiveDmg <= block) {
      enemy.block -= effectiveDmg;
      return 0;
    }
    effectiveDmg -= block;
    enemy.block = 0;
  }
  
  enemy.hp -= effectiveDmg;
  return effectiveDmg;
}

// ── function: applyDamageToPlayer ──
function applyDamageToPlayer(combat, dmg) {
  const p = combat.player;
  
  // Check intangible
  if (p.intangible > 0) {
    return 0;
  }
  
  let effectiveDmg = dmg;
  
  // Apply block
  const block = getPlayerBlock(combat);
  if (block > 0) {
    if (effectiveDmg <= block) {
      p.block -= effectiveDmg;
      return 0;
    }
    effectiveDmg -= block;
    p.block = 0;
  }
  
  // Bloodstone relic
  if (combat.player.relics.some(r => r.effect === "bloodstone")) {
    // Will be handled separately
  }
  
  p.hp -= effectiveDmg;
  return effectiveDmg;
}

// ── function: processStatusEffects ──
function processStatusEffects(combat, isPlayerTurn) {
  // Player turn start effects
  if (isPlayerTurn) {
    // Player bleed/poison at start of turn
    if (combat.player.bleed > 0) {
      const dmg = combat.player.bleed;
      combat.player.hp -= dmg;
      combat.player.bleed = Math.floor(dmg / 2);
      addLog(combat, `Player loses ${dmg} HP from Bleed.`, "log-damage");
    }
    if (combat.player.poison > 0) {
      const dmg = combat.player.poison;
      combat.player.hp -= dmg;
      combat.player.poison = Math.max(0, combat.player.poison - 1);
      addLog(combat, `Player loses ${dmg} HP from Poison.`, "log-damage");
    }
    
    // Regeneration at end of player's previous turn (handled at end of turn)
    
    // Intangible decrement at start of player turn
    if (combat.player.intangible > 0) {
      combat.player.intangible--;
    }
    
    // Weak/Vulnerable decrement
    if (combat.player.weak > 0) combat.player.weak--;
    if (combat.player.vulnerable > 0) combat.player.vulnerable--;
    
    // Enemy status effects at start of their turn
    combat.enemies.forEach((e, idx) => {
      if (e.bleed > 0) {
        const dmg = e.bleed;
        e.hp -= dmg;
        e.bleed = Math.floor(dmg / 2);
        addLog(combat, `${e.name} loses ${dmg} HP from Bleed.`, "log-damage");
      }
      if (e.burn > 0) {
        const dmg = e.burn;
        e.hp -= dmg;
        e.burn = Math.floor(dmg / 2);
        addLog(combat, `${e.name} loses ${dmg} HP from Burn.`, "log-damage");
      }
      if (e.poison > 0) {
        const dmg = e.poison;
        e.hp -= dmg;
        e.poison = Math.max(0, e.poison - 1);
        addLog(combat, `${e.name} loses ${dmg} HP from Poison.`, "log-damage");
      }
      if (e.weak > 0) e.weak--;
      if (e.vulnerable > 0) e.vulnerable--;
      if (e.intangible > 0) e.intangible--;
      
      // Regen
      if (e.regen > 0) {
        e.hp = Math.min(e.maxHp, e.hp + e.regen);
        e.regen--;
      }
      
      // Boss phase transitions
      if (e.isBoss && e.phase1) {
        if (e.hp <= Math.floor(e.maxHp * 0.5)) {
          e.phase1 = false;
          addLog(combat, `${e.name} enters Phase 2!`, "log-damage");
        }
      }
    });
  }
}

// ── function: startCombat ──
function startCombat(player, enemyTypes, isBoss) {
  const enemies = enemyTypes.map(t => createEnemy(t, false));
  const validEnemies = enemies.filter(e => e !== null);
  if (validEnemies.length === 0) return null;
  const combat = createCombatState(player, validEnemies);
  if (!combat) return null;
  combat.isBoss = isBoss || false;
  combat.player.drawPile = [...combat.player.deck];
  combat.player.discardPile = [];
  combat.player.hand = [];
  combat.player.exhausted = [];
  combat.player.block = 0;
  combat.player.energy = combat.player.maxEnergy;
  if (combat.player.relics.some(r => r.effect === "cursed_amulet")) {
    combat.enemies.forEach(e => { if (e.weak === 0) e.weak = 1; });
  }
  if (combat.player.relics.some(r => r.effect === "demon_horns")) {
    const imp = { ...IMP_CLAW };
    combat.player.hand.push(imp);
  }
  if (combat.player.relics.some(r => r.effect === "shadow_cloak")) {
    combat.player.intangible = 1;
  }
  if (combat.player.relics.some(r => r.effect === "philosophers_stone")) {
    combat.enemies.forEach(e => { e.strength += 1; });
  }
  if (combat.player.relics.some(r => r.effect === "black_blood")) {
    combat.player.hp = Math.max(1, combat.player.hp - Math.floor(combat.player.maxHp * 0.1));
    combat.player.blackBloodActive = true;
  }
  if (combat.player.relics.some(r => r.effect === "shadow_amulet")) {
    combat.player.intangible = 1;
    combat.player.maxHp -= 15;
  }
  combat.enemies.forEach(e => { e.intent = e.intents[0]; });
  drawCards(combat, 5);
  processStartOfCombat(combat);
  return combat;
}

// ── function: processStartOfCombat ──
function processStartOfCombat(combat) {
  // Hellfire Aura power
  combat.player.relics = combat.player.relics || [];
  const powers = combat.player.hand.filter(c => c.power || c.exhaust);
  
  // Check for Soul Siphon power (will be handled at start of turn)
}

// ── function: drawCards ──
function drawCards(combat, count) {
  let drawn = 0;
  while (drawn < count) {
    if (combat.player.drawPile.length === 0) {
      if (combat.player.discardPile.length === 0) break;
      // Shuffle discard into draw, excluding exhausted
      combat.player.drawPile = shuffleArray(combat.player.discardPile.filter(c => !c.exhaust));
      combat.player.discardPile = combat.player.discardPile.filter(c => c.exhaust);
    }
    if (combat.player.drawPile.length === 0) break;
    const card = combat.player.drawPile.pop();
    combat.player.hand.push(card);
    drawn++;
  }
  return drawn;
}

// ── function: playCard ──
function playCard(combat, cardIdx, targetEnemyIdx) {
  const card = combat.player.hand[cardIdx];
  if (!card || card.cost > combat.player.energy) return false;
  
  combat.player.energy -= card.cost;
  combat.player.hand.splice(cardIdx, 1);
  
  let logMsg = `Played ${getCardName(card)} `;
  
  // Process card effects
  if (card.dmg) {
    // Attack card
    let totalDmg = card.dmg;
    
    // Hellforged: first attack that costs HP
    if (combat.player.bloodReaperFirstAttackDmg && card.selfDmg && combat.player.relics.some(r => r.name === "Sacrificial Dagger")) {
      totalDmg = card.dmg; // No change to damage
    }
    
    if (card.allEnemies) {
      combat.enemies.forEach((e, idx) => {
        const actualDmg = applyDamageToEnemy(combat, idx, totalDmg, false, false);
        addLog(combat, `${e.name} takes ${actualDmg} damage.`, "log-damage");
      });
    } else {
      const target = targetEnemyIdx !== null ? targetEnemyIdx : 0;
      const actualDmg = applyDamageToEnemy(combat, target, totalDmg, false, false);
      addLog(combat, `${combat.enemies[target].name} takes ${actualDmg} damage.`, "log-damage");
    }
    
    // Apply debuffs
    if (card.weak > 0) {
      if (card.allEnemies) {
        combat.enemies.forEach(e => applyWeakToEnemy(e, card.weak));
      } else {
        const target = targetEnemyIdx !== null ? targetEnemyIdx : 0;
        applyWeakToEnemy(combat.enemies[target], card.weak);
      }
    }
    if (card.vulnerable > 0) {
      if (card.allEnemies) {
        combat.enemies.forEach(e => applyVulnerableToEnemy(e, card.vulnerable));
      } else {
        const target = targetEnemyIdx !== null ? targetEnemyIdx : 0;
        applyVulnerableToEnemy(combat.enemies[target], card.vulnerable);
      }
    }
    if (card.bleed > 0) {
      if (card.allEnemies) {
        combat.enemies.forEach(e => applyBleedToEnemy(e, card.bleed));
      } else {
        const target = targetEnemyIdx !== null ? targetEnemyIdx : 0;
        applyBleedToEnemy(combat.enemies[target], card.bleed);
      }
    }
    if (card.burn > 0) {
      if (card.allEnemies) {
        combat.enemies.forEach(e => applyBurnToEnemy(e, card.burn));
      } else {
        const target = targetEnemyIdx !== null ? targetEnemyIdx : 0;
        applyBurnToEnemy(combat.enemies[target], card.burn);
      }
    }
    
    // Nightime Lash bonus
    if (card.weakBonus) {
      const target = targetEnemyIdx !== null ? targetEnemyIdx : 0;
      if (combat.enemies[target].weak > 0) {
        applyDamageToEnemy(combat, target, card.weakBonus, false, false);
        addLog(combat, `${combat.enemies[target].name} takes ${card.weakBonus} extra damage from Weak!`, "log-damage");
      }
    }
    
    // Multi-hit
    if (card.multiHit) {
      for (let i = 0; i < card.multiHit; i++) {
        const target = targetEnemyIdx !== null ? targetEnemyIdx : Math.floor(Math.random() * combat.enemies.length);
        if (combat.enemies[target]) {
          applyDamageToEnemy(combat, target, card.dmg, false, false);
        }
      }
    }
    
    // Drain life
    if (card.heal && card.dmg) {
      combat.player.hp = Math.min(combat.player.maxHp, combat.player.hp + card.heal);
      addLog(combat, `Healed ${card.heal} HP.`, "log-heal");
    }
    
    // Bloodletting self damage
    if (card.selfDmg) {
      let selfDmg = card.selfDmg;
      // Sacrificial Dagger reduces first HP-costing attack
      if (combat.player.class === "blood_reaper" && combat.player.firstAttackCostsHp && combat.player.relics.some(r => r.name === "Sacrificial Dagger")) {
        selfDmg = Math.max(0, selfDmg - 1);
        combat.player.firstAttackCostsHp = false;
      }
      applySelfDamage(combat, selfDmg);
    }
    
    // Blood Pact power
    if (combat.player.relics.some(r => r.effect === "blood_pact")) {
      // This is handled in applySelfDamage
    }
    
    // Searing Strike: gain energy next turn
    if (card.energyNext) {
      combat.player.energyNext += card.energyNext;
    }
  }
  
  if (card.block) {
    let block = getEffectiveBlock(card.block, combat.player);
    combat.player.block += block;
    logMsg += `+${block} Block. `;
    
    // Blood Ward regen
    if (card.regen) {
      applyRegenToPlayer(combat.player, card.regen);
      addLog(combat, `Gained ${card.regen} Regeneration.`, "log-heal");
    }
    
    // Phantom Armor: next turn dexterity
    if (card.dexterityNext) {
      combat.player.tempDexterity += card.dexterityNext;
    }
    
    // Whispering Shroud: next turn block
    if (card.blockNextTurn) {
      combat.player.pendingBlockNext += card.blockNextTurn;
    }
  }
  
  if (card.draw) {
    const drawn = drawCards(combat, card.draw);
    addLog(combat, `Drew ${drawn} card(s).`, "log-block");
  }
  
  if (card.energyGain) {
    combat.player.energy += card.energyGain;
    addLog(combat, `Gained ${card.energyGain} Energy.`, "log-gold");
  }
  
  if (card.curse) {
    if (card.name === "Doubt") {
      // Doubt stays in hand, handled at end of turn
    } else {
      // Ethereal curses go to exhaust
      combat.player.exhausted.push(card);
    }
  }
  
  // Exhaust
  if (card.exhaust) {
    combat.player.exhausted.push(card);
    addLog(combat, `${getCardName(card)} is Exhausted.`, "");
  }
  
  // Summon Imp
  if (card.impClaw) {
    const imp = card.upgraded ? { ...IMP_CLAW_PLUS } : { ...IMP_CLAW };
    combat.player.hand.push(imp);
    addLog(combat, "Imp Claw added to hand.", "log-block");
  }
  
  // Dark Ritual: shuffle Dazed
  if (card.energyGain && card.id === "dark_ritual") {
    const dazed = { ...CURSE_CARDS.dazed };
    if (card.upgraded) {
      combat.player.discardPile.push(dazed);
    } else {
      combat.player.drawPile.push(dazed);
    }
    addLog(combat, "Dazed shuffled into draw pile.", "log-block");
  }
  
  // Tome of Dark Rituals: draw on Power card play
  if (combat.player.relics.some(r => r.effect === "tome_dark_rituals") && card.type === CARD_TYPES.POWER) {
    drawCards(combat, 1);
    addLog(combat, "Drew 1 card from Tome.", "log-block");
  }
  
  addLog(combat, logMsg, "");
  
  // Check for enemy death
  checkEnemyDeaths(combat);
  
  return true;
}

// ── function: applySelfDamage ──
function applySelfDamage(combat, dmg) {
  const p = combat.player;
  
  // Bloodstone: heal 1 HP when losing HP from card
  const hasBloodstone = p.relics.some(r => r.effect === "bloodstone");
  const hasBloodPact = p.relics.some(r => r.effect === "blood_pact");
  const strengthGain = hasBloodPact ? (p.relics.find(r => r.effect === "blood_pact")?.strengthGain || 1) : 0;
  
  // Apply intangible
  if (p.intangible > 0) {
    dmg = 1;
  }
  
  // Apply block
  if (p.block > 0) {
    if (dmg <= p.block) {
      p.block -= dmg;
      dmg = 0;
    } else {
      dmg -= p.block;
      p.block = 0;
    }
  }
  
  p.hp -= dmg;
  
  if (dmg > 0) {
    addLog(combat, `Lost ${dmg} HP from self-damage.`, "log-damage");
  }
  
  // Bloodstone heal
  if (hasBloodstone && dmg > 0) {
    p.hp = Math.min(p.maxHp, p.hp + 1);
    addLog(combat, "Bloodstone heals 1 HP.", "log-heal");
  }
  
  // Blood Pact: gain strength when losing HP
  if (strengthGain > 0) {
    p.strength += strengthGain;
    addLog(combat, `Blood Pact: gained ${strengthGain} Strength.`, "log-block");
  }
  
  // Black Blood: heal when playing attack
  if (p.blackBloodActive) {
    // This is handled in playCard for attacks
  }
}

// ── function: checkEnemyDeaths ──
function checkEnemyDeaths(combat) {
  combat.enemies = combat.enemies.filter(e => e.hp > 0);
}

// ── function: endPlayerTurn ──
function endPlayerTurn(combat) {
  // Discard hand to discard pile
  combat.player.discardPile.push(...combat.player.hand);
  combat.player.hand = [];
  
  // Process end-of-turn effects
  processEndOfPlayerTurn(combat);
  
  // Reset energy
  combat.player.energy = combat.player.maxEnergy + combat.player.energyNext;
  combat.player.energyNext = 0;
  
  combat.phase = "enemy";
  combat.endedTurn = true;
  
  // Process enemy turns
  processEnemyTurn(combat);
}

// ── function: processEndOfPlayerTurn ──
function processEndOfPlayerTurn(combat) {
  const p = combat.player;
  
  // Doubt curse: lose 1 HP per Doubt in hand
  const doubts = combat.player.discardPile.filter(c => c.name === "Doubt");
  // Actually Doubt stays in hand until discarded, but we discard at end of turn
  // The Doubt effect triggers at end of turn while in hand
  // Since we already discarded, check discard
  // Actually, let me handle Doubt differently - it triggers while in hand at end of turn
  // Let me re-check: the card says "At end of your turn, lose 1 HP"
  // So it should trigger before discarding
}

// ── function: processEndOfTurn ──
function processEndOfTurn(combat) {
  const p = combat.player;
  
  // Doubt curse
  const doubts = p.hand.filter(c => c.name === "Doubt");
  doubts.forEach(() => {
    p.hp -= 1;
    addLog(combat, "Doubt: lose 1 HP.", "log-damage");
  });
  
  // Regeneration
  if (p.regen > 0) {
    p.hp = Math.min(p.maxHp, p.hp + p.regen);
    p.regen--;
    addLog(combat, `Regeneration heals ${p.regen + 1} HP.`, "log-heal");
  }
  
  // Burn at end of player turn
  if (p.burn > 0) {
    const dmg = p.burn;
    p.hp -= dmg;
    p.burn = Math.floor(dmg / 2);
    addLog(combat, `Player loses ${dmg} HP from Burn.`, "log-damage");
  }
  
  // Poison at end of player turn (per spec: start of turn)
  // Actually per spec: Poison at start of turn
  // Burn at end of turn
  
  // Bleed at end of turn (per spec: start of turn)
}

// ── function: processEnemyTurn ──
function processEnemyTurn(combat) {
  let pendingActions = [];
  
  combat.enemies.forEach((e, idx) => {
    if (e.hp <= 0) return;
    
    // Hellfire Aura power (if it exists as a power card in play)
    // This would be tracked separately
    
    const intent = e.intent;
    
    switch (intent.type) {
      case "attack": {
        let dmg = intent.value + e.strength;
        const actualDmg = applyDamageToPlayer(combat, dmg);
        addLog(combat, `${e.name} attacks for ${dmg} damage.`, "log-damage");
        break;
      }
      case "defend": {
        e.block += intent.value;
        addLog(combat, `${e.name} gains ${intent.value} Block.`, "log-block");
        break;
      }
      case "multiAttack": {
        for (let i = 0; i < intent.hits; i++) {
          let dmg = intent.value + e.strength;
          const actualDmg = applyDamageToPlayer(combat, dmg);
          addLog(combat, `${e.name} hits for ${dmg}.`, "log-damage");
        }
        break;
      }
      case "debuff": {
        if (intent.debuff === "weak") {
          applyWeakToPlayer(combat.player, intent.value);
          addLog(combat, `${e.name} applies ${intent.value} Weak.`, "log-damage");
        } else if (intent.debuff === "vulnerable") {
          applyVulnerableToPlayer(combat.player, intent.value);
          addLog(combat, `${e.name} applies ${intent.value} Vulnerable.`, "log-damage");
        } else if (intent.debuff === "bleed") {
          applyBleedToPlayer(combat.player, intent.value);
          addLog(combat, `${e.name} applies ${intent.value} Bleed.`, "log-damage");
        } else if (intent.debuff === "burn") {
          applyBurnToPlayer(combat.player, intent.value);
          addLog(combat, `${e.name} applies ${intent.value} Burn.`, "log-damage");
        } else if (intent.debuff === "poison") {
          applyPoisonToPlayer(combat.player, intent.value);
          addLog(combat, `${e.name} applies ${intent.value} Poison.`, "log-damage");
        }
        break;
      }
      case "attackApply": {
        let dmg = intent.value + e.strength;
        applyDamageToPlayer(combat, dmg);
        addLog(combat, `${e.name} attacks for ${dmg}.`, "log-damage");
        if (intent.debuff) {
          if (intent.debuff === "weak") applyWeakToPlayer(combat.player, intent.debuffValue);
          if (intent.debuff === "burn") applyBurnToPlayer(combat.player, intent.debuffValue);
          if (intent.debuff === "poison") applyPoisonToPlayer(combat.player, intent.debuffValue);
        }
        break;
      }
      case "shieldBash": {
        let dmg = intent.value + e.strength;
        applyDamageToPlayer(combat, dmg);
        e.block += intent.block;
        addLog(combat, `${e.name} attacks for ${dmg} and gains ${intent.block} Block.`, "log-damage");
        break;
      }
      case "drainLife": {
        let dmg = intent.value + e.strength;
        applyDamageToPlayer(combat, dmg);
        e.hp = Math.min(e.maxHp, e.hp + intent.heal);
        addLog(combat, `${e.name} drains ${intent.heal} HP.`, "log-heal");
        break;
      }
      case "bloodRitual": {
        let dmg = intent.value + e.strength;
        applyDamageToPlayer(combat, dmg);
        e.hp = Math.min(e.maxHp, e.hp + intent.heal);
        addLog(combat, `${e.name} performs blood ritual.`, "log-heal");
        break;
      }
      case "buff": {
        if (intent.buff === "strength") {
          e.strength += intent.value;
          addLog(combat, `${e.name} gains ${intent.value} Strength.`, "log-block");
        }
        break;
      }
      case "buffAlly": {
        if (intent.block) {
          e.block += intent.block;
        }
        if (intent.regen) {
          e.regen += intent.regen;
        }
        addLog(combat, `${e.name} buffs itself.`, "log-block");
        break;
      }
      case "buffAllEnemies": {
        combat.enemies.forEach(e => {
          if (intent.buff === "strength") e.strength += intent.value;
        });
        addLog(combat, `${e.name} buffs all enemies.`, "log-block");
        break;
      }
      case "buffSelf": {
        if (intent.buff === "intangible") {
          e.intangible += 1;
          addLog(combat, `${e.name} gains Intangible.`, "log-block");
        }
        break;
      }
      case "selfBuff": {
        if (intent.buff === "block") {
          e.block += intent.value;
        }
        if (intent.debuff) {
          e[selfVulnerable] = 1;
        }
        break;
      }
      case "defend": {
        e.block += intent.value;
        addLog(combat, `${e.name} gains ${intent.value} Block.`, "log-block");
        break;
      }
      case "defendRegen": {
        e.block += intent.value;
        e.regen += intent.regen;
        addLog(combat, `${e.name} gains ${intent.value} Block and ${intent.regen} Regen.`, "log-block");
        break;
      }
      case "defendBuff": {
        e.block += intent.value;
        if (intent.buff === "strength") {
          e.strength += intent.buffValue;
        }
        addLog(combat, `${e.name} gains ${intent.value} Block.`, "log-block");
        break;
      }
      case "defendHeal": {
        e.block += intent.value;
        e.hp = Math.min(e.maxHp, e.hp + intent.heal);
        addLog(combat, `${e.name} gains ${intent.value} Block and heals.`, "log-heal");
        break;
      }
      case "summon": {
        if (e.minionCount < e.maxMinions) {
          const minion = createEnemy("skeleton", true);
          minion.hp = 15;
          minion.maxHp = 15;
          combat.enemies.push(minion);
          e.minionCount++;
          addLog(combat, `${e.name} summons a Skeleton!`, "log-damage");
        } else {
          // Skip summon, attack instead
          let dmg = 8 + e.strength;
          applyDamageToPlayer(combat, dmg);
          addLog(combat, `${e.name} attacks for ${dmg}.`, "log-damage");
        }
        break;
      }
      case "summonPlagueRat": {
        const rat = createEnemy("rat_swarm", true);
        rat.hp = 18;
        rat.maxHp = 18;
        combat.enemies.push(rat);
        addLog(combat, `${e.name} summons a Plague Rat!`, "log-damage");
        break;
      }
      case "summonHellhound": {
        const hound = createEnemy("hellhound", true);
        combat.enemies.push(hound);
        addLog(combat, `${e.name} summons a Hellhound!`, "log-damage");
        break;
      }
      case "summonImp": {
        const imp = createEnemy("infernal_imp", true);
        combat.enemies.push(imp);
        addLog(combat, `${e.name} summons another Imp!`, "log-damage");
        break;
      }
    }
    
    // Advance intent
    e.intentIndex = (e.intentIndex + 1) % e.intents.length;
    e.intent = e.intents[e.intentIndex];
  });
  
  // Check player death
  if (combat.player.hp <= 0) {
    combat.phase = "game_over";
    return;
  }
  
  // Check all enemies dead
  if (combat.enemies.every(e => e.hp <= 0)) {
    combat.phase = "combat_end";
    return;
  }
  
  // Start next player turn
  combat.turn++;
  combat.phase = "player";
  combat.endedTurn = false;
  
  // Start of player turn effects
  startPlayerTurn(combat);
}

// ── function: startPlayerTurn ──
function startPlayerTurn(combat) {
  const p = combat.player;
  
  // Energy from Searing Strike
  if (p.energyNext > 0) {
    p.energy += p.energyNext;
    p.energyNext = 0;
  }
  
  // Eternal Hourglass
  if (p.relics.some(r => r.effect === "eternal_hourglass") && p.hand.length === 0) {
    p.energy += 1;
    addLog(combat, "Eternal Hourglass: +1 Energy.", "log-gold");
  }
  
  // Bleeding Edge
  if (p.relics.some(r => r.effect === "bleeding_edge") && combat.enemies.length > 0) {
    const randomEnemy = combat.enemies[Math.floor(Math.random() * combat.enemies.length)];
    applyBleedToEnemy(randomEnemy, 1);
    addLog(combat, "Bleeding Edge: 1 Bleed applied.", "log-damage");
  }
  
  // Soul Siphon power
  const siphon = p.hand.find(c => c.power === "soul_siphon") || p.exhausted.find(c => c.power === "soul_siphon");
  if (siphon) {
    // Soul Siphon is a power, not in hand. Need to track powers separately.
    // For now, let's track powers in a separate array
  }
  
  // Draw cards
  drawCards(combat, 5);
  
  // Process status effects
  processStatusEffects(combat, true);
  
  // Check player death
  if (combat.player.hp <= 0) {
    combat.phase = "game_over";
  }
}

// ── function: getNextIntent ──
function getNextIntent(enemy) {
  enemy.intentIndex = (enemy.intentIndex + 1) % enemy.intents.length;
  return enemy.intents[enemy.intentIndex];
}

// ── function: isPlayerDead ──
// Check if player is dead
function isPlayerDead(combat) {
  return combat.player.hp <= 0;
}

// ── function: areAllEnemiesDead ──
// Check if all enemies are dead
function areAllEnemiesDead(combat) {
  return combat.enemies.every(e => e.hp <= 0);
}

// ── function: renderClassSelect ──
// ===================== UI RENDERING =====================

function renderClassSelect() {
  const app = document.getElementById("app");
  app.innerHTML = `
    <div class="screen active" id="class-select">
      <h1>SHADOWS OF THE ABYSS</h1>
      <p style="color:#887766; font-style:italic; margin-bottom:1rem;">Choose your cursed path</p>
      <div class="class-cards">
        <div class="class-card" onclick="selectClass('blood_reaper')">
          <h2 style="color:#c04040;">Blood Reaper</h2>
          <div class="theme">Sacrifice HP for power, life steal, Bleed</div>
          <div class="stats">
            Max HP: 70 | Energy: 3/turn<br>
            Signature: Sanguine Strike
          </div>
          <div class="cards-preview">5× Strike, 4× Defend, Sanguine Strike</div>
        </div>
        <div class="class-card" onclick="selectClass('shadowmancer')">
          <h2 style="color:#6060c0;">Shadowmancer</h2>
          <div class="theme">Debuffs (Weak, Vulnerable), Intangible, card manipulation</div>
          <div class="stats">
            Max HP: 70 | Energy: 3/turn<br>
            Signature: Shadow Bolt
          </div>
          <div class="cards-preview">5× Strike, 4× Defend, Shadow Bolt</div>
        </div>
        <div class="class-card" onclick="selectClass('hellforged')">
          <h2 style="color:#c08020;">Hellforged</h2>
          <div class="theme">Fire damage, Burn, summoning Imps, energy manipulation</div>
          <div class="stats">
            Max HP: 70 | Energy: 3/turn<br>
            Signature: Firebolt
          </div>
          <div class="cards-preview">5× Strike, 4× Defend, Firebolt</div>
        </div>
      </div>
    </div>
  `;
}

// ── function: renderMap ──
function renderMap() {
  const app = document.getElementById("app");
  const act = gameState.currentAct;
  const floor = gameState.currentFloor;
  app.innerHTML = `
    <div class="screen active" id="map-screen">
      <div class="map-info">
        <span class="act">Act ${act}: ${ACT_NAMES[act]}</span>
        <span class="gold">Gold: ${gameState.player.gold}</span>
        <span class="hp">HP: ${gameState.player.hp}/${gameState.player.maxHp}</span>
      </div>
      <div id="map-container">
        <canvas id="map-canvas" width="900" height="600"></canvas>
      </div>
      <button class="map-back" onclick="returnToClassSelect()">Return to Title</button>
    </div>
  `;
  drawMap();
}

// ── function: drawMapNode ──
function drawMapNode(ctx, x, y, type, isCurrent, isVisited, isStart) {
  let color, icon, borderColor;
  
  switch (type) {
    case "enemy": color = "#2a1a10"; icon = "⚔️"; borderColor = "#5a3a20"; break;
    case "elite": color = "#3a1a10"; icon = "👹"; borderColor = "#8a3a20"; break;
    case "boss": color = "#2a0a20"; icon = "💀"; borderColor = "#6a2060"; break;
    case "rest": color = "#1a2a10"; icon = "🏕️"; borderColor = "#3a5a20"; break;
    case "shop": color = "#2a2a10"; icon = "🏪"; borderColor = "#5a5a20"; break;
    case "event": color = "#1a1a2a"; icon = "❓"; borderColor = "#3a3a5a"; break;
    case "start": color = "#1a2a2a"; icon = "🏠"; borderColor = "#3a5a5a"; break;
    default: color = "#1a1a2a"; icon = "❓"; borderColor = "#3a3a5a";
  }
  
  // Draw connection line
  ctx.strokeStyle = isVisited ? "#3a3040" : "#2a2030";
  ctx.lineWidth = 2;
  
  // Draw node circle
  ctx.beginPath();
  ctx.arc(x, y, 18, 0, Math.PI * 2);
  ctx.fillStyle = isCurrent ? "#d4a050" : color;
  ctx.fill();
  ctx.strokeStyle = isCurrent ? "#e8b860" : borderColor;
  ctx.lineWidth = isCurrent ? 3 : 2;
  ctx.stroke();
  
  // Draw icon
  ctx.font = "14px serif";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillStyle = isCurrent ? "#000" : "#c8c0b8";
  ctx.fillText(icon, x, y);
  
  // Floor number
  ctx.font = "9px sans-serif";
  ctx.fillStyle = isCurrent ? "#000" : "#887766";
  const floorNum = Math.round((520 - y) / 38) + 2;
  ctx.fillText(floorNum, x, y + 26);
}

// ── function: renderCombat ──
function renderCombat(combat) {
  const app = document.getElementById("app");
  app.innerHTML = `
    <div class="screen active" id="combat-screen">
      <div id="combat-log"></div>
      <div id="enemy-zone"></div>
      <div id="combat-canvas"></div>
      <div id="combat-hud">
        <div id="player-zone"></div>
        <div id="status-zone"></div>
        <div id="power-zone"></div>
        <div id="potion-bar"></div>
        <div class="energy-display">Energy: <span id="energy-val">3</span></div>
        <div class="turn-display">Turn: <span id="turn-val">1</span></div>
        <button id="end-turn-btn" onclick="endTurn()">End Turn</button>
        <div id="card-hand"></div>
      </div>
    </div>
  `;
  renderCombatUI(combat);
}

// ── function: renderCombatUI ──
function renderCombatUI(combat) {
  // Enemies
  const enemyZone = document.getElementById("enemy-zone");
  if (enemyZone) {
    enemyZone.innerHTML = combat.enemies.map((e, idx) => `
      <div class="enemy" onclick="selectTarget(${idx})" style="${combat.selectedEnemy === idx ? 'border: 2px solid #d4a050;' : ''}">
        <div class="name">${e.name}</div>
        <div class="intent-icon">${e.intent ? getIntentIcon(e.intent) : ''}</div>
        <div class="intent-text">${e.intent ? getIntentText(e.intent) : ''}</div>
        <div class="enemy-sprite">${e.sprite}</div>
        <div class="hp-bar"><div class="hp-fill" style="width:${Math.max(0, (e.hp / e.maxHp) * 100)}%"></div></div>
        <div style="font-size:0.7rem; color:#c8c0b8;">${e.hp}/${e.maxHp}</div>
        ${e.block > 0 ? `<div class="block-indicator">🛡️ ${e.block}</div>` : ''}
        ${e.weak > 0 ? `<div style="font-size:0.6rem; color:#c06060;">Weak ${e.weak}</div>` : ''}
        ${e.vulnerable > 0 ? `<div style="font-size:0.6rem; color:#c080c0;">Vuln ${e.vulnerable}</div>` : ''}
        ${e.bleed > 0 ? `<div style="font-size:0.6rem; color:#c06040;">Bleed ${e.bleed}</div>` : ''}
        ${e.burn > 0 ? `<div style="font-size:0.6rem; color:#c04020;">Burn ${e.burn}</div>` : ''}
      </div>
    `).join('');
  }
  
  // Player zone
  const playerZone = document.getElementById("player-zone");
  if (playerZone) {
    const p = combat.player;
    playerZone.innerHTML = `
      <div class="player-sprite">${getClassSprite(p.class)}</div>
      <div class="player-stats">
        <div class="hp">❤️ ${p.hp}/${p.maxHp}</div>
        <div class="block">🛡️ ${p.block}</div>
        <div class="strength">💪 ${p.strength}</div>
        <div style="font-size:0.75rem; color:#665544;">${getDeckInfo(p)}</div>
      </div>
    `;
  }
  
  // Status zone
  const statusZone = document.getElementById("status-zone");
  if (statusZone) {
    const p = combat.player;
    const statuses = [];
    if (p.weak > 0) statuses.push({ label: `Weak ${p.weak}`, type: "debuff" });
    if (p.vulnerable > 0) statuses.push({ label: `Vuln ${p.vulnerable}`, type: "debuff" });
    if (p.bleed > 0) statuses.push({ label: `Bleed ${p.bleed}`, type: "debuff" });
    if (p.burn > 0) statuses.push({ label: `Burn ${p.burn}`, type: "debuff" });
    if (p.poison > 0) statuses.push({ label: `Poison ${p.poison}`, type: "debuff" });
    if (p.regen > 0) statuses.push({ label: `Regen ${p.regen}`, type: "buff" });
    if (p.intangible > 0) statuses.push({ label: `Intangible ${p.intangible}`, type: "buff" });
    statusZone.innerHTML = statuses.map(s => `<span class="status-badge ${s.type}">${s.label}</span>`).join('');
  }
  
  // Power zone
  const powerZone = document.getElementById("power-zone");
  if (powerZone) {
    const powers = combat.player.hand.filter(c => c.power || c.exhaust);
    // Track powers separately - for now show from hand
    powerZone.innerHTML = '';
  }
  
  // Potion bar
  const potionBar = document.getElementById("potion-bar");
  if (potionBar) {
    const potions = combat.player.potions || [];
    potionBar.innerHTML = potions.map((potionKey, idx) => {
      const def = POTION_DEFS[potionKey];
      return `<div class="potion-slot" onclick="usePotion(${idx})">
        ${def.icon}
        <div class="potion-tooltip">${def.name}: ${def.desc}</div>
      </div>`;
    }).join('');
    for (let i = potions.length; i < 3; i++) {
      potionBar.innerHTML += `<div class="potion-slot"></div>`;
    }
  }
  
  // Energy and turn display
  const energyVal = document.getElementById("energy-val");
  if (energyVal) energyVal.textContent = combat.player.energy;
  const turnVal = document.getElementById("turn-val");
  if (turnVal) turnVal.textContent = combat.turn;
  
  // Card hand
  const cardHand = document.getElementById("card-hand");
  if (cardHand) {
    cardHand.innerHTML = combat.player.hand.map((card, idx) => {
      const playable = card.cost <= combat.player.energy && !card.curse;
      const rarityClass = card.rarity || "common";
      return `<div class="card ${playable ? 'playable' : 'unplayable'}" onclick="playCardFromHand(${idx})" title="${getCardName(card)}: ${getCardDesc(card)}">
        <div class="card-cost">${card.cost}</div>
        <div class="card-type ${card.type}">${card.type === 'attack' ? '⚔' : card.type === 'skill' ? '★' : '◆'}</div>
        <div class="card-name">${getCardName(card)}</div>
        <div class="card-desc">${getCardDesc(card)}</div>
        <div class="card-rarity ${rarityClass}">${card.rarity || ''}</div>
      </div>`;
    }).join('');
  }
  
  // Combat log
  const combatLog = document.getElementById("combat-log");
  if (combatLog) {
    combatLog.innerHTML = combat.log.slice(-20).map(l => `<div class="log-entry ${l.cls}">${l.msg}</div>`).join('');
    combatLog.scrollTop = combatLog.scrollHeight;
  }
}

// ── function: getIntentIcon ──
function getIntentIcon(intent) {
  if (!intent) return "";
  switch (intent.type) {
    case "attack": return "⚔️";
    case "defend": return "🛡️";
    case "multiAttack": return "⚔️⚔️";
    case "debuff": return "💫";
    case "buff": return "✨";
    case "summon": return "👻";
    case "drainLife": return "🩸";
    case "bloodRitual": return "🩸";
    case "attackApply": return "⚔️";
    case "shieldBash": return "⚔️🛡️";
    case "defendRegen": return "🛡️✨";
    case "defendBuff": return "🛡️✨";
    case "defendHeal": return "🛡️💚";
    case "buffAlly": return "✨";
    case "buffAllEnemies": return "✨";
    case "buffSelf": return "✨";
    case "selfBuff": return "✨";
    case "summonPlagueRat": return "🐀";
    case "summonHellhound": return "🐕";
    case "summonImp": return "👹";
    default: return "❓";
  }
}

// ── function: getIntentText ──
function getIntentText(intent) {
  if (!intent) return "";
  switch (intent.type) {
    case "attack": return `Attack ${intent.value}`;
    case "defend": return `Defend ${intent.value}`;
    case "multiAttack": return `Multi ${intent.hits}×${intent.value}`;
    case "debuff": return `${intent.debuff} ${intent.value}`;
    case "buff": return `Buff ${intent.buff} ${intent.value}`;
    case "summon": return "Summon";
    case "drainLife": return `Drain ${intent.value} (+${intent.heal})`;
    case "bloodRitual": return `Blood ${intent.value} (+${intent.heal})`;
    case "attackApply": return `Attack ${intent.value} + ${intent.debuff}`;
    case "shieldBash": return `Bash ${intent.value} (+${intent.block} Block)`;
    case "defendRegen": return `Defend ${intent.value} (+${intent.regen} Regen)`;
    case "defendBuff": return `Defend ${intent.value} (+${intent.buffValue} ${intent.buff})`;
    case "defendHeal": return `Defend ${intent.value} (+${intent.heal})`;
    default: return "";
  }
}

// ── function: getClassSprite ──
function getClassSprite(cls) {
  switch (cls) {
    case "blood_reaper": return "🔴";
    case "shadowmancer": return "🟣";
    case "hellforged": return "🟠";
    default: return "⚫";
  }
}

// ── function: getDeckInfo ──
function getDeckInfo(player) {
  const draw = player.drawPile.length;
  const discard = player.discardPile.length;
  const exhaust = player.exhausted.length;
  return `Deck: ${draw} | Discard: ${discard} | Exhaust: ${exhaust}`;
}

// ── function: renderCardReward ──
function renderCardReward(options, isElite, isBoss) {
  const overlay = document.getElementById("reward-overlay");
  if (!overlay) {
    const app = document.getElementById("app");
    app.innerHTML += `
      <div class="screen active" id="reward-overlay">
        <div id="reward-container">
          <h2>${isBoss ? "Boss Defeated!" : isElite ? "Elite Defeated!" : "Combat Victory!"}</h2>
          <div class="reward-cards" id="reward-cards"></div>
          <button class="skip-btn" onclick="skipReward()">Skip</button>
        </div>
      </div>
    `;
    return;
  }
  
  const rewardCards = document.getElementById("reward-cards");
  if (rewardCards) {
    rewardCards.innerHTML = options.map((card, idx) => {
      const rarityClass = card.rarity || "common";
      return `<div class="reward-card" onclick="chooseReward(${idx})">
        <div class="card-cost">${card.cost}</div>
        <div class="card-name">${getCardName(card)}</div>
        <div class="card-desc">${getCardDesc(card)}</div>
        <div class="card-rarity ${rarityClass}">${card.rarity || ''}</div>
      </div>`;
    }).join('');
  }
  
  overlay.classList.add("active");
}

// ── function: renderShop ──
function renderShop() {
  const cls = gameState.player.class;
  const shopCards = getShopCards(cls);
  const shopRelics = getShopRelics();
  const shopPotions = getShopPotions();
  const shopColorless = getShopColorless();
  
  const overlay = document.getElementById("shop-overlay");
  if (!overlay) {
    const app = document.getElementById("app");
    app.innerHTML += `
      <div class="screen active" id="shop-overlay">
        <div id="shop-container">
          <h2>🏪 Shop</h2>
          <p style="color:#d4a050; margin-bottom:1rem;">Gold: ${gameState.player.gold}</p>
          <div class="shop-section">
            <h3>Class Cards</h3>
            <div class="shop-items" id="shop-cards"></div>
          </div>
          <div class="shop-section">
            <h3>Colorless</h3>
            <div class="shop-items" id="shop-colorless"></div>
          </div>
          <div class="shop-section">
            <h3>Relics</h3>
            <div class="shop-items" id="shop-relics"></div>
          </div>
          <div class="shop-section">
            <h3>Potions</h3>
            <div class="shop-items" id="shop-potions"></div>
          </div>
          <div class="shop-section">
            <button class="remove-card-btn" onclick="showCardRemoval()">Remove Card (75g)</button>
          </div>
          <button class="shop-close" onclick="closeShop()">Leave Shop</button>
        </div>
      </div>
    `;
  }
  
  const shopCardsEl = document.getElementById("shop-cards");
  if (shopCardsEl) {
    shopCardsEl.innerHTML = shopCards.map((card, idx) => {
      const price = card.rarity === "rare" ? 150 : card.rarity === "uncommon" ? 75 : 50;
      const canAfford = gameState.player.gold >= price;
      const rarityClass = card.rarity || "common";
      return `<div class="shop-item ${canAfford ? '' : 'disabled'}" onclick="${canAfford ? `buyShopCard(${idx})` : ''}">
        <div class="item-name">${getCardName(card)}</div>
        <div class="item-desc">${getCardDesc(card)}</div>
        <div class="price">${price}g</div>
      </div>`;
    }).join('');
  }
  
  const shopColorlessEl = document.getElementById("shop-colorless");
  if (shopColorlessEl) {
    const price = 60;
    const canAfford = gameState.player.gold >= price;
    shopColorlessEl.innerHTML = `<div class="shop-item ${canAfford ? '' : 'disabled'}" onclick="${canAfford ? 'buyShopColorless()' : ''}">
      <div class="item-name">${getCardName(shopColorless)}</div>
      <div class="item-desc">${getCardDesc(shopColorless)}</div>
      <div class="price">${price}g</div>
    </div>`;
  }
  
  const shopRelicsEl = document.getElementById("shop-relics");
  if (shopRelicsEl) {
    shopRelicsEl.innerHTML = shopRelics.map((relic, idx) => {
      const canAfford = gameState.player.gold >= 150;
      return `<div class="shop-item ${canAfford ? '' : 'disabled'}" onclick="${canAfford ? `buyShopRelic(${idx})` : ''}">
        <div class="item-name">${relic.name}</div>
        <div class="item-desc">${relic.desc}</div>
        <div class="price">150g</div>
      </div>`;
    }).join('');
  }
  
  const shopPotionsEl = document.getElementById("shop-potions");
  if (shopPotionsEl) {
    shopPotionsEl.innerHTML = shopPotions.map((potionKey, idx) => {
      const def = POTION_DEFS[potionKey];
      const canAfford = gameState.player.gold >= 50;
      return `<div class="shop-item ${canAfford ? '' : 'disabled'}" onclick="${canAfford ? `buyShopPotion(${idx})` : ''}">
        <div class="item-name">${def.icon} ${def.name}</div>
        <div class="item-desc">${def.desc}</div>
        <div class="price">50g</div>
      </div>`;
    }).join('');
  }
  
  overlay.classList.add("active");
}

// ── function: renderEvent ──
function renderEvent(event) {
  const overlay = document.getElementById("event-overlay");
  if (!overlay) {
    const app = document.getElementById("app");
    app.innerHTML += `
      <div class="screen active" id="event-overlay">
        <div id="event-container">
          <h2>${event.name}</h2>
          <div id="event-text"></div>
          <div class="event-choices" id="event-choices"></div>
        </div>
      </div>
    `;
  }
  
  const eventText = document.getElementById("event-text");
  if (eventText) eventText.textContent = event.text;
  
  const eventChoices = document.getElementById("event-choices");
  if (eventChoices) {
    eventChoices.innerHTML = event.choices.map((choice, idx) => `
      <div class="event-choice" onclick="chooseEventChoice(${idx})">
        <div class="choice-label">${choice.label}</div>
        <div class="choice-desc">${choice.desc}</div>
      </div>
    `).join('');
  }
  
  overlay.classList.add("active");
}

// ── function: renderRestSite ──
function renderRestSite() {
  const overlay = document.getElementById("rest-overlay");
  if (!overlay) {
    const app = document.getElementById("app");
    app.innerHTML += `
      <div class="screen active" id="rest-overlay">
        <div id="rest-container">
          <h2>Rest Site</h2>
          <p style="color:#887766; margin-bottom:1rem;">Choose your path</p>
          <div class="rest-choices">
            <div class="rest-choice" onclick="chooseRest('rest')">
              <div class="choice-label">🏕️ Rest</div>
              <div class="choice-desc">Heal 25% max HP (${Math.floor(gameState.player.maxHp * 0.25)} HP)</div>
            </div>
            <div class="rest-choice" onclick="chooseRest('forge')">
              <div class="choice-label">⚒️ Forge</div>
              <div class="choice-desc">Upgrade a random card</div>
            </div>
            <div class="rest-choice" onclick="chooseRest('meditate')">
              <div class="choice-label">🧘 Meditate</div>
              <div class="choice-desc">Remove a card from deck</div>
            </div>
          </div>
        </div>
      </div>
    `;
  }
  overlay.classList.add("active");
}

// ── function: renderBossReward ──
function renderBossReward(relicOptions) {
  const overlay = document.getElementById("reward-overlay");
  if (!overlay) {
    const app = document.getElementById("app");
    app.innerHTML += `
      <div class="screen active" id="reward-overlay">
        <div id="reward-container">
          <h2>👑 Boss Defeated!</h2>
          <p style="color:#d4a050; margin-bottom:1rem;">Choose a boss relic</p>
          <div class="reward-cards" id="reward-cards"></div>
        </div>
      </div>
    `;
  }
  
  const rewardCards = document.getElementById("reward-cards");
  if (rewardCards) {
    rewardCards.innerHTML = relicOptions.map((relic, idx) => `
      <div class="reward-card" onclick="chooseBossRelic(${idx})" style="cursor:pointer;">
        <div class="card-name" style="font-size:0.85rem;">${relic.name}</div>
        <div class="card-desc" style="font-size:0.7rem;">${relic.desc}</div>
      </div>
    `).join('');
  }
  
  overlay.classList.add("active");
}

// ── function: renderGameOver ──
function renderGameOver() {
  const app = document.getElementById("app");
  app.innerHTML = `
    <div class="screen active" id="gameover-screen">
      <h1>☠️ DEFEAT</h1>
      <p style="color:#887766; margin-top:1rem;">Your journey ends in the ${ACT_NAMES[gameState.currentAct]}</p>
      <p style="color:#d4a050; margin-top:0.5rem;">Floor reached: ${gameState.currentFloor}</p>
      <button class="restart-btn" onclick="returnToClassSelect()">Try Again</button>
    </div>
  `;
}

// ── function: renderVictory ──
function renderVictory() {
  const app = document.getElementById("app");
  app.innerHTML = `
    <div class="screen active" id="victory-screen">
      <h1>✨ VICTORY ✨</h1>
      <p style="color:#d4a050; margin-top:1rem; font-size:1.2rem;">You have defeated the Archdemon!</p>
      <p style="color:#887766; margin-top:1rem;">The Abyss is sealed... for now.</p>
      <button class="restart-btn" onclick="returnToClassSelect()">Play Again</button>
    </div>
  `;
}

// ── function: renderCardRemoval ──
function renderCardRemoval() {
  const overlay = document.getElementById("remove-overlay");
  if (!overlay) {
    const app = document.getElementById("app");
    app.innerHTML += `
      <div class="screen active" id="remove-overlay">
        <div id="remove-container">
          <h2>Remove a Card</h2>
          <p style="color:#887766; margin-bottom:1rem;">Choose one card to remove (75g)</p>
          <div id="remove-list"></div>
          <button class="skip-btn" onclick="cancelCardRemoval()">Cancel</button>
        </div>
      </div>
    `;
  }
  
  const removeList = document.getElementById("remove-list");
  if (removeList) {
    removeList.innerHTML = gameState.player.deck.filter(c => !c.curse).map((card, idx) => `
      <div class="remove-item" onclick="confirmCardRemoval(${idx})">${getCardName(card)}</div>
    `).join('');
  }
  
  overlay.classList.add("active");
}

// ── const: gameState ──
const gameState = {
  screen: "class-select",
  player: null,
  currentAct: 1,
  currentFloor: 2,
  combat: null,
  rewardCards: [],
  shopItems: null,
  eventChoice: null,
  restChoice: null,
  bossRelicOptions: null,
  mapScroll: 0
};

// ── function: initGame ──
function initGame() {
  renderClassSelect();
}

// ── function: selectClass ──
function selectClass(cls) {
  gameState.player = createPlayer(cls);
  gameState.currentAct = 1;
  gameState.currentFloor = 2;
  
  const startDeck = STARTING_DECKS[cls];
  gameState.player.deck = startDeck.map(s => {
    const def = getCardDef(s.id);
    return { ...def };
  });
  
  renderMap();
}

// ── function: returnToClassSelect ──
function returnToClassSelect() {
  gameState.player = null;
  gameState.currentAct = 1;
  gameState.currentFloor = 2;
  renderClassSelect();
}

// ── function: advanceMap ──
function advanceMap() {
  gameState.currentFloor++;
  if (gameState.currentFloor > 15) {
    if (gameState.currentAct < 3) {
      gameState.currentAct++;
      gameState.currentFloor = 2;
      renderMap();
    } else {
      renderVictory();
    }
    return;
  }
  
  const act = gameState.currentAct;
  const floor = gameState.currentFloor;
  const groups = FLOOR_GROUPS[act];
  const group = groups[floor];
  
  if (typeof group === "string") {
    handleMapNode(group, null);
  } else if (Array.isArray(group)) {
    handleMapNode(group[0].type, [group[0], group[1]]);
  }
}

// ── function: handleMapNode ──
function handleMapNode(type, branch) {
  const nodeType = getTypeFromNodeOrEnemy(type);
  if (nodeType === "enemy") {
    startEnemyCombat(branch);
  } else if (nodeType === "elite") {
    startEliteCombat(branch);
  } else if (nodeType === "boss") {
    startBossCombat();
  } else if (nodeType === "rest") {
    renderRestSite();
  } else if (nodeType === "shop") {
    openShop();
  } else if (nodeType === "event") {
    openEvent();
  } else {
    gameState.currentFloor++;
    renderMap();
  }
}

// ── function: startEnemyCombat ──
function startEnemyCombat(branch) {
  let enemyTypes;
  if (branch) {
    enemyTypes = branch.map(b => b.type);
  } else {
    const act = gameState.currentAct;
    const actEnemies = Object.entries(ENEMIES).filter(([k, v]) => v.act === act && !v.elite && !v.boss);
    enemyTypes = [actEnemies[Math.floor(Math.random() * actEnemies.length)][0]];
  }
  
  const enemies = enemyTypes.map(t => createEnemy(t, false));
  gameState.combat = startCombat(gameState.player, enemies, false);
  
  if (!gameState.combat) {
    console.error("startCombat returned null! player:", gameState.player?.class, "enemies:", enemyTypes);
    return;
  }
  
  gameState.combat.goldReward = getGoldReward(false, false);
  renderCombat(gameState.combat);
}

// ── function: startEliteCombat ──
function startEliteCombat(branch) {
  let enemyTypes;
  if (branch && branch[0] && branch[0].type === "elite") {
    enemyTypes = [branch[0].type];
  } else {
    const act = gameState.currentAct;
    const actElites = Object.entries(ENEMIES).filter(([k, v]) => v.act === act && v.elite);
    enemyTypes = [actElites[Math.floor(Math.random() * actElites.length)][0]];
  }
  
  const enemies = enemyTypes.map(t => createEnemy(t, false));
  gameState.combat = startCombat(gameState.player, enemies, false);
  gameState.combat.goldReward = getGoldReward(false, false);
  gameState.combat.isElite = true;
  renderCombat(gameState.combat);
}

// ── function: startBossCombat ──
function startBossCombat() {
  const act = gameState.currentAct;
  const bossKey = Object.entries(ENEMIES).find(([k, v]) => v.act === act && v.boss);
  const bossType = bossKey ? bossKey[0] : "archdemon";
  
  const enemies = [createEnemy(bossType, false)];
  gameState.combat = startCombat(gameState.player, enemies, true);
  gameState.combat.goldReward = getGoldReward(false, true);
  gameState.combat.isBoss = true;
  renderCombat(gameState.combat);
}

// ── function: openShop ──
function openShop() { renderShop(); }

// ── function: openEvent ──
function openEvent() { renderEvent(getRandomEvent()); }

// ── function: closeShop ──
function closeShop() {
  const overlay = document.getElementById("shop-overlay");
  if (overlay) overlay.classList.remove("active");
  gameState.currentFloor++;
  renderMap();
}

// ── function: buyShopCard ──
function buyShopCard(idx) {
  const shopCards = getShopCards(gameState.player.class);
  const card = shopCards[idx];
  const price = card.rarity === "rare" ? 150 : card.rarity === "uncommon" ? 75 : 50;
  if (gameState.player.gold >= price) {
    gameState.player.gold -= price;
    gameState.player.deck.push({ ...card });
    renderShop();
  }
}

// ── function: buyShopColorless ──
function buyShopColorless() {
  const card = getShopColorless();
  if (gameState.player.gold >= 60) {
    gameState.player.gold -= 60;
    gameState.player.deck.push({ ...card });
    renderShop();
  }
}

// ── function: buyShopRelic ──
function buyShopRelic(idx) {
  const relics = getShopRelics();
  const relic = relics[idx];
  if (gameState.player.gold >= 150) {
    gameState.player.gold -= 150;
    gameState.player.relics.push({ ...relic });
    renderShop();
  }
}

// ── function: buyShopPotion ──
function buyShopPotion(idx) {
  const potions = getShopPotions();
  const potionKey = potions[idx];
  if (gameState.player.gold >= 50) {
    gameState.player.gold -= 50;
    if (!gameState.player.potions) gameState.player.potions = [];
    if (gameState.player.potions.length < 3) {
      gameState.player.potions.push(potionKey);
      renderShop();
    }
  }
}

// ── function: showCardRemoval ──
function showCardRemoval() {
  if (gameState.player.gold >= 75) renderCardRemoval();
}

// ── function: confirmCardRemoval ──
function confirmCardRemoval(idx) {
  const nonCurseCards = gameState.player.deck.filter(c => !c.curse);
  if (idx < nonCurseCards.length && gameState.player.gold >= 75) {
    gameState.player.gold -= 75;
    const card = nonCurseCards[idx];
    const origIdx = gameState.player.deck.indexOf(card);
    gameState.player.deck.splice(origIdx, 1);
    closeShop();
  }
}

// ── function: cancelCardRemoval ──
function cancelCardRemoval() {
  const overlay = document.getElementById("remove-overlay");
  if (overlay) overlay.classList.remove("active");
}

// ── function: chooseEventChoice ──
function chooseEventChoice(idx) {
  const overlay = document.getElementById("event-overlay");
  if (!overlay) return;
  
  const event = gameState.eventChoice || getRandomEvent();
  const choice = event.choices[idx];
  
  switch (choice.effect) {
    case "offer_hp":
      gameState.player.hp -= 15;
      gameState.player.relics.push(getRandomRareRelic());
      break;
    case "pray":
      gameState.player.hp = Math.min(gameState.player.maxHp, gameState.player.hp + 10);
      gameState.player.deck.push({ ...CURSE_CARDS.doubt });
      break;
    case "leave":
      break;
    case "trade_card": {
      const nonCurse = gameState.player.deck.filter(c => !c.curse);
      if (nonCurse.length > 0) {
        const card = nonCurse[Math.floor(Math.random() * nonCurse.length)];
        const ci = gameState.player.deck.indexOf(card);
        gameState.player.deck.splice(ci, 1);
      }
      gameState.player.gold += 100;
      break;
    }
    case "buy_wares":
      gameState.player.gold -= 50;
      gameState.player.potions = gameState.player.potions || [];
      if (gameState.player.potions.length < 3) gameState.player.potions.push(getRandomPotion());
      gameState.player.relics.push(getRandomCommonRelic());
      break;
    case "attack_peddler":
      const enemies = [createEnemy("infernal_imp", false)];
      gameState.combat = startCombat(gameState.player, enemies, false);
      gameState.combat.goldReward = 75;
      renderCombat(gameState.combat);
      return;
    case "drink":
      gameState.player.hp -= 10;
      while (removeCurseFromDeck({ player: gameState.player })) {}
      break;
    case "bottle":
      gameState.player.potions = gameState.player.potions || [];
      if (gameState.player.potions.length < 3) gameState.player.potions.push("cursed_brew");
      break;
    case "accept":
      gameState.player.hp -= 20;
      upgradeRandomCards({ player: gameState.player }, 2);
      break;
    case "bet_gold":
      if (gameState.player.gold >= 30) {
        gameState.player.gold -= 30;
        if (Math.random() < 0.5) gameState.player.gold += 60;
      }
      break;
    case "bet_card": {
      const nonCurse = gameState.player.deck.filter(c => !c.curse);
      if (nonCurse.length > 0) {
        const card = nonCurse[Math.floor(Math.random() * nonCurse.length)];
        const ci = gameState.player.deck.indexOf(card);
        gameState.player.deck.splice(ci, 1);
      }
      break;
    }
    case "study":
      gameState.player.hp -= 5;
      gameState.player.tempEnergyNext = 1;
      break;
    case "read_forbidden": {
      const pool = getCardPool(gameState.player.class);
      const powers = pool.filter(c => c.type === CARD_TYPES.POWER);
      if (powers.length > 0) {
        const power = powers[Math.floor(Math.random() * powers.length)];
        gameState.player.deck.push({ ...power });
      }
      gameState.player.deck.push({ ...CURSE_CARDS.doubt });
      break;
    }
  }
  
  overlay.classList.remove("active");
  gameState.currentFloor++;
  renderMap();
}

// ── function: chooseRest ──
function chooseRest(choice) {
  const overlay = document.getElementById("rest-overlay");
  if (!overlay) return;
  
  switch (choice) {
    case "rest":
      const heal = Math.floor(gameState.player.maxHp * 0.25);
      gameState.player.hp = Math.min(gameState.player.maxHp, gameState.player.hp + heal);
      break;
    case "forge":
      upgradeRandomCards({ player: gameState.player }, 1);
      break;
    case "meditate":
      showCardRemoval();
      break;
  }
  
  overlay.classList.remove("active");
  gameState.currentFloor++;
  renderMap();
}

// ── function: chooseReward ──
function chooseReward(idx) {
  const overlay = document.getElementById("reward-overlay");
  if (overlay) overlay.classList.remove("active");
  
  const card = gameState.rewardCards[idx];
  if (card) gameState.player.deck.push({ ...card });
  
  gameState.currentFloor++;
  renderMap();
}

// ── function: skipReward ──
function skipReward() {
  const overlay = document.getElementById("reward-overlay");
  if (overlay) overlay.classList.remove("active");
  gameState.currentFloor++;
  renderMap();
}

// ── function: chooseBossRelic ──
function chooseBossRelic(idx) {
  const overlay = document.getElementById("reward-overlay");
  if (overlay) overlay.classList.remove("active");
  
  const relic = gameState.bossRelicOptions[idx];
  if (relic) gameState.player.relics.push({ ...relic });
  
  gameState.player.hp = gameState.player.maxHp;
  
  if (gameState.currentAct < 3) {
    gameState.currentAct++;
    gameState.currentFloor = 2;
    renderMap();
  } else {
    renderVictory();
  }
}

// ── function: playCardFromHand ──
function playCardFromHand(idx) {
  if (!gameState.combat || gameState.combat.phase !== "player") return;
  
  const card = gameState.combat.player.hand[idx];
  if (!card || card.cost > gameState.combat.player.energy) return;
  if (card.name === "Doubt") return;
  if (card.ethereal) return;
  
  playCard(gameState.combat, idx, gameState.combat.selectedEnemy);
  
  if (isPlayerDead(gameState.combat)) { renderGameOver(); return; }
  if (areAllEnemiesDead(gameState.combat)) { handleCombatVictory(); return; }
  
  renderCombatUI(gameState.combat);
}

// ── function: selectTarget ──
function selectTarget(idx) {
  if (gameState.combat) {
    gameState.combat.selectedEnemy = idx;
    renderCombatUI(gameState.combat);
  }
}

// ── function: endTurn ──
function endTurn() {
  if (!gameState.combat || gameState.combat.phase !== "player") return;
  
  endPlayerTurn(gameState.combat);
  
  if (gameState.combat.phase === "game_over") { renderGameOver(); return; }
  if (gameState.combat.phase === "combat_end") { handleCombatVictory(); return; }
  
  renderCombatUI(gameState.combat);
}

// ── function: handleCombatVictory ──
function handleCombatVictory() {
  const combat = gameState.combat;
  const isElite = combat.isElite;
  const isBoss = combat.isBoss;
  
  let gold = combat.goldReward;
  if (gameState.player.relics.some(r => r.effect === "greedy_imp")) gold += 10;
  gameState.player.gold += gold;
  
  const potionCount = getPotionDropChance(isElite);
  for (let i = 0; i < potionCount; i++) {
    const potion = getRandomPotion();
    if (!gameState.player.potions) gameState.player.potions = [];
    if (gameState.player.potions.length < 3) gameState.player.potions.push(potion);
  }
  
  if (isBoss) {
    for (let i = 0; i < 2; i++) {
      const potion = getRandomPotion();
      if (!gameState.player.potions) gameState.player.potions = [];
      if (gameState.player.potions.length < 3) gameState.player.potions.push(potion);
    }
  }
  
  if (gameState.player.relics.some(r => r.effect === "ashen_armor")) {
    gameState.player.hp = Math.min(gameState.player.maxHp, gameState.player.hp + 6);
  }
  
  const pool = getCardPool(gameState.player.class);
  gameState.rewardCards = pickCardReward(pool, isElite, isBoss);
  
  if (isBoss) {
    gameState.bossRelicOptions = pickBossRelics();
    renderBossReward(gameState.bossRelicOptions);
  } else {
    renderCardReward(gameState.rewardCards, isElite, isBoss);
  }
  
  if (isElite) {
    const relic = getRelicDrop(true);
    if (relic) gameState.player.relics.push({ ...relic });
  }
}

// ── function: usePotion ──
function usePotion(idx) {
  if (!gameState.combat || gameState.combat.phase !== "player") return;
  
  const potions = gameState.combat.player.potions;
  if (!potions || idx >= potions.length) return;
  
  const potionKey = potions[idx];
  potions.splice(idx, 1);
  
  switch (potionKey) {
    case "blood_vial":
      gameState.combat.player.hp = Math.min(gameState.combat.player.maxHp, gameState.combat.player.hp + 15);
      break;
    case "firebomb":
      gameState.combat.enemies.forEach(e => applyDamageToEnemy(gameState.combat, gameState.combat.enemies.indexOf(e), 10, false, false));
      break;
    case "dex_tonic":
      gameState.combat.player.dexterity += 3;
      break;
    case "energy_elixir":
      gameState.combat.player.energy += 2;
      break;
    case "cursed_brew":
      gameState.combat.enemies.forEach(e => applyVulnerableToEnemy(e, 3));
      break;
    case "bleeding_flask":
      if (gameState.combat.enemies.length > 0) {
        const target = gameState.combat.selectedEnemy !== null ? gameState.combat.selectedEnemy : 0;
        applyBleedToEnemy(gameState.combat.enemies[target], 6);
      }
      break;
    case "imp_bottle":
      gameState.combat.player.hand.push({ ...IMP_CLAW_PLUS });
      break;
    case "smoke_bomb":
      if (!gameState.combat.isBoss) {
        gameState.combat = null;
        gameState.currentFloor++;
        renderMap();
        return;
      }
      break;
  }
  
  renderCombatUI(gameState.combat);
}

// ── function: getTypeFromEnemy ──
function getTypeFromEnemy(enemyType) {
  const enemy = ENEMIES[enemyType];
  if (!enemy) return "enemy";
  if (enemy.boss) return "boss";
  if (enemy.elite) return "elite";
  return "enemy";
}

// ── function: drawMap ──
function drawMap() {
  const container = document.getElementById("map-container");
  if (!container) return;
  
  // Remove canvas
  const canvas = container.querySelector("canvas");
  if (canvas) canvas.remove();
  
  // Clear previous nodes and paths
  container.querySelectorAll(".map-node, .map-path").forEach(n => n.remove());
  
  const act = gameState.currentAct;
  const floor = gameState.currentFloor;
  const groups = FLOOR_GROUPS[act];
  
  const startX = 450;
  const startY = 520;
  const spacingY = 38;
  
  for (let f = 2; f <= 15; f++) {
    const y = startY - (f - 2) * spacingY;
    const group = groups[f];
    
    if (typeof group === "string") {
      const x = startX;
      createMapNode(container, x, y, group, f === floor, f < floor, f === 2, f, group, startY);
    } else if (Array.isArray(group)) {
      const positions = group.map((g, i) => ({
        x: i === 0 ? startX - 70 : startX + 70,
        y: y,
        type: typeof g === "string" ? g : getTypeFromEnemy(g.type),
        floor: f,
        enemyType: typeof g === "string" ? g : g.type
      }));
      positions.forEach(pos => {
        createMapNode(container, pos.x, pos.y, pos.type, f === floor, f < floor, f === 2, f, pos.enemyType, startY);
      });
    }
  }
  
  // Add click handler to container for event delegation
  container.onclick = (e) => {
    const node = e.target.closest('.map-node');
    if (node) {
      const title = node.title;
      const floorNum = parseInt(title.match(/Floor (\d+)/)?.[1]);
      if (node.classList.contains('visited') || node.classList.contains('player')) {
        gameState.currentFloor = floorNum;
        const groups = FLOOR_GROUPS[gameState.currentAct];
        const group = groups[floorNum];
        if (typeof group === "string") {
          handleMapNode(group, null);
        } else if (Array.isArray(group)) {
          handleMapNode(group[0].type, [group[0], group[1]]);
        }
      }
    }
  };
}

// ── function: getTypeFromNodeOrEnemy ──
function getTypeFromNodeOrEnemy(type) {
  if (["enemy", "elite", "boss", "rest", "shop", "event"].includes(type)) return type;
  // It's an enemy type name - look it up
  const enemy = ENEMIES[type];
  if (enemy) {
    if (enemy.boss) return "boss";
    if (enemy.elite) return "elite";
    return "enemy";
  }
  return "enemy";
}

// ── function: createMapNode ──
function createMapNode(container, x, y, type, isCurrent, isVisited, isStart, floor, enemyType, startY) {
  const node = document.createElement("div");
  let icon, cls;
  switch (type) {
    case "enemy": icon = "⚔️"; cls = "enemy"; break;
    case "elite": icon = "👹"; cls = "elite"; break;
    case "boss": icon = "💀"; cls = "boss"; break;
    case "rest": icon = "🏕️"; cls = "rest"; break;
    case "shop": icon = "🏪"; cls = "shop"; break;
    case "event": icon = "❓"; cls = "event"; break;
    case "start": icon = "🏠"; cls = "start"; break;
    default: icon = "❓"; cls = "event";
  }
  
  node.className = `map-node ${cls}`;
  if (isCurrent) node.classList.add("current");
  if (isVisited) node.classList.add("visited");
  if (isStart) node.classList.add("player");
  if (!isVisited && !isStart) node.classList.add("locked");
  
  node.style.left = (x - 20) + "px";
  node.style.top = (y - 20) + "px";
  node.textContent = icon;
  node.title = `Floor ${floor}: ${type}`;
  node.style.cursor = (isVisited || isStart) ? "pointer" : "default";
  node.style.zIndex = "20";
  
  if (isVisited || isStart) {
    node.onclick = (e) => {
      e.stopPropagation();
      if (isVisited || isStart) {
        gameState.currentFloor = floor;
        const groups = FLOOR_GROUPS[gameState.currentAct];
        const group = groups[floor];
        if (typeof group === "string") {
          handleMapNode(group, null);
        } else if (Array.isArray(group)) {
          handleMapNode(group[0].type, [group[0], group[1]]);
        }
      }
    };
  }
  
  container.appendChild(node);
  
  // Draw path to previous floor
  if (floor > 2) {
    const prevY = startY - (floor - 3) * 38;
    const prevX = 450;
    const path = document.createElement("div");
    path.className = "map-path";
    const dx = x - prevX;
    const dy = y - prevY;
    const length = Math.sqrt(dx * dx + dy * dy);
    const angle = Math.atan2(dy, dx) * (180 / Math.PI);
    path.style.width = length + "px";
    path.style.left = prevX + "px";
    path.style.top = prevY + "px";
    path.style.transform = `rotate(${angle}deg)`;
    path.style.transformOrigin = "0 50%";
    container.appendChild(path);
  }
}

// ── main ──
// Main entry point - runs after all js_define units
initGame();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-35b-a3b","provider":"lmstudio","persona":"composer","sessionId":"5d6fec58-a06a-4f1f-a5f2-97b6f5b3d63d","tokensIn":16828667,"tokensOut":93898,"tokensTotal":16922565,"turns":168,"toolCalls":167,"failedToolCalls":4,"timestamp":"2026-07-20T22:47:32.478Z"} -->