← Shadows results

Shadows

thinkingcap qwen3.6 27b · 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 92,094 bytes · SHA-256 b4f10f2d6029
<!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: base ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0f; color: #d4c5a9; font-family: 'Georgia', serif; }
.screen { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow-y: auto; overflow-x: hidden; }
.screen.active { display: flex; flex-direction: column; align-items: center; justify-content: center; }
button { cursor: pointer; font-family: 'Georgia', serif; border: none; outline: none; transition: all 0.2s; }
button:hover { filter: brightness(1.2); transform: scale(1.03); }
button:active { transform: scale(0.97); }
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: #1a1a2e; }
::-webkit-scrollbar-thumb { background: #4a3f6b; border-radius: 4px; }
/* ── css: title-screen ── */
#screen-title { background: radial-gradient(ellipse at center, #1a0a2e 0%, #0a0a0f 70%); }
.title-content { text-align: center; max-width: 600px; padding: 40px; }
.game-title { font-size: 3em; color: #c9a84c; text-shadow: 0 0 20px rgba(201,168,76,0.5), 0 2px 4px rgba(0,0,0,0.8); margin-bottom: 10px; letter-spacing: 3px; }
.game-subtitle { font-size: 1.1em; color: #8a7a5c; margin-bottom: 40px; font-style: italic; }
.class-select { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin-top: 30px; }
.class-card { width: 160px; padding: 20px 15px; background: linear-gradient(180deg, #1a1a2e, #0f0f1a); border: 2px solid #3a3a5c; border-radius: 10px; cursor: pointer; transition: all 0.3s; text-align: center; }
.class-card:hover { border-color: #c9a84c; box-shadow: 0 0 20px rgba(201,168,76,0.3); transform: translateY(-5px); }
.class-icon { font-size: 2.5em; margin-bottom: 10px; }
.class-name { color: #c9a84c; font-size: 1.1em; margin-bottom: 8px; font-weight: bold; }
.class-desc { color: #8a7a5c; font-size: 0.8em; line-height: 1.3; }
/* ── css: map-screen ── */
#screen-map { background: radial-gradient(ellipse at center, #1a0a2e 0%, #0a0a0f 70%); justify-content: flex-start; padding-top: 20px; }
.map-header { text-align: center; margin-bottom: 15px; width: 100%; max-width: 500px; }
.act-title { color: #c9a84c; font-size: 1.3em; letter-spacing: 2px; }
.floor-info { color: #8a7a5c; font-size: 0.9em; margin-top: 5px; }
.player-stats-bar { display: flex; gap: 15px; justify-content: center; align-items: center; background: rgba(26,26,46,0.8); padding: 8px 20px; border-radius: 8px; margin-top: 8px; font-size: 0.85em; }
.stat-item { display: flex; align-items: center; gap: 4px; }
.map-container { width: 100%; max-width: 500px; padding: 10px; position: relative; min-height: 60vh; }
.map-grid { display: flex; flex-direction: column-reverse; align-items: center; gap: 8px; padding-bottom: 20px; }
.map-row { display: flex; justify-content: center; gap: 30px; width: 100%; position: relative; }
.map-node { width: 50px; height: 50px; border-radius: 8px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center; font-size: 1.4em; position: relative; }
.map-node.locked { opacity: 0.3; cursor: not-allowed; filter: grayscale(1); }
.map-node.available { animation: nodePulse 2s infinite; border-color: #c9a84c !important; box-shadow: 0 0 15px rgba(201,168,76,0.4); }
.map-node.visited { opacity: 0.5; filter: grayscale(0.5); }
@keyframes nodePulse { 0%, 100% { box-shadow: 0 0 10px rgba(201,168,76,0.3); } 50% { box-shadow: 0 0 25px rgba(201,168,76,0.6); } }
.node-enemy { background: #4a1a1a; border: 2px solid #8b3a3a; }
.node-elite { background: #4a1a3a; border: 2px solid #8b3a7a; }
.node-boss { background: #5a0a0a; border: 2px solid #c9a84c; font-size: 1.6em; }
.node-rest { background: #1a3a1a; border: 2px solid #3a7a3a; }
.node-shop { background: #3a3a1a; border: 2px solid #7a7a3a; }
.node-event { background: #1a2a4a; border: 2px solid #3a5a8b; }
.map-connector { width: 2px; height: 8px; background: #3a3a5c; margin: 0 auto; }
/* ── css: combat-screen ── */
#screen-combat { background: radial-gradient(ellipse at center, #1a0a2e 0%, #0a0a0f 70%); justify-content: flex-start; padding-top: 5px; }
.combat-header { width: 100%; max-width: 800px; display: flex; justify-content: space-between; align-items: center; padding: 5px 15px; background: rgba(26,26,46,0.9); border-radius: 8px; margin-bottom: 5px; }
.combat-player-info { display: flex; gap: 15px; align-items: center; font-size: 0.85em; }
.hp-bar-container { width: 120px; height: 14px; background: #2a1a1a; border-radius: 7px; overflow: hidden; position: relative; }
.hp-bar-fill { height: 100%; background: linear-gradient(90deg, #8b3a3a, #c9a84c); transition: width 0.3s; }
.hp-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 0.7em; color: white; text-shadow: 1px 1px 2px black; }
.combat-area { width: 100%; max-width: 800px; display: flex; gap: 10px; margin-bottom: 5px; }
.enemy-zone { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 8px; min-height: 200px; justify-content: center; }
.player-zone { width: 140px; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; }
.enemy-card { background: linear-gradient(135deg, #2a1a1a, #1a0f0f); border: 2px solid #5a3a3a; border-radius: 8px; padding: 10px; text-align: center; min-width: 100px; position: relative; }
.enemy-card.targetable:hover { border-color: #c94a4a; box-shadow: 0 0 15px rgba(201,74,74,0.3); cursor: pointer; }
.enemy-name { color: #c9a84c; font-size: 0.8em; margin-bottom: 4px; }
.enemy-hp-bar { width: 80px; height: 8px; background: #2a1a1a; border-radius: 4px; overflow: hidden; margin: 4px auto; }
.enemy-hp-fill { height: 100%; background: linear-gradient(90deg, #8b3a3a, #c94a4a); transition: width 0.3s; }
.intent-icon { font-size: 0.75em; margin-top: 4px; color: #db5a5a; }
.player-avatar { width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(135deg, #3a2a5c, #1a1a3e); border: 2px solid #5a4a7c; display: flex; align-items: center; justify-content: center; font-size: 1.8em; }
.player-block { color: #6ab0de; font-size: 0.9em; margin-top: 5px; }
.buffs-row { display: flex; gap: 3px; flex-wrap: wrap; justify-content: center; max-width: 120px; }
.buff-icon { width: 22px; height: 22px; border-radius: 4px; font-size: 0.6em; display: flex; align-items: center; justify-content: center; color: white; position: relative; }
.buff-count { position: absolute; bottom: -2px; right: -2px; background: #1a1a2e; border-radius: 50%; width: 14px; height: 14px; font-size: 0.7em; display: flex; align-items: center; justify-content: center; }
.hand-area { width: 100%; max-width: 800px; min-height: 160px; padding: 5px; position: relative; }
.hand-cards { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; perspective: 800px; }
.game-card { width: 110px; min-height: 140px; background: linear-gradient(180deg, #2a2a3e, #1a1a2e); border: 2px solid #4a4a6c; border-radius: 8px; padding: 6px; cursor: pointer; transition: all 0.2s; display: flex; flex-direction: column; position: relative; user-select: none; }
.game-card:hover { transform: translateY(-15px); z-index: 10; box-shadow: 0 8px 25px rgba(0,0,0,0.6); border-color: #c9a84c; }
.game-card.unplayable { opacity: 0.5; cursor: not-allowed; filter: grayscale(0.5); }
.game-card.unplayable:hover { transform: none; box-shadow: none; border-color: #4a4a6c; }
.card-cost { position: absolute; top: -8px; left: -8px; width: 24px; height: 24px; background: linear-gradient(135deg, #4a3a7c, #2a1a5e); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.8em; color: white; font-weight: bold; border: 2px solid #6a5a9c; }
.card-name { color: #c9a84c; font-size: 0.7em; text-align: center; margin-bottom: 3px; line-height: 1.1; }
.card-type-badge { position: absolute; top: -2px; right: -2px; width: 16px; height: 16px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.5em; }
.type-attack { background: #8b3a3a; }
.type-skill { background: #3a7a3a; }
.type-power { background: #3a3a8b; }
.card-desc { color: #9a9ab0; font-size: 0.6em; text-align: center; flex: 1; line-height: 1.2; margin-top: 5px; }
.combat-footer { width: 100%; max-width: 800px; display: flex; justify-content: space-between; align-items: center; padding: 5px 15px; gap: 10px; }
.end-turn-btn { background: linear-gradient(135deg, #4a2a2a, #2a1a1a); color: #c9a84c; border: 2px solid #6a4a4a; padding: 8px 25px; border-radius: 8px; font-size: 0.9em; }
.potion-slots { display: flex; gap: 5px; }
.potion-slot { width: 35px; height: 35px; background: #1a1a2e; border: 2px solid #4a4a6c; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 1.1em; }
.potion-slot:hover { border-color: #c9a84c; }
.discard-pile-info { font-size: 0.75em; color: #6a6a8c; text-align: center; margin-top: 3px; }
.enemy-debuffs { display: flex; gap: 2px; justify-content: center; margin-top: 3px; flex-wrap: wrap; }
.enemy-buff-icon { width: 18px; height: 18px; border-radius: 3px; font-size: 0.5em; display: flex; align-items: center; justify-content: center; color: white; position: relative; }
/* ── css: ui-screens ── */
#screen-rewards, #screen-shop, #screen-event, #screen-rest { background: radial-gradient(ellipse at center, #1a0a2e 0%, #0a0a0f 70%); }
.reward-title { color: #c9a84c; font-size: 1.5em; margin-bottom: 20px; text-shadow: 0 0 10px rgba(201,168,76,0.3); }
.card-choice-row { display: flex; gap: 15px; justify-content: center; flex-wrap: wrap; margin: 15px 0; }
.reward-card { width: 140px; min-height: 180px; background: linear-gradient(180deg, #2a2a3e, #1a1a2e); border: 2px solid #4a4a6c; border-radius: 10px; padding: 10px; cursor: pointer; transition: all 0.2s; display: flex; flex-direction: column; }
.reward-card:hover { transform: translateY(-8px); box-shadow: 0 8px 25px rgba(0,0,0,0.6); border-color: #c9a84c; }
.gold-reward { color: #c9a84c; font-size: 1.2em; margin: 10px 0; }
.btn-primary { background: linear-gradient(135deg, #4a3a7c, #2a1a5e); color: #d4c5a9; border: 2px solid #6a5a9c; padding: 10px 30px; border-radius: 8px; font-size: 1em; margin-top: 15px; }
.btn-secondary { background: linear-gradient(135deg, #2a2a3e, #1a1a2e); color: #9a9ab0; border: 2px solid #4a4a6c; padding: 8px 20px; border-radius: 8px; font-size: 0.9em; margin-top: 10px; }
.shop-grid { display: flex; gap: 15px; justify-content: center; flex-wrap: wrap; max-width: 700px; }
.shop-item { width: 130px; background: linear-gradient(180deg, #2a2a3e, #1a1a2e); border: 2px solid #4a4a6c; border-radius: 8px; padding: 10px; text-align: center; cursor: pointer; transition: all 0.2s; }
.shop-item:hover { border-color: #c9a84c; transform: translateY(-3px); }
.shop-price { color: #c9a84c; font-size: 0.85em; margin-top: 5px; }
.event-text { max-width: 500px; text-align: center; line-height: 1.6; margin-bottom: 20px; color: #b0a890; font-size: 1em; }
.event-choices { display: flex; flex-direction: column; gap: 10px; align-items: center; }
.rest-options { display: flex; gap: 20px; justify-content: center; margin-top: 20px; }
.rest-option { width: 150px; padding: 20px; background: linear-gradient(180deg, #2a2a3e, #1a1a2e); border: 2px solid #4a4a6c; border-radius: 10px; text-align: center; cursor: pointer; transition: all 0.2s; }
.rest-option:hover { border-color: #c9a84c; transform: translateY(-5px); }
.victory-text, .defeat-text { font-size: 3em; margin-bottom: 20px; text-shadow: 0 0 30px rgba(201,168,76,0.5); }
.defeat-text { color: #8b3a3a; }
.victory-text { color: #c9a84c; }
.deck-view { max-width: 600px; margin: 10px auto; padding: 10px; background: rgba(26,26,46,0.5); border-radius: 8px; }
.deck-cards-grid { display: flex; gap: 5px; flex-wrap: wrap; justify-content: center; }
.mini-card { width: 70px; min-height: 90px; background: linear-gradient(180deg, #2a2a3e, #1a1a2e); border: 1px solid #4a4a6c; border-radius: 5px; padding: 4px; font-size: 0.55em; display: flex; flex-direction: column; cursor: pointer; }
.mini-card:hover { border-color: #c9a84c; }
.rarity-common { color: #6ab0de; }
.rarity-uncommon { color: #3adb3a; }
.rarity-rare { color: #db3adb; }
</style>
</head>
<body>
<div id="screen-title" class="screen active"></div>
<div id="screen-map" class="screen"></div>
<div id="screen-combat" class="screen"></div>
<div id="screen-rewards" class="screen"></div>
<div id="screen-shop" class="screen"></div>
<div id="screen-event" class="screen"></div>
<div id="screen-rest" class="screen"></div>
<div id="screen-victory" class="screen"></div>
<div id="screen-defeat" class="screen"></div>
<script>
'use strict';

// ── const: CARD_TYPES ──
const CARD_TYPES = { ATTACK: 'attack', SKILL: 'skill', POWER: 'power', CURSE: 'curse', STATUS: 'status' };

// ── const: RARITIES ──
const RARITIES = { BASIC: 'basic', COMMON: 'common', UNCOMMON: 'uncommon', RARE: 'rare' };

// ── const: CLASSES ──
const CLASSES = { BLOOD_REAPER: 'blood_reaper', SHADOWMANCER: 'shadowmancer', HELLFORGED: 'hellforged' };

// ── function: makeCardDef ──
function makeCardDef(id, name, type, cost, rarity, desc, upgName, upgDesc) {
  return { id, name, type, cost, rarity, desc, upgraded: false, upgName: upgName || (name + '+'), upgDesc: upgDesc || desc };
}

// ── const: BASIC_STRIKE ──
const BASIC_STRIKE = makeCardDef('strike', 'Strike', CARD_TYPES.ATTACK, 1, RARITIES.BASIC, 'Deal 6 damage.', null, 'Deal 8 damage.');

// ── const: BASIC_DEFEND ──
const BASIC_DEFEND = makeCardDef('defend', 'Defend', CARD_TYPES.SKILL, 1, RARITIES.BASIC, 'Gain 5 Block.', null, 'Gain 8 Block.');

// ── const: BR_ATTACKS ──
// Blood Reaper cards
const BR_ATTACKS = [
  makeCardDef('sanguine_strike', 'Sanguine Strike', CARD_TYPES.ATTACK, 1, RARITIES.COMMON, 'Deal 8 damage. Heal 2 HP.', null, 'Deal 10 damage. Heal 3 HP.'),
  makeCardDef('hemorrhage', 'Hemorrhage', CARD_TYPES.ATTACK, 1, RARITIES.COMMON, 'Deal 5 damage. Apply 3 Bleed.', null, 'Deal 6 damage. Apply 4 Bleed.'),
  makeCardDef('bloodletting', 'Bloodletting', CARD_TYPES.ATTACK, 1, RARITIES.UNCOMMON, 'Lose 3 HP. Deal 12 damage.', null, 'Lose 2 HP. Deal 15 damage.'),
  makeCardDef('crimson_eruption', 'Crimson Eruption', CARD_TYPES.ATTACK, 2, RARITIES.RARE, 'Deal 8 damage to ALL enemies. Lose 2 HP.', null, 'Deal 10 damage to ALL enemies. Lose 2 HP.'),
];

// ── const: BR_DEFENSES ──
const BR_DEFENSES = [
  makeCardDef('crimson_shield', 'Crimson Shield', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Gain 6 Block. Lose 2 HP.', null, 'Gain 8 Block. Lose 2 HP.'),
  makeCardDef('blood_ward', 'Blood Ward', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Gain 3 Block. Gain 1 Regeneration.', null, 'Gain 4 Block. Gain 2 Regeneration.'),
  makeCardDef('sacrificial_barrier', 'Sacrificial Barrier', CARD_TYPES.SKILL, 2, RARITIES.UNCOMMON, 'Gain 10 Block. Lose 5 HP.', null, 'Gain 13 Block. Lose 5 HP.'),
  makeCardDef('vital_surge', 'Vital Surge', CARD_TYPES.SKILL, -1, RARITIES.RARE, 'Gain Block equal to your missing HP. Exhaust.', null, 'Gain Block equal to missing HP + 3. Exhaust.'),
];

// ── const: BR_OTHERS ──
const BR_OTHERS = [
  makeCardDef('life_tap', 'Life Tap', CARD_TYPES.SKILL, 0, RARITIES.COMMON, 'Draw 2 cards. Lose 3 HP.', null, 'Draw 3 cards. Lose 3 HP.'),
  makeCardDef('rend_flesh', 'Rend Flesh', CARD_TYPES.SKILL, 1, RARITIES.UNCOMMON, 'Apply 5 Bleed to ALL enemies. Lose 2 HP.', null, 'Apply 7 Bleed to ALL enemies. Lose 2 HP.'),
  makeCardDef('soul_siphon', 'Soul Siphon', CARD_TYPES.POWER, 2, RARITIES.UNCOMMON, '(Power) At start of your turn, gain 1 Energy and lose 1 HP.', null, 'At start of your turn, gain 1 Energy and lose 1 HP.'),
  makeCardDef('blood_pact', 'Blood Pact', CARD_TYPES.POWER, 1, RARITIES.RARE, '(Power) Whenever you lose HP from a card, gain 1 Strength.', null, 'Whenever you lose HP from a card, gain 2 Strength.'),
];

// ── const: SM_ATTACKS ──
// Shadowmancer cards
const SM_ATTACKS = [
  makeCardDef('shadow_bolt', 'Shadow Bolt', CARD_TYPES.ATTACK, 1, RARITIES.COMMON, 'Deal 6 damage. Apply 1 Weak.', null, 'Deal 8 damage. Apply 2 Weak.'),
  makeCardDef('grasp_of_despair', 'Grasp of Despair', CARD_TYPES.ATTACK, 1, RARITIES.COMMON, 'Deal 4 damage. Apply 1 Vulnerable.', null, 'Deal 5 damage. Apply 2 Vulnerable.'),
  makeCardDef('nightmare_lash', 'Nightmare Lash', CARD_TYPES.ATTACK, 2, RARITIES.UNCOMMON, 'Deal 7 damage. If enemy has Weak, deal 7 more.', null, 'Deal 9 damage. If Weak, deal 9 more.'),
  makeCardDef('eternal_darkness', 'Eternal Darkness', CARD_TYPES.ATTACK, 2, RARITIES.RARE, 'Deal 3 damage to a random enemy 3 times.', null, 'Deal 4 damage to a random enemy 3 times.'),
];

// ── const: SM_DEFENSES ──
const SM_DEFENSES = [
  makeCardDef('cloak_of_shadows', 'Cloak of Shadows', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Gain 5 Block. Draw 1 card.', null, 'Gain 7 Block. Draw 1 card.'),
  makeCardDef('haunting_mist', 'Haunting Mist', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Gain 7 Block. Apply 1 Weak to ALL enemies.', null, 'Gain 9 Block. Apply 1 Weak to ALL enemies.'),
  makeCardDef('phantom_armor', 'Phantom Armor', CARD_TYPES.SKILL, 1, RARITIES.UNCOMMON, 'Gain 8 Block. Next turn, gain 2 Dexterity.', null, 'Gain 10 Block. Next turn, gain 3 Dexterity.'),
  makeCardDef('whispering_shroud', 'Whispering Shroud', CARD_TYPES.SKILL, 1, RARITIES.RARE, 'Gain 4 Block. Next turn, gain 4 Block.', null, 'Gain 5 Block. Next turn, gain 5 Block.'),
];

// ── const: SM_OTHERS ──
const SM_OTHERS = [
  makeCardDef('shadowform', 'Shadowform', CARD_TYPES.SKILL, 2, RARITIES.UNCOMMON, 'Gain 2 Intangible. Exhaust.', null, 'Gain 3 Intangible. Exhaust.'),
  makeCardDef('curse_of_misfortune', 'Curse of Misfortune', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Enemy loses 3 Strength this turn. Apply 1 Vulnerable.', null, 'Enemy loses 4 Strength this turn. Apply 2 Vulnerable.'),
  makeCardDef('dark_ritual', 'Dark Ritual', CARD_TYPES.SKILL, 0, RARITIES.UNCOMMON, 'Gain 2 Energy. Shuffle a Dazed into your discard pile.', null, 'Gain 2 Energy. Shuffle a Dazed into your discard pile.'),
  makeCardDef('umbral_mastery', 'Umbral Mastery', CARD_TYPES.POWER, 2, RARITIES.RARE, '(Power) At start of your turn, apply 1 Weak to ALL enemies.', null, 'At start of your turn, apply 2 Weak to ALL enemies.'),
];

// ── const: HF_ATTACKS ──
// Hellforged cards
const HF_ATTACKS = [
  makeCardDef('firebolt', 'Firebolt', CARD_TYPES.ATTACK, 1, RARITIES.COMMON, 'Deal 7 damage. Apply 2 Burn.', null, 'Deal 9 damage. Apply 3 Burn.'),
  makeCardDef('hellfire_blast', 'Hellfire Blast', CARD_TYPES.ATTACK, 2, RARITIES.UNCOMMON, 'Deal 9 damage to ALL enemies.', null, 'Deal 12 damage to ALL enemies.'),
  makeCardDef('searing_strike', 'Searing Strike', CARD_TYPES.ATTACK, 1, RARITIES.COMMON, 'Deal 6 damage. Gain 1 Energy next turn.', null, 'Deal 8 damage. Gain 1 Energy next turn.'),
  makeCardDef('immolate', 'Immolate', CARD_TYPES.ATTACK, 2, RARITIES.RARE, 'Deal 12 damage. Exhaust.', null, 'Deal 16 damage. Exhaust.'),
];

// ── const: HF_DEFENSES ──
const HF_DEFENSES = [
  makeCardDef('obsidian_skin', 'Obsidian Skin', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Gain 7 Block.', null, 'Gain 10 Block.'),
  makeCardDef('flame_ward', 'Flame Ward', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Gain 5 Block. Apply 2 Burn to a random enemy.', null, 'Gain 6 Block. Apply 3 Burn to a random enemy.'),
  makeCardDef('demonic_resilience', 'Demonic Resilience', CARD_TYPES.SKILL, 1, RARITIES.UNCOMMON, 'Gain 4 Block. Heal 2 HP.', null, 'Gain 5 Block. Heal 3 HP.'),
  makeCardDef('brimstone_shield', 'Brimstone Shield', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Gain 3 Block. Gain 2 Strength this turn.', null, 'Gain 4 Block. Gain 3 Strength this turn.'),
];

// ── const: HF_OTHERS ──
const HF_OTHERS = [
  makeCardDef('summon_imp', 'Summon Imp', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Add an Imp Claw to your hand. Exhaust.', null, 'Add an Imp Claw+ to your hand. Exhaust.'),
  makeCardDef('infernal_pact', 'Infernal Pact', CARD_TYPES.SKILL, 0, RARITIES.UNCOMMON, 'Lose 2 HP. Gain 2 Energy. Add an Imp Claw to your hand.', null, 'Lose 1 HP. Gain 2 Energy. Add an Imp Claw+.'),
  makeCardDef('hellfire_aura', 'Hellfire Aura', CARD_TYPES.POWER, 2, RARITIES.RARE, '(Power) At start of your turn, deal 3 damage to ALL enemies.', null, 'At start of your turn, deal 5 damage to ALL enemies.'),
  makeCardDef('soul_burn', 'Soul Burn', CARD_TYPES.SKILL, 1, RARITIES.UNCOMMON, 'Apply 8 Burn to an enemy. Draw 1 card.', null, 'Apply 10 Burn to an enemy. Draw 1 card.'),
];

// ── const: COLORLESS_CARDS ──
// Colorless cards (shop only)
const COLORLESS_CARDS = [
  makeCardDef('flash_of_insight', 'Flash of Insight', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Draw 2 cards.', null, 'Draw 3 cards.'),
  makeCardDef('iron_will', 'Iron Will', CARD_TYPES.SKILL, 1, RARITIES.COMMON, 'Gain 8 Block.', null, 'Gain 11 Block.'),
  makeCardDef('quick_slash', 'Quick Slash', CARD_TYPES.ATTACK, 1, RARITIES.COMMON, 'Deal 7 damage.', null, 'Deal 9 damage.'),
  makeCardDef('potent_brew', 'Potent Brew', CARD_TYPES.SKILL, 0, RARITIES.UNCOMMON, 'Gain 1 Energy. Exhaust.', null, 'Gain 2 Energy. Exhaust.'),
];

// ── const: WOUND ──
// Curses and status cards
const WOUND = { id: 'wound', name: 'Wound', type: CARD_TYPES.CURSE, cost: -1, rarity: RARITIES.BASIC, desc: 'Unplayable. Ethereal.', upgraded: false };

// ── const: DOUBT ──
const DOUBT = { id: 'doubt', name: 'Doubt', type: CARD_TYPES.CURSE, cost: -1, rarity: RARITIES.BASIC, desc: 'Unplayable. End of turn: lose 1 HP.', upgraded: false };

// ── const: DAZED ──
const DAZED = { id: 'dazed', name: 'Dazed', type: CARD_TYPES.STATUS, cost: -1, rarity: RARITIES.BASIC, desc: 'Unplayable. Ethereal.', upgraded: false };

// ── const: CLASS_POOLS ──
// Class card pools
const CLASS_POOLS = {
  [CLASSES.BLOOD_REAPER]: [...BR_ATTACKS, ...BR_DEFENSES, ...BR_OTHERS],
  [CLASSES.SHADOWMANCER]: [...SM_ATTACKS, ...SM_DEFENSES, ...SM_OTHERS],
  [CLASSES.HELLFORGED]: [...HF_ATTACKS, ...HF_DEFENSES, ...HF_OTHERS],
};

// ── function: getStartingDeck ──
function getStartingDeck(classId) {
  const deck = [];
  for (let i = 0; i < 5; i++) deck.push({ ...BASIC_STRIKE });
  for (let i = 0; i < 4; i++) deck.push({ ...BASIC_DEFEND });
  if (classId === CLASSES.BLOOD_REAPER) deck.push({ ...BR_ATTACKS[0] });
  else if (classId === CLASSES.SHADOWMANCER) deck.push({ ...SM_ATTACKS[0] });
  else deck.push({ ...HF_ATTACKS[0] });
  return deck;
}

// ── function: pickRandomCard ──
function pickRandomCard(pool, rarityWeights) {
  const weights = rarityWeights || { [RARITIES.COMMON]: 60, [RARITIES.UNCOMMON]: 30, [RARITIES.RARE]: 10 };
  let r = Math.random() * 100;
  let chosenRarity = RARITIES.COMMON;
  if (r < weights[RARITIES.RARE]) chosenRarity = RARITIES.RARE;
  else if (r < weights[RARITIES.RARE] + weights[RARITIES.UNCOMMON]) chosenRarity = RARITIES.UNCOMMON;
  const filtered = pool.filter(c => c.rarity === chosenRarity);
  if (filtered.length > 0) return { ...filtered[Math.floor(Math.random() * filtered.length)] };
  // fallback to any card
  return { ...pool[Math.floor(Math.random() * pool.length)] };
}

// ── function: pickRewardCards ──
function pickRewardCards(pool, isBoss, isElite) {
  const weights = isBoss ? { [RARITIES.COMMON]: 40, [RARITIES.UNCOMMON]: 30, [RARITIES.RARE]: 30 } :
    isElite ? { [RARITIES.COMMON]: 50, [RARITIES.UNCOMMON]: 30, [RARITIES.RARE]: 20 } :
    { [RARITIES.COMMON]: 60, [RARITIES.UNCOMMON]: 30, [RARITIES.RARE]: 10 };
  const cards = [];
  if (isBoss) {
    // Boss always has one rare
    const rares = pool.filter(c => c.rarity === RARITIES.RARE);
    cards.push({ ...rares[Math.floor(Math.random() * rares.length)] });
    for (let i = 1; i < 3; i++) cards.push(pickRandomCard(pool, weights));
  } else {
    for (let i = 0; i < 3; i++) cards.push(pickRandomCard(pool, weights));
  }
  return cards;
}

// ── const: ENEMIES ──
// Enemy definitions with intent patterns
const ENEMIES = {
  // Act 1 - The Crypts
  skeleton_warrior: { name: 'Skeleton Warrior', hp: 22, maxHp: 22, intents: [
    { type: 'attack', value: 6 }, { type: 'attack', value: 6 }, { type: 'defend', value: 8 }
  ]},
  zombie_brute: { name: 'Zombie Brute', hp: 28, maxHp: 28, intents: [
    { type: 'attack', value: 8 }, { type: 'debuff', debuffType: 'weak', value: 2 }, { type: 'attack', value: 8 }
  ]},
  dark_cultist: { name: 'Dark Cultist', hp: 20, maxHp: 20, intents: [
    { type: 'buff', buffType: 'strength', value: 3 }, { type: 'attack', value: 5 }, { type: 'attack', value: 5 }
  ]},
  rat_swarm: { name: 'Rat Swarm', hp: 15, maxHp: 15, intents: [
    { type: 'multi_attack', hits: 2, value: 3 }, { type: 'multi_attack', hits: 2, value: 3 }
  ]},
  tomb_guardian: { name: 'Tomb Guardian', hp: 42, maxHp: 42, isElite: true, intents: [
    { type: 'attack_defend', attackValue: 6, defendValue: 8 }, { type: 'multi_attack', hits: 3, value: 4 }, { type: 'attack', value: 12 }
  ]},
  blood_witch: { name: 'Blood Witch', hp: 36, maxHp: 36, isElite: true, intents: [
    { type: 'drain', attackValue: 5, healValue: 4 }, { type: 'debuff_combo', weak: 1, vulnerable: 1 }, { type: 'drain', attackValue: 7, healValue: 4 }
  ]},
  lich_king: { name: 'Lich King', hp: 65, maxHp: 65, isBoss: true, intents: [
    { type: 'summon', summonEnemy: 'skeleton_minion' }, { type: 'attack', value: 8 }, { type: 'defend', value: 12 }
  ], phase2Intents: [
    { type: 'attack', value: 10 }, { type: 'attack', value: 10 }, { type: 'defend', value: 15 }
  ]},
  skeleton_minion: { name: 'Skeleton Minion', hp: 15, maxHp: 15, intents: [
    { type: 'attack', value: 5 }, { type: 'attack', value: 5 }, { type: 'defend', value: 4 }
  ]},

  // Act 2 - Blighted Woods
  wraith: { name: 'Wraith', hp: 30, maxHp: 30, intents: [
    { type: 'attack_debuff', attackValue: 7, debuffType: 'weak', value: 1 }, { type: 'attack', value: 7 }, { type: 'defend', value: 10 }
  ]},
  corrupted_beast: { name: 'Corrupted Beast', hp: 38, maxHp: 38, intents: [
    { type: 'attack', value: 12 }, { type: 'self_vuln_defend', defendValue: 5 }, { type: 'attack', value: 12 }
  ]},
  thorned_horror: { name: 'Thorned Horror', hp: 25, maxHp: 25, intents: [
    { type: 'multi_attack', hits: 3, value: 4 }, { type: 'debuff', debuffType: 'bleed', value: 3 }, { type: 'attack', value: 8 }
  ]},
  witch_doctor: { name: 'Witch Doctor', hp: 22, maxHp: 22, intents: [
    { type: 'self_buff', defendValue: 4, regenValue: 2 }, { type: 'attack_debuff', attackValue: 6, debuffType: 'poison', value: 2 }, { type: 'attack', value: 6 }
  ]},
  treant_protector: { name: 'Treant Protector', hp: 50, maxHp: 50, isElite: true, intents: [
    { type: 'attack_defend', attackValue: 9, defendValue: 6 }, { type: 'debuff', debuffType: 'vulnerable', value: 2 }, { type: 'multi_attack', hits: 2, value: 10 }
  ]},
  plague_shaman: { name: 'Plague Shaman', hp: 42, maxHp: 42, isElite: true, intents: [
    { type: 'debuff', debuffType: 'poison', value: 4 }, { type: 'summon', summonEnemy: 'plague_rat' }, { type: 'attack', value: 8 }
  ]},
  plague_rat: { name: 'Plague Rat', hp: 18, maxHp: 18, intents: [
    { type: 'attack', value: 5 }, { type: 'attack', value: 5 }, { type: 'defend', value: 3 }
  ]},
  eldritch_treant: { name: 'Eldritch Treant', hp: 75, maxHp: 75, isBoss: true, intents: [
    { type: 'attack_debuff', attackValue: 10, debuffType: 'weak', value: 2 }, { type: 'defend_regen', defendValue: 15, regenValue: 2 }, { type: 'multi_attack', hits: 2, value: 8 }
  ], phase2Intents: [
    { type: 'attack_debuff', attackValue: 14, debuffType: 'vulnerable', value: 1 }, { type: 'defend_heal', defendValue: 20, healValue: 5 }, { type: 'attack_debuff', attackValue: 14, debuffType: 'vulnerable', value: 1 }
  ]},

  // Act 3 - Abyssal Spire
  hellhound: { name: 'Hellhound', hp: 26, maxHp: 26, intents: [
    { type: 'attack_debuff', attackValue: 8, debuffType: 'burn', value: 2 }, { type: 'attack', value: 8 }, { type: 'defend', value: 9 }
  ]},
  shadow_demon: { name: 'Shadow Demon', hp: 30, maxHp: 30, intents: [
    { type: 'attack_debuff', attackValue: 7, debuffType: 'weak', value: 1 }, { type: 'attack', value: 7 }, { type: 'self_buff_intangible' }
  ]},
  infernal_imp: { name: 'Infernal Imp', hp: 18, maxHp: 18, intents: [
    { type: 'attack', value: 5 }, { type: 'attack', value: 5 }, { type: 'summon', summonEnemy: 'infernal_imp' }
  ]},
  abyssal_cultist: { name: 'Abyssal Cultist', hp: 24, maxHp: 24, intents: [
    { type: 'buff_all', buffType: 'strength', value: 2 }, { type: 'attack', value: 7 }, { type: 'attack', value: 7 }
  ]},
  flamespawn: { name: 'Flamespawn', hp: 48, maxHp: 48, isElite: true, intents: [
    { type: 'attack_debuff', attackValue: 9, debuffType: 'burn', value: 3 }, { type: 'defend_buff', defendValue: 10, buffType: 'strength', value: 2 }, { type: 'multi_attack', hits: 2, value: 8 }
  ]},
  dread_knight: { name: 'Dread Knight', hp: 55, maxHp: 55, isElite: true, intents: [
    { type: 'attack', value: 12 }, { type: 'debuff', debuffType: 'bleed', value: 3 }, { type: 'defend_heal', defendValue: 12, healValue: 4 }
  ]},
  archdemon: { name: 'Archdemon', hp: 85, maxHp: 85, isBoss: true, intents: [
    { type: 'summon', summonEnemy: 'hellhound' }, { type: 'attack_debuff', attackValue: 12, debuffType: 'burn', value: 2 }, { type: 'defend_buff', defendValue: 15, buffType: 'strength', value: 3 }
  ], phase2Intents: [
    { type: 'attack', value: 16 }, { type: 'multi_attack', hits: 3, value: 7 }, { type: 'attack', value: 16 }
  ]},
};

// ── const: ACT_ENEMY_POOLS ──
const ACT_ENEMY_POOLS = {
  1: ['skeleton_warrior', 'zombie_brute', 'dark_cultist', 'rat_swarm'],
  2: ['wraith', 'corrupted_beast', 'thorned_horror', 'witch_doctor'],
  3: ['hellhound', 'shadow_demon', 'infernal_imp', 'abyssal_cultist'],
};

// ── const: RELICS ──
// Relics
const RELICS = [
  { id: 'bloodstone', name: 'Bloodstone', desc: 'Whenever you lose HP from a card, heal 1 HP.', rarity: 'common' },
  { id: 'shadow_cloak', name: 'Shadow Cloak', desc: 'Start combat with 1 Intangible.', rarity: 'rare' },
  { id: 'cursed_amulet', name: 'Cursed Amulet', desc: 'Start combat: apply 1 Weak to ALL enemies.', rarity: 'common' },
  { id: 'tome_dark_rituals', name: 'Tome of Dark Rituals', desc: 'When you play a Power card, draw 1 card.', rarity: 'uncommon' },
  { id: 'bleeding_edge', name: 'Bleeding Edge', desc: 'Start turn: apply 1 Bleed to random enemy.', rarity: 'common' },
  { id: 'soul_ember', name: 'Soul Ember', desc: 'When you apply Burn, deal 2 extra damage.', rarity: 'rare' },
  { id: 'eternal_hourglass', name: 'Eternal Hourglass', desc: 'Start turn: if no cards in hand, gain 1 Energy.', rarity: 'uncommon' },
  { id: 'ashen_armor', name: 'Ashen Armor', desc: 'End combat: heal 6 HP.', rarity: 'common' },
  { id: 'greedy_imp', name: 'Greedy Imp', desc: 'Enemies drop 10 extra Gold.', rarity: 'common' },
  { id: 'demon_horns', name: 'Demon Horns', desc: 'Start combat: add random Imp Claw to hand.', rarity: 'uncommon', classSpecific: CLASSES.HELLFORGED },
  { id: 'sacrificial_dagger', name: 'Sacrificial Dagger', desc: 'First Attack costing HP each combat costs 1 less HP.', rarity: 'uncommon', classSpecific: CLASSES.BLOOD_REAPER },
];

// ── const: BOSS_RELICS ──
const BOSS_RELICS = [
  { id: 'cursed_crown', name: "Cursed Crown", desc: '+1 Energy/turn. Curses can\'t be removed.' },
  { id: 'ectoplasm', name: 'Ectoplasm', desc: '+1 Energy. Lose all Gold, gain no more.' },
  { id: 'philosophers_stone', name: "Philosopher's Stone", desc: '+1 Energy. Enemies start with +1 Strength.' },
  { id: 'black_blood', name: 'Black Blood', desc: 'Start combat: lose 10% max HP. Play Attack → heal 1 HP.' },
  { id: 'shadow_amulet', name: 'Shadow Amulet', desc: 'Start turn with 1 Intangible. -15 max HP.' },
];

// ── const: POTIONS ──
// Potions
const POTIONS = [
  { id: 'blood_vial', name: 'Blood Vial', icon: '🩸', desc: 'Heal 15 HP.' },
  { id: 'firebomb', name: 'Firebomb', icon: '💥', desc: 'Deal 10 damage to ALL enemies.' },
  { id: 'dex_tonic', name: 'Dex Tonic', icon: '⚡', desc: 'Gain 3 Dexterity for 1 turn.' },
  { id: 'energy_elixir', name: 'Energy Elixir', icon: '✨', desc: 'Gain 2 Energy this turn.' },
  { id: 'cursed_brew', name: 'Cursed Brew', icon: '☠️', desc: 'Apply 3 Vulnerable to all enemies.' },
  { id: 'bleeding_flask', name: 'Bleeding Flask', icon: '🔴', desc: 'Apply 6 Bleed to an enemy.' },
  { id: 'imp_in_bottle', name: 'Imp-in-a-Bottle', icon: '👹', desc: 'Add Imp Claw+ to hand.' },
  { id: 'smoke_bomb', name: 'Smoke Bomb', icon: '💨', desc: 'Escape combat (no rewards).' },
];

// ── const: EVENTS ──
// Events
const EVENTS = [
  {
    id: 'forgotten_altar', title: 'The Forgotten Altar', text: 'A blood-stained altar hums with dark power.',
    choices: [
      { text: '[Offer 15 HP]', effect: (g) => { g.player.hp = Math.max(1, g.player.hp - 15); g.player.relics.push({ ...RELICS[Math.floor(Math.random() * RELICS.length)] }); }},
      { text: '[Pray]', effect: (g) => { g.player.hp = Math.min(g.player.maxHp, g.player.hp + 10); addToDeck(g, DOUBT); }},
      { text: '[Leave]', effect: () => {} },
    ]
  },
  {
    id: 'shadowy_peddler', title: 'Shadowy Peddler', text: 'A hooded figure offers a trade.',
    choices: [
      { text: '[Trade a card for 100 Gold]', effect: (g) => { if (g.player.deck.length > 5) g.player.deck.splice(0, 1); g.player.gold += 100; }},
      { text: '[Buy wares (-50 Gold)]', effect: (g) => { g.player.gold -= 50; g.player.potions.push({ ...POTIONS[Math.floor(Math.random() * POTIONS.length)] }); g.player.relics.push({ ...RELICS[Math.floor(Math.random() * RELICS.length)] }); }},
      { text: '[Leave]', effect: () => {} },
    ]
  },
  {
    id: 'cursed_spring', title: 'Cursed Spring', text: 'A spring bubbles with dark water.',
    choices: [
      { text: '[Drink (-10 HP, remove curses)]', effect: (g) => { g.player.hp = Math.max(1, g.player.hp - 10); g.player.deck = g.player.deck.filter(c => c.type !== CARD_TYPES.CURSE); }},
      { text: '[Bottle it]', effect: (g) => { if (g.player.potions.length < 3) g.player.potions.push({ id: 'cursed_brew', name: 'Cursed Brew', icon: '☠️', desc: 'Apply 3 Vulnerable to all enemies.' }); }},
      { text: '[Leave]', effect: () => {} },
    ]
  },
  {
    id: 'restless_dead', title: 'Restless Dead', text: 'Ghosts offer a bargain.',
    choices: [
      { text: '[Accept (-20 HP, upgrade 2 cards)]', effect: (g) => { g.player.hp = Math.max(1, g.player.hp - 20); let count = 0; for (let c of g.player.deck) { if (!c.upgraded && count < 2) { upgradeCard(c); count++; } } }},
      { text: '[Refuse]', effect: () => {} },
    ]
  },
  {
    id: 'impish_gambler', title: 'Impish Gambler', text: 'A grinning imp challenges you.',
    choices: [
      { text: '[Bet 30 Gold (50/50)]', effect: (g) => { g.player.gold -= 30; if (Math.random() < 0.5) g.player.gold += 60; }},
      { text: '[Wager a card]', effect: (g) => { const idx = Math.floor(Math.random() * g.player.deck.length); const c = g.player.deck.splice(idx, 1)[0]; if (c.type === CARD_TYPES.CURSE) g.player.gold += 100; }},
      { text: '[Leave]', effect: () => {} },
    ]
  },
  {
    id: 'ancient_library', title: 'Ancient Library', text: 'Dusty tomes contain forgotten knowledge.',
    choices: [
      { text: '[Study (-5 HP, +1 Energy next combat)]', effect: (g) => { g.player.hp = Math.max(1, g.player.hp - 5); g.player.temporaryBonusEnergy = (g.player.temporaryBonusEnergy || 0) + 1; }},
      { text: '[Read forbidden page]', effect: (g) => { const pool = CLASS_POOLS[g.player.class]; const powers = pool.filter(c => c.type === CARD_TYPES.POWER); if (powers.length > 0) addToDeck(g, { ...powers[Math.floor(Math.random() * powers.length)] }); addToDeck(g, DOUBT); }},
      { text: '[Leave]', effect: () => {} },
    ]
  },
];

// ── function: upgradeCard ──
function upgradeCard(card) {
  card.upgraded = true;
  if (card.id === 'strike') { card.name = 'Strike+'; card.desc = 'Deal 8 damage.'; }
  else if (card.id === 'defend') { card.name = 'Defend+'; card.desc = 'Gain 8 Block.'; }
  else {
    const pool = [...BR_ATTACKS, ...BR_DEFENSES, ...BR_OTHERS, ...SM_ATTACKS, ...SM_DEFENSES, ...SM_OTHERS, ...HF_ATTACKS, ...HF_DEFENSES, ...HF_OTHERS, ...COLORLESS_CARDS];
    const def = pool.find(c => c.id === card.id);
    if (def) { card.name = def.upgName; card.desc = def.upgDesc; }
  }
}

// ── function: addToDeck ──
function addToDeck(gameState, cardDef) {
  gameState.player.deck.push({ ...cardDef });
}

// ── let: G ──
// Game state and map generation
let G = null;

// ── function: createGameState ──
// Global game state

function createGameState(classId) {
  return {
    screen: 'title',
    player: {
      class: classId,
      hp: 70,
      maxHp: 70,
      gold: 99,
      deck: getStartingDeck(classId),
      relics: [],
      potions: [],
      temporaryBonusEnergy: 0,
    },
    act: 1,
    floor: 1,
    mapNodes: [],
    currentNodeIndex: -1,
    combat: null,
    rewards: null,
    shopData: null,
    eventData: null,
    restData: null,
  };
}

// ── function: generateMap ──
function generateMap(act) {
  const nodes = [];
  // Floor layout: 2=Enemy,3=Enemy,4=Event,5=Shop,6=Enemy,7=Rest,8=Enemy,9=Elite/Enemy,10=Event,11=Enemy,12=Elite/Enemy,13=Enemy,14=Rest,15=Boss
  const floorTypes = [
    { type: 'enemy', hasBranch: true },   // floor 2
    { type: 'enemy' },                     // floor 3
    { type: 'event' },                     // floor 4
    { type: 'shop' },                      // floor 5
    { type: 'enemy' },                     // floor 6
    { type: 'rest' },                      // floor 7
    { type: 'enemy', hasBranch: true },   // floor 8
    { type: 'elite', hasAltEnemy: true }, // floor 9
    { type: 'event' },                     // floor 10
    { type: 'enemy' },                     // floor 11
    { type: 'elite', hasAltEnemy: true }, // floor 12
    { type: 'enemy' },                     // floor 13
    { type: 'rest' },                      // floor 14
    { type: 'boss' },                      // floor 15
  ];

  const enemyPool = ACT_ENEMY_POOLS[act];
  const bossKey = act === 1 ? 'lich_king' : act === 2 ? 'eldritch_treant' : 'archdemon';
  const eliteKeys = act === 1 ? ['tomb_guardian', 'blood_witch'] : act === 2 ? ['treant_protector', 'plague_shaman'] : ['flamespawn', 'dread_knight'];

  for (let i = 0; i < floorTypes.length; i++) {
    const ft = floorTypes[i];
    const node = { floor: i + 2, type: ft.type, visited: false, available: false };

    if (ft.type === 'enemy') {
      const eKey = enemyPool[Math.floor(Math.random() * enemyPool.length)];
      node.enemyKeys = [eKey];
      if (ft.hasBranch) {
        const altKey = enemyPool[Math.floor(Math.random() * enemyPool.length)];
        node.branchEnemyKey = altKey;
      }
    } else if (ft.type === 'elite') {
      node.eliteKey = eliteKeys[i - 7] || eliteKeys[0]; // approximate index
      const eKey = enemyPool[Math.floor(Math.random() * enemyPool.length)];
      node.altEnemyKey = eKey;
    } else if (ft.type === 'boss') {
      node.enemyKeys = [bossKey];
    }

    nodes.push(node);
  }

  return nodes;
}

// ── function: getNodeIcon ──
function getNodeIcon(type) {
  switch (type) {
    case 'enemy': return '⚔️';
    case 'elite': return '💀';
    case 'rest': return '🏕️';
    case 'shop': return '🛒';
    case 'event': return '❓';
    case 'boss': return '👑';
    default: return '?';
  }
}

// ── function: getActName ──
function getActName(act) {
  switch (act) {
    case 1: return 'The Crypts';
    case 2: return 'Blighted Woods';
    case 3: return 'Abyssal Spire';
    default: return '';
  }
}

// ── function: getClassName ──
function getClassName(classId) {
  switch (classId) {
    case CLASSES.BLOOD_REAPER: return 'Blood Reaper';
    case CLASSES.SHADOWMANCER: return 'Shadowmancer';
    case CLASSES.HELLFORGED: return 'Hellforged';
    default: return '';
  }
}

// ── function: getClassIcon ──
function getClassIcon(classId) {
  switch (classId) {
    case CLASSES.BLOOD_REAPER: return '🩸';
    case CLASSES.SHADOWMANCER: return '🌑';
    case CLASSES.HELLFORGED: return '🔥';
    default: return '?';
  }
}

// ── function: getClassDesc ──
function getClassDesc(classId) {
  switch (classId) {
    case CLASSES.BLOOD_REAPER: return 'Sacrifice HP for devastating power and life steal.';
    case CLASSES.SHADOWMANCER: return 'Debuff enemies with Weak, Vulnerable, and Intangible.';
    case CLASSES.HELLFORGED: return 'Fire damage, Burn effects, and summon Imps.';
    default: return '';
  }
}

// ── 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: showScreen ──
function showScreen(id) {
  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
  const el = document.getElementById(id);
  if (el) el.classList.add('active');
}

// ── function: renderTitleScreen ──
// UI Rendering Functions
function renderTitleScreen() {
  const el = document.getElementById('screen-title');
  el.innerHTML = `
    <div class="title-content">
      <h1 class="game-title">Shadows of the Abyss</h1>
      <p class="game-subtitle">A Dark Fantasy Roguelike Deckbuilder</p>
      <div class="class-select">
        <div class="class-card" onclick="selectClass('${CLASSES.BLOOD_REAPER}')">
          <div class="class-icon">🩸</div>
          <div class="class-name">Blood Reaper</div>
          <div class="class-desc">Sacrifice HP for devastating power and life steal.</div>
        </div>
        <div class="class-card" onclick="selectClass('${CLASSES.SHADOWMANCER}')">
          <div class="class-icon">🌑</div>
          <div class="class-name">Shadowmancer</div>
          <div class="class-desc">Debuff enemies with Weak, Vulnerable, and Intangible.</div>
        </div>
        <div class="class-card" onclick="selectClass('${CLASSES.HELLFORGED}')">
          <div class="class-icon">🔥</div>
          <div class="class-name">Hellforged</div>
          <div class="class-desc">Fire damage, Burn effects, and summon Imps.</div>
        </div>
      </div>
    </div>
  `;
}

// ── function: renderMapScreen ──
function renderMapScreen() {
  const el = document.getElementById('screen-map');
  let nodesHtml = '';
  
  for (let i = G.mapNodes.length - 1; i >= 0; i--) {
    const node = G.mapNodes[i];
    if (!node) continue;
    
    let cls = `map-node node-${node.type}`;
    if (node.visited) cls += ' visited';
    else if (node.available) cls += ' available';
    else cls += ' locked';

    const onclick = node.available ? `onclick="selectNode(${i})"` : '';
    
    nodesHtml += `<div class="map-row">
      <div class="${cls}" ${onclick} title="${node.type.toUpperCase()} - Floor ${node.floor}">${getNodeIcon(node.type)}</div>
    </div>`;
  }

  el.innerHTML = `
    <div class="map-header">
      <div class="act-title">Act ${G.act}: ${getActName(G.act)}</div>
      <div class="floor-info">Floor ${G.floor} of 15</div>
      <div class="player-stats-bar">
        <span class="stat-item">❤️ ${G.player.hp}/${G.player.maxHp}</span>
        <span class="stat-item">💰 ${G.player.gold}</span>
        <span class="stat-item">🃏 ${G.player.deck.length} cards</span>
        <span class="stat-item">🏺 ${G.player.relics.length} relics</span>
      </div>
    </div>
    <div class="map-container">
      <div class="map-grid">${nodesHtml}</div>
    </div>
  `;
  
  // Mark next nodes as available
  updateMapAvailability();
}

// ── function: updateMapAvailability ──
function updateMapAvailability() {
  const visitedCount = G.mapNodes.filter(n => n.visited).length;
  if (visitedCount < G.mapNodes.length) {
    const nextNode = G.mapNodes[visitedCount];
    if (nextNode && !nextNode.visited) {
      nextNode.available = true;
    }
  }
}

// ── function: renderCombatScreen ──
function renderCombatScreen() {
  const c = G.combat;
  const el = document.getElementById('screen-combat');
  
  let enemiesHtml = '';
  for (let i = 0; i < c.enemies.length; i++) {
    const e = c.enemies[i];
    if (!e.isAlive) continue;
    
    const hpPct = Math.max(0, (e.currentHp / e.maxHp) * 100);
    let intentText = '';
    let intents = e.isBoss && e.inPhase2 ? e.phase2Intents : e.intents;
    const intent = intents[e.intentIndex % intents.length];
    
    switch (intent.type) {
      case 'attack': intentText = `⚔️ ${intent.value + (e.buffs.strength || 0)} dmg`; break;
      case 'defend': intentText = `🛡️ ${intent.value} Block`; break;
      case 'multi_attack': intentText = `⚔️ ${intent.hits}×${intent.value}`; break;
      case 'attack_debuff': intentText = `⚔️ ${intent.attackValue + (e.buffs.strength || 0)} + ${intent.debuffType}`; break;
      case 'debuff': intentText = `☠️ ${intent.debuffType} ×${intent.value}`; break;
      case 'summon': intentText = `👹 Summon`; break;
      case 'drain': intentText = `💀 Drain`; break;
      default: intentText = `⚔️ ?`;
    }

    let enemyBuffsHtml = '';
    if (e.buffs.strength > 0) enemyBuffsHtml += `<div class="enemy-buff-icon" style="background:#8b3a3a;" title="Strength +${e.buffs.strength}">💪</div>`;
    if (e.debuffs.weak > 0 && e.debuffs.weakDuration > 0) enemyBuffsHtml += `<div class="enemy-buff-icon" style="background:#5a3a7a;" title="Weak ${e.debuffs.weakDuration}">😰</div>`;
    if (e.debuffs.vulnerable > 0 && e.debuffs.vulnerableDuration > 0) enemyBuffsHtml += `<div class="enemy-buff-icon" style="background:#7a3a3a;" title="Vulnerable ${e.debuffs.vulnerableDuration}">🔥</div>`;
    if (e.debuffs.bleed > 0) enemyBuffsHtml += `<div class="enemy-buff-icon" style="background:#8b1a1a;" title="Bleed ${e.debuffs.bleed}">🩸</div>`;
    if (e.debuffs.burn > 0) enemyBuffsHtml += `<div class="enemy-buff-icon" style="background:#db5a1a;" title="Burn ${e.debuffs.burn}">🔥</div>`;

    enemiesHtml += `<div class="enemy-card targetable" onclick="selectTargetEnemy(${i})">
      <div class="enemy-name">${e.name}</div>
      <div class="enemy-hp-bar"><div class="enemy-hp-fill" style="width:${hpPct}%"></div></div>
      <div style="font-size:0.7em;color:#9a9ab0;">${e.currentHp}/${e.maxHp} HP</div>
      ${e.block > 0 ? `<div style="font-size:0.7em;color:#6ab0de;">🛡️ ${e.block}</div>` : ''}
      <div class="intent-icon">${intentText}</div>
      <div class="enemy-debuffs">${enemyBuffsHtml}</div>
    </div>`;
  }

  let playerBuffsHtml = '';
  if (c.player.buffs.strength > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#8b3a3a;" title="Strength +${c.player.buffs.strength}">💪<span class="buff-count">${c.player.buffs.strength}</span></div>`;
  if (c.player.buffs.dexterity > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#3a7a3a;" title="Dex +${c.player.buffs.dexterity}">🏃<span class="buff-count">${c.player.buffs.dexterity}</span></div>`;
  if (c.player.buffs.regeneration > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#3adb3a;" title="Regen ${c.player.buffs.regeneration}">💚<span class="buff-count">${c.player.buffs.regeneration}</span></div>`;
  if (c.player.debuffs.weak > 0 && c.player.debuffs.weakDuration > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#5a3a7a;" title="Weak ${c.player.debuffs.weakDuration}">😰<span class="buff-count">${c.player.debuffs.weakDuration}</span></div>`;
  if (c.player.debuffs.vulnerable > 0 && c.player.debuffs.vulnerableDuration > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#7a3a3a;" title="Vuln ${c.player.debuffs.vulnerableDuration}">🔥<span class="buff-count">${c.player.debuffs.vulnerableDuration}</span></div>`;
  if (c.player.debuffs.bleed > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#8b1a1a;" title="Bleed ${c.player.debuffs.bleed}">🩸<span class="buff-count">${c.player.debuffs.bleed}</span></div>`;
  if (c.player.debuffs.burn > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#db5a1a;" title="Burn ${c.player.debuffs.burn}">🔥<span class="buff-count">${c.player.debuffs.burn}</span></div>`;
  if (c.player.debuffs.poison > 0 && c.player.debuffs.poisonDuration > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#3a7a1a;" title="Poison ${c.player.debuffs.poisonDuration}">☠️<span class="buff-count">${c.player.debuffs.poisonDuration}</span></div>`;
  if (c.player.debuffs.intangible > 0) playerBuffsHtml += `<div class="buff-icon" style="background:#5a5a9c;" title="Intangible ${c.player.debuffs.intangible}">👻<span class="buff-count">${c.player.debuffs.intangible}</span></div>`;

  let handHtml = '';
  for (let i = 0; i < c.hand.length; i++) {
    const card = c.hand[i];
    const playable = c.isPlayerTurn && canPlayCardInCombat(card);
    const typeClass = `type-${card.type}`;
    
    handHtml += `<div class="game-card ${playable ? '' : 'unplayable'}" onclick="${playable ? `playCardFromHand(${i})` : ''}">
      <div class="card-cost">${card.cost >= 0 ? card.cost : (card.id === 'vital_surge' ? 'X' : '?')}</div>
      <div class="card-type-badge ${typeClass}"></div>
      <div class="card-name rarity-${card.rarity}">${card.name}</div>
      <div class="card-desc">${card.desc}</div>
    </div>`;
  }

  let potionsHtml = '';
  for (let i = 0; i < G.player.potions.length; i++) {
    const p = G.player.potions[i];
    potionsHtml += `<div class="potion-slot" title="${p.name}: ${p.desc}" onclick="usePotionFromCombat(${i})">${p.icon}</div>`;
  }

  el.innerHTML = `
    <div class="combat-header">
      <div class="combat-player-info">
        <span>❤️ ${c.player.hp}/${c.player.maxHp}</span>
        <div class="hp-bar-container"><div class="hp-bar-fill" style="width:${(c.player.hp/c.player.maxHp)*100}%"></div><span class="hp-text">${c.player.hp}/${c.player.maxHp}</span></div>
      </div>
      <div class="combat-player-info">
        <span>⚡ ${c.player.energy}/${c.player.maxEnergy}</span>
        <span>🛡️ ${c.player.block}</span>
        <span style="color:#6a6a8c;">Turn ${c.turn}</span>
      </div>
    </div>
    <div class="combat-area">
      <div class="enemy-zone">${enemiesHtml || '<div style="color:#4a4a6c;text-align:center;">All enemies defeated!</div>'}</div>
      <div class="player-zone">
        <div class="player-avatar">${getClassIcon(G.player.class)}</div>
        ${c.player.block > 0 ? `<div class="player-block">🛡️ ${c.player.block}</div>` : ''}
        <div class="buffs-row">${playerBuffsHtml}</div>
      </div>
    </div>
    <div class="hand-area">
      <div class="hand-cards">${handHtml || '<div style="color:#4a4a6c;text-align:center;">No cards in hand</div>'}</div>
    </div>
    <div class="discard-pile-info">Draw: ${c.drawPile.length} | Discard: ${c.discardPile.length} | Powers: ${c.powers.length}</div>
    <div class="combat-footer">
      <div class="potion-slots">${potionsHtml}</div>
      <button class="end-turn-btn" onclick="${c.isPlayerTurn ? 'endCombatTurn()' : ''}" ${!c.isPlayerTurn ? 'disabled style="opacity:0.3"' : ''}>End Turn</button>
    </div>
  `;
}

// ── function: canPlayCardInCombat ──
function canPlayCardInCombat(card) {
  const c = G.combat;
  if (!c.isPlayerTurn) return false;
  if (card.type === CARD_TYPES.CURSE || card.type === CARD_TYPES.STATUS) return false;
  if (card.cost < 0 && card.id !== 'vital_surge') return false;
  if (card.cost > c.player.energy && !(card.cost < 0)) return false;
  return true;
}

// ── function: renderRewardsScreen ──
function renderRewardsScreen() {
  const el = document.getElementById('screen-rewards');
  const r = G.rewards;
  
  let cardsHtml = '';
  for (let i = 0; i < r.cards.length; i++) {
    const card = r.cards[i];
    cardsHtml += `<div class="reward-card" onclick="chooseRewardCard(${i})">
      <div style="text-align:center;margin-bottom:5px;">
        <span style="color:#c9a84c;font-size:0.9em;">${card.name}</span>
      </div>
      <div class="rarity-${card.rarity}" style="font-size:0.7em;text-align:center;">${card.rarity.toUpperCase()}</div>
      <div style="color:#9a9ab0;font-size:0.75em;text-align:center;margin-top:8px;flex:1;">${card.desc}</div>
    </div>`;
  }

  el.innerHTML = `
    <h2 class="reward-title">Victory!</h2>
    <div class="gold-reward">💰 +${r.gold} Gold</div>
    <p style="color:#8a7a5c;margin-bottom:10px;">Choose a card:</p>
    <div class="card-choice-row">${cardsHtml}</div>
    ${r.potion ? `<button class="btn-secondary" onclick="takePotionReward()" style="margin-top:10px;">🧪 Take Potion</button>` : ''}
    <br><button class="btn-secondary" onclick="skipRewards()">Skip</button>
  `;
}

// ── function: renderShopScreen ──
function renderShopScreen() {
  const el = document.getElementById('screen-shop');
  const shop = G.shopData;
  
  let itemsHtml = '';
  
  // Cards for sale
  for (let i = 0; i < shop.cards.length; i++) {
    const card = shop.cards[i];
    const price = card.rarity === RARITIES.RARE ? 150 : card.rarity === RARITIES.UNCOMMON ? 75 : 50;
    itemsHtml += `<div class="shop-item" onclick="buyShopCard(${i})">
      <div style="color:#c9a84c;font-size:0.8em;">${card.name}</div>
      <div class="rarity-${card.rarity}" style="font-size:0.65em;">${card.rarity.toUpperCase()}</div>
      <div style="color:#9a9ab0;font-size:0.65em;margin-top:4px;">${card.desc}</div>
      <div class="shop-price">💰 ${price}g</div>
    </div>`;
  }

  // Relics for sale
  for (let i = 0; i < shop.relics.length; i++) {
    const relic = shop.relics[i];
    itemsHtml += `<div class="shop-item" onclick="buyShopRelic(${i})">
      <div style="color:#c9a84c;font-size:0.8em;">${relic.name}</div>
      <div style="color:#9a9ab0;font-size:0.65em;margin-top:4px;">${relic.desc}</div>
      <div class="shop-price">💰 150g</div>
    </div>`;
  }

  // Potions for sale
  for (let i = 0; i < shop.potions.length; i++) {
    const potion = shop.potions[i];
    itemsHtml += `<div class="shop-item" onclick="buyShopPotion(${i})">
      <div style="font-size:1.2em;">${potion.icon}</div>
      <div style="color:#c9a84c;font-size:0.75em;">${potion.name}</div>
      <div class="shop-price">💰 50g</div>
    </div>`;
  }

  // Card removal option
  if (shop.canRemove) {
    itemsHtml += `<div class="shop-item" onclick="removeCardFromShop()">
      <div style="font-size:1.2em;">🗑️</div>
      <div style="color:#c9a84c;font-size:0.75em;">Remove Card</div>
      <div class="shop-price">💰 75g</div>
    </div>`;
  }

  el.innerHTML = `
    <h2 class="reward-title">🛒 Shop</h2>
    <div style="color:#c9a84c;margin-bottom:10px;">💰 ${G.player.gold} Gold</div>
    <div class="shop-grid">${itemsHtml}</div>
    <button class="btn-secondary" onclick="leaveShop()">Leave Shop</button>
  `;
}

// ── function: renderEventScreen ──
function renderEventScreen() {
  const el = document.getElementById('screen-event');
  const evt = G.eventData;
  
  let choicesHtml = '';
  for (let i = 0; i < evt.choices.length; i++) {
    choicesHtml += `<button class="btn-primary" onclick="chooseEvent(${i})">${evt.choices[i].text}</button>`;
  }

  el.innerHTML = `
    <h2 class="reward-title">${evt.title}</h2>
    <p class="event-text">${evt.text}</p>
    <div class="event-choices">${choicesHtml}</div>
  `;
}

// ── function: renderRestScreen ──
function renderRestScreen() {
  const el = document.getElementById('screen-rest');
  
  // Deck view for forge/meditate
  let deckCardsHtml = '';
  for (let i = 0; i < G.player.deck.length; i++) {
    const card = G.player.deck[i];
    deckCardsHtml += `<div class="mini-card" onclick="selectDeckCard(${i})">
      <div style="color:#c9a84c;font-size:0.7em;text-align:center;">${card.name}</div>
      <div style="color:#9a9ab0;font-size:0.6em;text-align:center;margin-top:2px;">${card.desc.substring(0, 30)}...</div>
    </div>`;
  }

  el.innerHTML = `
    <h2 class="reward-title">🏕️ Rest Site</h2>
    <p style="color:#8a7a5c;margin-bottom:15px;">Choose an action:</p>
    <div class="rest-options" id="restOptions">
      <div class="rest-option" onclick="restAction('heal')">
        <div style="font-size:2em;">💤</div>
        <div style="color:#c9a84c;margin-top:5px;">Rest</div>
        <div style="color:#8a7a5c;font-size:0.75em;">Heal 25% HP</div>
      </div>
      <div class="rest-option" onclick="restAction('forge')">
        <div style="font-size:2em;">⚒️</div>
        <div style="color:#c9a84c;margin-top:5px;">Forge</div>
        <div style="color:#8a7a5c;font-size:0.75em;">Upgrade a card</div>
      </div>
      <div class="rest-option" onclick="restAction('meditate')">
        <div style="font-size:2em;">🧘</div>
        <div style="color:#c9a84c;margin-top:5px;">Meditate</div>
        <div style="color:#8a7a5c;font-size:0.75em;">Remove a card</div>
      </div>
    </div>
    <div id="restDeckView" class="deck-view" style="display:none;">
      <p style="color:#8a7a5c;margin-bottom:10px;font-size:0.9em;" id="restDeckTitle">Select a card:</p>
      <div class="deck-cards-grid">${deckCardsHtml}</div>
      <button class="btn-secondary" onclick="cancelRestAction()">Cancel</button>
    </div>
  `;
}

// ── function: renderVictoryScreen ──
function renderVictoryScreen() {
  const el = document.getElementById('screen-victory');
  el.innerHTML = `
    <h1 class="victory-text">🏆 Victory!</h1>
    <p style="color:#c9a84c;font-size:1.2em;">You have defeated the Archdemon and conquered the Abyss!</p>
    <p style="color:#8a7a5c;margin-top:10px;">Class: ${getClassName(G.player.class)} | Act 3 Cleared</p>
    <button class="btn-primary" onclick="location.reload()" style="margin-top:20px;">Play Again</button>
  `;
}

// ── function: renderDefeatScreen ──
function renderDefeatScreen() {
  const el = document.getElementById('screen-defeat');
  el.innerHTML = `
    <h1 class="defeat-text">💀 Defeated</h1>
    <p style="color:#8a7a5c;font-size:1.1em;">Your soul has been consumed by the Abyss...</p>
    <p style="color:#6a6a8c;margin-top:10px;">Act ${G.act}, Floor ${G.floor}</p>
    <button class="btn-primary" onclick="location.reload()" style="margin-top:20px;">Try Again</button>
  `;
}

// ── let: selectedDeckCardIndex ──
// Game Flow Logic
let selectedDeckCardIndex = -1;

// ── let: currentRestAction ──
let currentRestAction = '';

// ── function: selectClass ──
function selectClass(classId) {
  G = createGameState(classId);
  G.mapNodes = generateMap(1);
  G.screen = 'map';
  showScreen('screen-map');
  renderMapScreen();
}

// ── function: selectNode ──
function selectNode(index) {
  const node = G.mapNodes[index];
  if (!node || !node.available || node.visited) return;

  node.visited = true;
  node.available = false;
  G.floor = node.floor;

  // Mark next nodes available
  updateMapAvailability();

  switch (node.type) {
    case 'enemy': startCombat(node.enemyKeys); break;
    case 'elite': startCombat([node.eliteKey]); break;
    case 'boss': startCombat(node.enemyKeys, true); break;
    case 'rest': showScreen('screen-rest'); renderRestScreen(); break;
    case 'shop': openShop(); break;
    case 'event': triggerEvent(); break;
  }
}

// ── function: startCombat ──
function startCombat(enemyKeys, isBoss = false) {
  G.combat = new Combat(enemyKeys, G.player);
  G.combat.isBossFight = isBoss;
  // Start first player turn (draw cards, gain energy)
  G.combat.startPlayerTurn();
  showScreen('screen-combat');
  renderCombatScreen();
}

// ── function: selectTargetEnemy ──
function selectTargetEnemy(index) {
  if (!G.combat || !G.combat.isPlayerTurn) return;
  G.combat.targetEnemyIndex = index;
}

// ── function: playCardFromHand ──
function playCardFromHand(index) {
  if (!G.combat || !G.combat.isPlayerTurn) return;
  const result = G.combat.playCard(index);
  if (result) renderCombatScreen();
}

// ── function: endCombatTurn ──
function endCombatTurn() {
  if (!G.combat || !G.combat.isPlayerTurn) return;
  G.combat.endPlayerTurn();
  
  // Check combat outcome after enemy turn
  if (G.combat.won) {
    setTimeout(() => showCombatRewards(false), 500);
  } else if (G.combat.lost) {
    setTimeout(() => {
      showScreen('screen-defeat');
      renderDefeatScreen();
    }, 500);
  } else if (G.combat.escaped) {
    // Smoke bomb escape - no rewards, continue map
    G.screen = 'map';
    showScreen('screen-map');
    renderMapScreen();
  } else {
    renderCombatScreen();
  }
}

// ── function: usePotionFromCombat ──
function usePotionFromCombat(index) {
  if (!G.combat || !G.combat.isPlayerTurn) return;
  const result = G.combat.usePotion(index);
  if (result && G.combat.escaped) {
    G.screen = 'map';
    showScreen('screen-map');
    renderMapScreen();
  } else if (result) {
    renderCombatScreen();
  }
}

// ── function: showCombatRewards ──
function showCombatRewards(isBoss) {
  const c = G.combat;
  
  // Calculate gold reward
  let goldReward = isBoss ? 50 : Math.floor(Math.random() * 11) + 10; // 10-20 normal, 25-35 elite, 50 boss
  if (c.enemies.some(e => e.isElite)) goldReward = Math.floor(Math.random() * 11) + 25;
  
  // Greedy Imp relic bonus
  for (const r of G.player.relics) {
    if (r.id === 'greedy_imp') goldReward += 10;
  }

  // Ectoplasm: no gold gain
  for (const r of G.player.relics) {
    if (r.id === 'ectoplasm') goldReward = 0;
  }

  G.player.gold += goldReward;

  // Ashen Armor relic: heal at end of combat
  for (const r of G.player.relics) {
    if (r.id === 'ashen_armor') G.player.hp = Math.min(G.player.maxHp, G.player.hp + 6);
  }

  // Card rewards
  const pool = CLASS_POOLS[G.player.class];
  const cards = pickRewardCards(pool, isBoss, c.enemies.some(e => e.isElite));

  // Potion chance
  let potionDrop = false;
  if (isBoss) {
    potionDrop = true;
    // Boss drops 2 potions
    for (let i = 0; i < 2; i++) {
      if (G.player.potions.length < 3) G.player.potions.push({ ...POTIONS[Math.floor(Math.random() * POTIONS.length)] });
    }
  } else if (c.enemies.some(e => e.isElite)) {
    potionDrop = Math.random() < 0.7;
  } else {
    potionDrop = Math.random() < 0.4;
  }

  // Relic from elite
  let relicReward = null;
  if (c.enemies.some(e => e.isElite)) {
    const eligibleRelics = RELICS.filter(r => !r.classSpecific || r.classSpecific === G.player.class);
    relicReward = { ...eligibleRelics[Math.floor(Math.random() * eligibleRelics.length)] };
    G.player.relics.push(relicReward);
  }

  // Boss rewards: choose from boss relics
  let bossRelicChoice = null;
  if (isBoss) {
    const available = BOSS_RELICS.filter(r => !G.player.relics.find(pr => pr.id === r.id));
    bossRelicChoice = [];
    for (let i = 0; i < Math.min(3, available.length); i++) {
      bossRelicChoice.push({ ...available[i] });
    }
  }

  // Full heal after boss
  if (isBoss) {
    G.player.hp = G.player.maxHp;
  }

  G.rewards = { gold: goldReward, cards, potionDrop, relicReward, bossRelicChoice };
  
  // Sync player HP from combat back to game state
  G.player.hp = c.player.hp;

  showScreen('screen-rewards');
  renderRewardsScreen();
}

// ── function: chooseRewardCard ──
function chooseRewardCard(index) {
  const card = G.rewards.cards[index];
  addToDeck(G, card);
  proceedAfterReward();
}

// ── function: takePotionReward ──
function takePotionReward() {
  if (G.player.potions.length < 3) {
    G.player.potions.push({ ...POTIONS[Math.floor(Math.random() * POTIONS.length)] });
  }
  proceedAfterReward();
}

// ── function: skipRewards ──
function skipRewards() {
  proceedAfterReward();
}

// ── function: proceedAfterReward ──
function proceedAfterReward() {
  // Check for boss relic choice
  if (G.rewards.bossRelicChoice && G.rewards.bossRelicChoice.length > 0) {
    showBossRelicChoice(G.rewards.bossRelicChoice);
    return;
  }

  advanceMap();
}

// ── function: showBossRelicChoice ──
function showBossRelicChoice(relics) {
  const el = document.getElementById('screen-rewards');
  let relicsHtml = '';
  for (let i = 0; i < relics.length; i++) {
    const r = relics[i];
    relicsHtml += `<div class="reward-card" onclick="chooseBossRelic(${i})">
      <div style="text-align:center;margin-bottom:5px;">
        <span style="color:#c9a84c;font-size:0.9em;">${r.name}</span>
      </div>
      <div style="color:#9a9ab0;font-size:0.75em;text-align:center;margin-top:8px;">${r.desc}</div>
    </div>`;
  }

  el.innerHTML = `
    <h2 class="reward-title">Boss Relic</h2>
    <p style="color:#8a7a5c;margin-bottom:10px;">Choose a relic:</p>
    <div class="card-choice-row">${relicsHtml}</div>
    <button class="btn-secondary" onclick="skipBossRelic()">Skip</button>
  `;
}

// ── function: chooseBossRelic ──
function chooseBossRelic(index) {
  const relics = G.rewards.bossRelicChoice;
  if (!relics || !relics[index]) return;
  
  const relic = { ...relics[index] };
  G.player.relics.push(relic);

  // Apply immediate effects
  switch (relic.id) {
    case 'ectoplasm': G.player.gold = 0; break;
    case 'shadow_amulet': G.player.maxHp -= 15; G.player.hp = Math.min(G.player.hp, G.player.maxHp); break;
  }

  advanceMap();
}

// ── function: skipBossRelic ──
function skipBossRelic() {
  advanceMap();
}

// ── function: advanceMap ──
function advanceMap() {
  const visitedCount = G.mapNodes.filter(n => n.visited).length;
  
  if (visitedCount >= G.mapNodes.length) {
    // Act complete
    if (G.act < 3) {
      G.act++;
      G.floor = 1;
      G.mapNodes = generateMap(G.act);
      showScreen('screen-map');
      renderMapScreen();
    } else {
      // Victory!
      showScreen('screen-victory');
      renderVictoryScreen();
    }
  } else {
    showScreen('screen-map');
    renderMapScreen();
  }
}

// ── function: openShop ──
// Shop functions
function openShop() {
  const pool = CLASS_POOLS[G.player.class];
  
  G.shopData = {
    cards: [pickRandomCard(pool), pickRandomCard(pool), pickRandomCard(pool)],
    colorlessCard: COLORLESS_CARDS[Math.floor(Math.random() * COLORLESS_CARDS.length)],
    relics: [],
    potions: [],
    canRemove: true,
  };

  // Add a colorless card to the shop cards
  G.shopData.cards.push({ ...G.shopData.colorlessCard });

  // Random relics for sale (2)
  const eligibleRelics = RELICS.filter(r => !r.classSpecific || r.classSpecific === G.player.class);
  for (let i = 0; i < 2; i++) {
    G.shopData.relics.push({ ...eligibleRelics[Math.floor(Math.random() * eligibleRelics.length)] });
  }

  // Random potions for sale (3)
  for (let i = 0; i < 3; i++) {
    G.shopData.potions.push({ ...POTIONS[Math.floor(Math.random() * POTIONS.length)] });
  }

  showScreen('screen-shop');
  renderShopScreen();
}

// ── function: buyShopCard ──
function buyShopCard(index) {
  const card = G.shopData.cards[index];
  if (!card) return;
  
  const price = card.rarity === RARITIES.RARE ? 150 : card.rarity === RARITIES.UNCOMMON ? 75 : 
    (card.id && COLORLESS_CARDS.find(c => c.id === card.id)) ? 60 : 50;
  
  if (G.player.gold < price) return;
  
  G.player.gold -= price;
  addToDeck(G, card);
  G.shopData.cards.splice(index, 1);
  renderShopScreen();
}

// ── function: buyShopRelic ──
function buyShopRelic(index) {
  const relic = G.shopData.relics[index];
  if (!relic || G.player.gold < 150) return;
  
  G.player.gold -= 150;
  G.player.relics.push(relic);
  G.shopData.relics.splice(index, 1);
  renderShopScreen();
}

// ── function: buyShopPotion ──
function buyShopPotion(index) {
  const potion = G.shopData.potions[index];
  if (!potion || G.player.gold < 50) return;
  if (G.player.potions.length >= 3) return;
  
  G.player.gold -= 50;
  G.player.potions.push(potion);
  G.shopData.potions.splice(index, 1);
  renderShopScreen();
}

// ── function: removeCardFromShop ──
function removeCardFromShop() {
  if (G.player.gold < 75 || !G.shopData.canRemove) return;
  
  // Show deck to choose from
  const el = document.getElementById('screen-shop');
  let deckCardsHtml = '';
  for (let i = 0; i < G.player.deck.length; i++) {
    const card = G.player.deck[i];
    deckCardsHtml += `<div class="mini-card" onclick="confirmShopRemove(${i})">
      <div style="color:#c9a84c;font-size:0.7em;text-align:center;">${card.name}</div>
      <div style="color:#9a9ab0;font-size:0.6em;text-align:center;margin-top:2px;">${card.desc.substring(0, 30)}...</div>
    </div>`;
  }

  el.innerHTML = `
    <h2 class="reward-title">🗑️ Remove a Card</h2>
    <p style="color:#8a7a5c;margin-bottom:10px;">Select a card to remove (75g):</p>
    <div class="deck-view"><div class="deck-cards-grid">${deckCardsHtml}</div></div>
    <button class="btn-secondary" onclick="renderShopScreen()">Cancel</button>
  `;
}

// ── function: confirmShopRemove ──
function confirmShopRemove(index) {
  G.player.gold -= 75;
  G.player.deck.splice(index, 1);
  G.shopData.canRemove = false;
  renderShopScreen();
}

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

// ── function: triggerEvent ──
// Event functions
function triggerEvent() {
  const evt = EVENTS[Math.floor(Math.random() * EVENTS.length)];
  G.eventData = evt;
  showScreen('screen-event');
  renderEventScreen();
}

// ── function: chooseEvent ──
function chooseEvent(index) {
  const choice = G.eventData.choices[index];
  if (choice && choice.effect) {
    choice.effect(G);
  }
  advanceMap();
}

// ── function: restAction ──
// Rest functions
function restAction(action) {
  currentRestAction = action;
  
  if (action === 'heal') {
    G.player.hp = Math.min(G.player.maxHp, G.player.hp + Math.floor(G.player.maxHp * 0.25));
    advanceMap();
    return;
  }

  // Show deck for forge/meditate
  document.getElementById('restOptions').style.display = 'none';
  const deckView = document.getElementById('restDeckView');
  if (deckView) {
    deckView.style.display = 'block';
    document.getElementById('restDeckTitle').textContent = action === 'forge' ? 'Select a card to upgrade:' : 'Select a card to remove:';
  }
}

// ── function: selectDeckCard ──
function selectDeckCard(index) {
  selectedDeckCardIndex = index;
  
  if (currentRestAction === 'forge') {
    const card = G.player.deck[index];
    if (!card.upgraded) {
      upgradeCard(card);
    }
  } else if (currentRestAction === 'meditate') {
    G.player.deck.splice(index, 1);
  }

  advanceMap();
}

// ── function: cancelRestAction ──
function cancelRestAction() {
  currentRestAction = '';
  selectedDeckCardIndex = -1;
  renderRestScreen();
}

// ── class: Combat ──
class Combat {
  constructor(enemyKeys, playerState) {
    this.enemies = [];
    for (const key of enemyKeys) {
      const def = ENEMIES[key];
      if (!def) continue;
      this.enemies.push({ ...def, currentHp: def.hp, block: 0, intentIndex: Math.floor(Math.random() * 3), buffs: { strength: 0, dexterity: 0, regeneration: 0 }, debuffs: { weak: 0, vulnerable: 0, bleed: 0, burn: 0, poison: 0, intangible: 0, weakDuration: 0, vulnerableDuration: 0, bleedDuration: 0, burnDuration: 0, poisonDuration: 0 }, isAlive: true });
    }
    this.player = { hp: playerState.hp, maxHp: playerState.maxHp, block: 0, energy: 3, maxEnergy: 3, buffs: { strength: 0, dexterity: 0, regeneration: 0 }, debuffs: { weak: 0, vulnerable: 0, bleed: 0, burn: 0, poison: 0, intangible: 0, weakDuration: 0, vulnerableDuration: 0, bleedDuration: 0, burnDuration: 0, poisonDuration: 0 }, nextTurnBlock: 0, nextTurnEnergy: 0 };
    this.drawPile = shuffleArray(playerState.deck.map(c => ({ ...c })));
    this.discardPile = []; this.hand = []; this.powers = []; this.exhausted = [];
    this.turn = 1; this.isPlayerTurn = true; this.targetEnemyIndex = null;
    this.firstAttackCostsHpThisCombat = true;
    this._applyStartRelics(playerState);
  }

  _applyStartRelics(ps) {
    const relics = ps.relics || [];
    for (const r of relics) {
      if (r.id === 'shadow_cloak') this.player.debuffs.intangible++;
      if (r.id === 'cursed_amulet') for (const e of this.enemies) { if (e.isAlive) { e.debuffs.weak++; e.debuffs.weakDuration = 99; } }
      if (r.id === 'black_blood') this.player.hp -= Math.floor(this.player.maxHp * 0.1);
      if (r.id === 'shadow_amulet') this.player.debuffs.intangible++;
    }
    for (const r of relics) {
      if (r.id === 'demon_horns') this.hand.push({ id: 'imp_claw', name: 'Imp Claw', type: CARD_TYPES.ATTACK, cost: 0, rarity: RARITIES.BASIC, desc: 'Deal 4 damage. Exhaust.', exhaust: true });
      if (['cursed_crown', 'ectoplasm', 'philosophers_stone'].includes(r.id)) this.player.maxEnergy = 4;
    }
  }

  startPlayerTurn() {
    this.isPlayerTurn = true; this.turn++; this.player.block = 0;
    if (this.player.nextTurnBlock) { this.player.block += this.player.nextTurnBlock; this.player.nextTurnBlock = 0; }
    if (this.player.debuffs.bleed > 0) { const d = this._int(this.player.debuffs.bleed); this.player.hp -= d; this.player.debuffs.bleed = Math.floor(this.player.debuffs.bleed / 2); }
    if (this.player.debuffs.poison > 0 && this.player.debuffs.poisonDuration > 0) { const d = this._int(this.player.debuffs.poison); this.player.hp -= d; this.player.debuffs.poison--; this.player.debuffs.poisonDuration--; }
    if (this.player.debuffs.intangible > 0) this.player.debuffs.intangible--;
    for (const p of this.powers) {
      if (p.id === 'soul_siphon') { this.player.energy = Math.min(this.player.maxEnergy, this.player.energy + 1); this._dmgPlayer(1, true); }
      if (p.id === 'umbral_mastery') { const a = p.upgraded ? 2 : 1; for (const e of this.enemies) { if (e.isAlive) { e.debuffs.weak += a; e.debuffs.weakDuration = Math.max(e.debuffs.weakDuration, 99); } } }
      if (p.id === 'hellfire_aura') { const d = p.upgraded ? 5 : 3; for (const e of this.enemies) { if (e.isAlive) this._dmgEnemy(e, d); } }
    }
    for (const r of G.player.relics) { if (r.id === 'bleeding_edge') { const ae = this.enemies.filter(e => e.isAlive); if (ae.length > 0) { const t = ae[Math.floor(Math.random() * ae.length)]; t.debuffs.bleed++; t.debuffs.bleedDuration = Math.max(t.debuffs.bleedDuration, 99); } } }
    for (const r of G.player.relics) { if (r.id === 'eternal_hourglass' && this.hand.length === 0) this.player.energy = Math.min(this.player.maxEnergy, this.player.energy + 1); }
    this.player.energy = this.player.maxEnergy;
    if (this.player.nextTurnEnergy > 0) { this.player.energy += this.player.nextTurnEnergy; this.player.nextTurnEnergy = 0; }
    if (G.player.temporaryBonusEnergy > 0) { this.player.energy += G.player.temporaryBonusEnergy; G.player.temporaryBonusEnergy--; }
    this.drawCards(5);
    for (const e of this.enemies) { if (e.isAlive && e.debuffs.bleed > 0) { e.currentHp -= e.debuffs.bleed; e.debuffs.bleed = Math.floor(e.debuffs.bleed / 2); } }
    for (const e of this.enemies) { if (!e.isBoss || !e.phase2Entered) continue; if (e.currentHp <= Math.floor(e.maxHp * 0.5) && !e.inPhase2) e.inPhase2 = true; }
  }

  drawCards(n) { for (let i = 0; i < n; i++) { if (this.drawPile.length === 0 && this.discardPile.length > 0) { this.drawPile = shuffleArray(this.discardPile.filter(c => !c.exhaust)); this.discardPile = []; } if (this.drawPile.length > 0) this.hand.push(this.drawPile.pop()); } }

  canPlayCard(card) {
    if (card.type === CARD_TYPES.CURSE || card.type === CARD_TYPES.STATUS) return false;
    if (card.cost < 0 && card.id !== 'vital_surge') return false;
    if (card.cost > this.player.energy && !(card.cost < 0)) return false;
    return true;
  }

  playCard(idx) {
    const card = this.hand[idx];
    if (!this.canPlayCard(card)) return false;
    if (card.cost >= 0) this.player.energy -= card.cost;
    let tIdx = this.targetEnemyIndex !== null ? this.targetEnemyIndex : 0;
    const ae = this.enemies.filter(e => e.isAlive);
    if (ae.length === 0) { this.hand.splice(idx, 1); return true; }
    this._execCard(card, tIdx);
    for (const r of G.player.relics) { if (r.id === 'tome_dark_rituals' && card.type === CARD_TYPES.POWER) this.drawCards(1); }
    this.hand.splice(idx, 1);
    if (card.exhaust || card.type === CARD_TYPES.STATUS) this.exhausted.push(card);
    else if (card.type === CARD_TYPES.POWER) this.powers.push(card);
    else this.discardPile.push(card);
    return true;
  }

  _dmgEnemy(e, d) { let r = Math.max(0, d || 0); if (e.block > 0) { const b = Math.min(e.block, r); e.block -= b; r -= b; } e.currentHp -= r; if (e.currentHp <= 0) { e.currentHp = 0; e.isAlive = false; } }
  _dmgPlayer(d, fromCard) { let f = Math.max(0, d || 0); if (this.player.debuffs.intangible > 0 && fromCard) f = 1; this.player.hp -= f; }
  _int(d) { return this.player.debuffs.intangible > 0 ? 1 : Math.max(0, d || 0); }

  endPlayerTurn() {
    for (const c of this.hand) { if (c.id === 'doubt') this._dmgPlayer(1, false); }
    if (this.player.debuffs.burn > 0 && this.player.debuffs.burnDuration > 0) { const d = this._int(this.player.debuffs.burn); this.player.hp -= d; this.player.debuffs.burn = Math.floor(this.player.debuffs.burn / 2); }
    if (this.player.buffs.regeneration > 0) { this.player.hp = Math.min(this.player.maxHp, this.player.hp + this.player.buffs.regeneration); this.player.buffs.regeneration--; }
    this.discardPile.push(...this.hand); this.hand = []; this.isPlayerTurn = false;
    this._enemyTurn();
  }

  _enemyTurn() {
    for (const enemy of this.enemies) {
      if (!enemy.isAlive) continue;
      if (enemy.isBoss && enemy.inPhase2) enemy.buffs.strength += (enemy.name === 'Archdemon' ? 2 : 1);
      let intents = enemy.isBoss && enemy.inPhase2 ? enemy.phase2Intents : enemy.intents;
      const intent = intents[enemy.intentIndex % intents.length];
      enemy.block = 0; this._execEnemy(enemy, intent);
      if (enemy.debuffs.burn > 0) { enemy.currentHp -= enemy.debuffs.burn; enemy.debuffs.burn = Math.floor(enemy.debuffs.burn / 2); }
      if (enemy.buffs.regeneration > 0) { enemy.currentHp = Math.min(enemy.maxHp, enemy.currentHp + enemy.buffs.regeneration); enemy.buffs.regeneration--; }
      enemy.intentIndex++;
    }
    if (this.enemies.filter(e => e.isAlive).length === 0) { G.combat.won = true; return; }
    if (this.player.hp <= 0) { G.combat.lost = true; return; }
    this.startPlayerTurn();
  }

  _execEnemy(enemy, intent) {
    const ae = this.enemies.filter(e => e.isAlive); const s = enemy.buffs.strength || 0;
    switch (intent.type) {
      case 'attack': this._dmgP(s ? intent.value + s : intent.value); break;
      case 'defend': enemy.block += intent.value; break;
      case 'multi_attack': for (let i = 0; i < intent.hits; i++) this._dmgP(intent.value); break;
      case 'attack_debuff': { this._dmgP(s ? intent.attackValue + s : intent.attackValue); if (intent.debuffType === 'weak') { this.player.debuffs.weak += intent.value; this.player.debuffs.weakDuration = Math.max(this.player.debuffs.weakDuration, 3); } if (intent.debuffType === 'burn') { this.player.debuffs.burn += intent.value; this.player.debuffs.burnDuration = Math.max(this.player.debuffs.burnDuration, 99); } if (intent.debuffType === 'vulnerable') { this.player.debuffs.vulnerable += intent.value; this.player.debuffs.vulnerableDuration = Math.max(this.player.debuffs.vulnerableDuration, 3); } break; }
      case 'debuff': { if (intent.debuffType === 'weak') { this.player.debuffs.weak += intent.value; this.player.debuffs.weakDuration = Math.max(this.player.debuffs.weakDuration, 3); } if (intent.debuffType === 'vulnerable') { this.player.debuffs.vulnerable += intent.value; this.player.debuffs.vulnerableDuration = Math.max(this.player.debuffs.vulnerableDuration, 3); } if (intent.debuffType === 'bleed') { this.player.debuffs.bleed += intent.value; this.player.debuffs.bleedDuration = Math.max(this.player.debuffs.bleedDuration, 99); } if (intent.debuffType === 'poison') { this.player.debuffs.poison += intent.value; this.player.debuffs.poisonDuration = Math.max(this.player.debuffs.poisonDuration, 3); } break; }
      case 'debuff_combo': { if (intent.weak) { this.player.debuffs.weak += intent.weak; this.player.debuffs.weakDuration = Math.max(this.player.debuffs.weakDuration, 3); } if (intent.vulnerable) { this.player.debuffs.vulnerable += intent.vulnerable; this.player.debuffs.vulnerableDuration = Math.max(this.player.debuffs.vulnerableDuration, 3); } break; }
      case 'buff': if (intent.buffType === 'strength') enemy.buffs.strength += intent.value; break;
      case 'self_buff': enemy.block += (intent.defendValue || 0); enemy.buffs.regeneration += (intent.regenValue || 0); break;
      case 'self_buff_intangible': enemy.debuffs.intangible++; break;
      case 'self_vuln_defend': enemy.block += intent.defendValue; enemy.debuffs.vulnerable++; break;
      case 'attack_defend': this._dmgP(s ? intent.attackValue + s : intent.attackValue); enemy.block += intent.defendValue; break;
      case 'defend_buff': enemy.block += intent.defendValue; if (intent.buffType === 'strength') enemy.buffs.strength += intent.value; break;
      case 'drain': this._dmgP(s ? intent.attackValue + s : intent.attackValue); enemy.currentHp = Math.min(enemy.maxHp, enemy.currentHp + intent.healValue); break;
      case 'defend_regen': enemy.block += intent.defendValue; enemy.buffs.regeneration += intent.regenValue; break;
      case 'defend_heal': enemy.block += intent.defendValue; enemy.currentHp = Math.min(enemy.maxHp, enemy.currentHp + intent.healValue); break;
      case 'summon': { const sd = ENEMIES[intent.summonEnemy]; if (sd) { let cs = true; if (enemy.isBoss && enemy.name === 'Lich King' && !enemy.inPhase2 && this.enemies.filter(e => e.name.includes('Minion') || e.name.includes('Skeleton')).length >= 2) cs = false; if (enemy.name === 'Infernal Imp' && this.enemies.filter(e => e.name === 'Infernal Imp').length >= 3) cs = false; if (cs) this.enemies.push({ ...sd, currentHp: sd.hp, block: 0, intentIndex: Math.floor(Math.random() * 3), buffs: { strength: 0, dexterity: 0, regeneration: 0 }, debuffs: { weak: 0, vulnerable: 0, bleed: 0, burn: 0, poison: 0, intangible: 0, weakDuration: 0, vulnerableDuration: 0, bleedDuration: 0, burnDuration: 0, poisonDuration: 0 }, isAlive: true }); else this._dmgP(5); } break; }
      case 'buff_all': for (const e of ae) { if (intent.buffType === 'strength') e.buffs.strength += intent.value; } break;
    }
  }

  _dmgP(d) { let r = Math.max(0, d || 0); if (this.player.block > 0) { const b = Math.min(this.player.block, r); this.player.block -= b; r -= b; } this.player.hp -= r; }

  usePotion(idx) {
    const p = G.player.potions[idx]; if (!p) return false;
    switch (p.id) {
      case 'blood_vial': this.player.hp = Math.min(this.player.maxHp, this.player.hp + 15); break;
      case 'firebomb': for (const e of this.enemies) { if (e.isAlive) this._dmgEnemy(e, 10); } break;
      case 'dex_tonic': this.player.buffs.dexterity += 3; break;
      case 'energy_elixir': this.player.energy += 2; break;
      case 'cursed_brew': for (const e of this.enemies) { if (e.isAlive) e.debuffs.vulnerable += 3; } break;
      case 'bleeding_flask': { const a = this.enemies.filter(e => e.isAlive); if (a.length > 0) a[0].debuffs.bleed += 6; } break;
      case 'imp_in_bottle': this.hand.push({ id: 'imp_claw_plus', name: 'Imp Claw+', type: CARD_TYPES.ATTACK, cost: 0, rarity: RARITIES.BASIC, desc: 'Deal 6 damage. Exhaust.', exhaust: true }); break;
      case 'smoke_bomb': G.combat.escaped = true; return true;
    }
    G.player.potions.splice(idx, 1); return true;
  }

  _execCard(card, tIdx) {
    const ae = this.enemies.filter(e => e.isAlive); if (ae.length === 0) return;
    let te = null; for (const e of this.enemies) { if (e.isAlive && e.name === ae[tIdx]?.name || (this.targetEnemyIndex !== null && this.enemies[this.targetEnemyIndex] === e)) { te = e; break; } }
    if (!te) te = ae[0];
    const tgt = () => te; const allA = () => this.enemies.filter(e => e.isAlive);
    const strB = this.player.buffs.strength || 0; const dexB = this.player.buffs.dexterity || 0;

    const dd = (e, d) => { let f = Math.max(1, d + strB); if (this.player.debuffs.weak > 0 && this.player.debuffs.weakDuration > 0) for (let i = 0; i < Math.min(this.player.debuffs.weak, this.player.debuffs.weakDuration); i++) f = Math.floor(f * 0.75); if (e.debuffs.vulnerable > 0 && e.debuffs.vulnerableDuration > 0) for (let i = 0; i < Math.min(e.debuffs.vulnerable, e.debuffs.vulnerableDuration); i++) f = Math.ceil(f * 1.5); this._dmgEnemy(e, f); if (card.id && ['firebolt', 'hellfire_blast', 'flame_ward', 'soul_burn'].includes(card.id)) for (const r of G.player.relics) { if (r.id === 'soul_ember') this._dmgEnemy(e, 2); } if (card.type === CARD_TYPES.ATTACK) for (const r of G.player.relics) { if (r.id === 'black_blood') this.player.hp = Math.min(this.player.maxHp, this.player.hp + 1); } };
    const dda = (d) => { allA().forEach(e => dd(e, d)); };
    const gb = (a) => { this.player.block += a + dexB; };
    const dc = (n) => { this.drawCards(n); };
    const ade = (e, t, v) => { e.debuffs[t] += v; e.debuffs[t + 'Duration'] = Math.max(e.debuffs[t + 'Duration'], 99); };
    const ada = (t, v) => { allA().forEach(e => ade(e, t, v)); };

    let hpr = 0; if ((card.type === CARD_TYPES.ATTACK || card.id === 'bloodletting') && G.player.relics.some(r => r.id === 'sacrificial_dagger' && this.firstAttackCostsHpThisCombat)) { hpr = 1; this.firstAttackCostsHpThisCombat = false; }

    switch (card.id) {
      case 'strike': dd(tgt(), card.upgraded ? 8 : 6); break;
      case 'defend': gb(card.upgraded ? 8 : 5); break;
      case 'sanguine_strike': dd(tgt(), card.upgraded ? 10 : 8); this.player.hp = Math.min(this.player.maxHp, this.player.hp + (card.upgraded ? 3 : 2)); break;
      case 'hemorrhage': dd(tgt(), card.upgraded ? 6 : 5); ade(tgt(), 'bleed', card.upgraded ? 4 : 3); break;
      case 'bloodletting': { const hl = Math.max(1, (card.upgraded ? 2 : 3) - hpr); this._dmgPlayer(this._int(hl), true); dd(tgt(), card.upgraded ? 15 : 12); break; }
      case 'crimson_eruption': { const hl = Math.max(1, 2 - hpr); this._dmgPlayer(this._int(hl), true); dda(card.upgraded ? 10 : 8); break; }
      case 'crimson_shield': gb(card.upgraded ? 8 : 6); this._dmgPlayer(this._int(2), true); break;
      case 'blood_ward': gb(card.upgraded ? 4 : 3); this.player.buffs.regeneration += (card.upgraded ? 2 : 1); break;
      case 'sacrificial_barrier': gb(card.upgraded ? 13 : 10); this._dmgPlayer(this._int(5), true); break;
      case 'vital_surge': { const m = this.player.maxHp - this.player.hp; gb(m + (card.upgraded ? 3 : 0)); card.exhaust = true; break; }
      case 'life_tap': dc(card.upgraded ? 3 : 2); this._dmgPlayer(this._int(3), true); break;
      case 'rend_flesh': ada('bleed', card.upgraded ? 7 : 5); this._dmgPlayer(this._int(2), true); break;
      case 'soul_siphon': case 'blood_pact': break;
      case 'shadow_bolt': dd(tgt(), card.upgraded ? 8 : 6); ade(tgt(), 'weak', card.upgraded ? 2 : 1); break;
      case 'grasp_of_despair': dd(tgt(), card.upgraded ? 5 : 4); ade(tgt(), 'vulnerable', card.upgraded ? 2 : 1); break;
      case 'nightmare_lash': { const t = tgt(); dd(t, card.upgraded ? 9 : 7); if (t.debuffs.weak > 0) dd(t, card.upgraded ? 9 : 7); break; }
      case 'eternal_darkness': for (let i = 0; i < 3; i++) { const t = allA()[Math.floor(Math.random() * allA().length)]; if (t) dd(t, card.upgraded ? 4 : 3); } break;
      case 'cloak_of_shadows': gb(card.upgraded ? 7 : 5); dc(1); break;
      case 'haunting_mist': gb(card.upgraded ? 9 : 7); ada('weak', 1); break;
      case 'phantom_armor': gb(card.upgraded ? 10 : 8); this.player.buffs.dexterity += (card.upgraded ? 3 : 2); break;
      case 'whispering_shroud': gb(card.upgraded ? 5 : 4); this.player.nextTurnBlock += (card.upgraded ? 5 : 4); break;
      case 'shadowform': this.player.debuffs.intangible += (card.upgraded ? 3 : 2); card.exhaust = true; break;
      case 'curse_of_misfortune': { const t = tgt(); if (t.buffs.strength > 0) t.buffs.strength -= (card.upgraded ? 4 : 3); else t.buffs.strength = -(card.upgraded ? 4 : 3); ade(t, 'vulnerable', card.upgraded ? 2 : 1); break; }
      case 'dark_ritual': this.player.energy += 2; this.discardPile.push({ ...DAZED }); break;
      case 'umbral_mastery': break;
      case 'firebolt': dd(tgt(), card.upgraded ? 9 : 7); ade(tgt(), 'burn', card.upgraded ? 3 : 2); break;
      case 'hellfire_blast': dda(card.upgraded ? 12 : 9); break;
      case 'searing_strike': dd(tgt(), card.upgraded ? 8 : 6); this.player.nextTurnEnergy += 1; break;
      case 'immolate': dd(tgt(), card.upgraded ? 16 : 12); card.exhaust = true; break;
      case 'obsidian_skin': gb(card.upgraded ? 10 : 7); break;
      case 'flame_ward': gb(card.upgraded ? 6 : 5); { const t = allA()[Math.floor(Math.random() * allA().length)]; if (t) ade(t, 'burn', card.upgraded ? 3 : 2); } break;
      case 'demonic_resilience': gb(card.upgraded ? 5 : 4); this.player.hp = Math.min(this.player.maxHp, this.player.hp + (card.upgraded ? 3 : 2)); break;
      case 'brimstone_shield': gb(card.upgraded ? 4 : 3); this.player.buffs.strength += (card.upgraded ? 3 : 2); break;
      case 'summon_imp': { const imp = card.upgraded ? { id: 'imp_claw_plus', name: 'Imp Claw+', type: CARD_TYPES.ATTACK, cost: 0, rarity: RARITIES.BASIC, desc: 'Deal 6 damage. Exhaust.', exhaust: true } : { id: 'imp_claw', name: 'Imp Claw', type: CARD_TYPES.ATTACK, cost: 0, rarity: RARITIES.BASIC, desc: 'Deal 4 damage. Exhaust.', exhaust: true }; this.hand.push(imp); card.exhaust = true; break; }
      case 'infernal_pact': { const hl = Math.max(1, (card.upgraded ? 1 : 2) - hpr); this._dmgPlayer(this._int(hl), true); this.player.energy += 2; const imp = card.upgraded ? { id: 'imp_claw_plus', name: 'Imp Claw+', type: CARD_TYPES.ATTACK, cost: 0, rarity: RARITIES.BASIC, desc: 'Deal 6 damage. Exhaust.', exhaust: true } : { id: 'imp_claw', name: 'Imp Claw', type: CARD_TYPES.ATTACK, cost: 0, rarity: RARITIES.BASIC, desc: 'Deal 4 damage. Exhaust.', exhaust: true }; this.hand.push(imp); break; }
      case 'hellfire_aura': break;
      case 'soul_burn': ade(tgt(), 'burn', card.upgraded ? 10 : 8); dc(1); break;
      case 'imp_claw': dd(tgt(), 4); card.exhaust = true; break;
      case 'imp_claw_plus': dd(tgt(), 6); card.exhaust = true; break;
      case 'flash_of_insight': dc(card.upgraded ? 3 : 2); break;
      case 'iron_will': gb(card.upgraded ? 11 : 8); break;
      case 'quick_slash': dd(tgt(), card.upgraded ? 9 : 7); break;
      case 'potent_brew': this.player.energy += (card.upgraded ? 2 : 1); card.exhaust = true; break;
    }
    for (const r of G.player.relics) { if (r.id === 'bloodstone' && ['bloodletting', 'crimson_eruption', 'crimson_shield', 'sacrificial_barrier', 'life_tap', 'rend_flesh', 'infernal_pact'].includes(card.id)) this.player.hp = Math.min(this.player.maxHp, this.player.hp + 1); }
    for (const p of this.powers) { if (p.id === 'blood_pact' && ['bloodletting', 'crimson_eruption', 'crimson_shield', 'sacrificial_barrier', 'life_tap', 'rend_flesh', 'infernal_pact'].includes(card.id)) this.player.buffs.strength += (p.upgraded ? 2 : 1); }
  }
}

// ── main ──
// Initialize game
renderTitleScreen();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"composer","sessionId":"639fb44e-9741-49c6-a74c-791022c97219","tokensIn":3427197,"tokensOut":67651,"tokensTotal":3494848,"turns":61,"toolCalls":60,"failedToolCalls":2,"timestamp":"2026-07-18T16:17:14.670Z"} -->