← Shadows results

Shadows

Ornith 1.5 35B A3B · full

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 88,209 bytes · SHA-256 10f8bde52538
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadows of the Abyss</title>
<style>
  :root{
    --bg:#0b0810; --bg2:#150e1c; --panel:#1d1526; --panel2:#261b31;
    --gold:#d9b24a; --gold2:#f0d68a; --blood:#8b1a1a; --blood2:#c0392b;
    --purple:#6a2c9c; --purple2:#9b59b6; --teal:#1f6f6f; --text:#e8e0f0;
    --muted:#9a8fb0; --line:#3a2b4a; --block:#5b9dd5; --hp:#b02828;
  }
  *{box-sizing:border-box;}
  html,body{margin:0; padding:0; background:radial-gradient(circle at 50% 0%, #1a1224 0%, var(--bg) 70%); color:var(--text); font-family:'Trebuchet MS','Segoe UI',system-ui,sans-serif; min-height:100vh;}
  #app{max-width:1100px; margin:0 auto; padding:16px; min-height:100vh;}
  h1,h2,h3{margin:0 0 8px; font-weight:700; letter-spacing:.5px;}
  .title-logo{font-size:46px; text-align:center; background:linear-gradient(180deg,var(--gold2),var(--gold) 60%,#7a5a1e); -webkit-background-clip:text; background-clip:text; color:transparent; text-shadow:0 0 24px rgba(217,178,74,.25); letter-spacing:2px; margin-bottom:2px;}
  .title-sub{text-align:center; color:var(--muted); letter-spacing:6px; font-size:13px; text-transform:uppercase; margin-bottom:20px;}
  button{font-family:inherit; cursor:pointer; border:1px solid var(--line); background:var(--panel2); color:var(--text); padding:10px 18px; border-radius:8px; font-size:15px; transition:.15s; }
  button:hover:not(:disabled){background:#362645; border-color:var(--gold); transform:translateY(-1px);}
  button:disabled{opacity:.4; cursor:not-allowed;}
  button.primary{background:linear-gradient(180deg,#7a2a2a,#5a1616); border-color:#a33;}
  button.primary:hover{background:linear-gradient(180deg,#9a3434,#6a1a1a);}
  button.gold{background:linear-gradient(180deg,#6a5020,#4a3a14); border-color:var(--gold);}
  button.gold:hover{border-color:var(--gold2);}
  .btn-row{display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-top:14px;}
  .card{width:168px; min-height:232px; border-radius:12px; padding:10px; background:linear-gradient(180deg,var(--panel2),#140e1f); border:2px solid var(--line); position:relative; box-shadow:0 6px 16px rgba(0,0,0,.5); transition:.15s; user-select:none;}
  .card.attack{border-color:#a33;} .card.skill{border-color:#3a6;} .card.power{border-color:var(--purple2);}
  .card .cost{position:absolute; top:-8px; left:-8px; width:34px; height:34px; border-radius:50%; background:radial-gradient(circle at 35% 30%,#6ec1ff,#1f5fb0); display:flex; align-items:center; justify-content:center; font-weight:800; font-size:16px; border:2px solid #cfe6ff; color:#fff; box-shadow:0 2px 6px rgba(0,0,0,.6);}
  .card .cname{font-size:14px; font-weight:700; text-align:center; margin:10px 0 2px; min-height:36px; display:flex; align-items:center; justify-content:center;}
  .card .ctype{font-size:10px; text-align:center; color:var(--muted); text-transform:uppercase; letter-spacing:1px; margin-bottom:6px;}
  .card .cdesc{font-size:11px; line-height:1.35; text-align:center; color:var(--text); min-height:96px; display:flex; align-items:center; justify-content:center; padding:4px;}
  .card .rarity{text-align:center; font-size:9px; letter-spacing:1px; margin-top:auto; color:var(--muted); text-transform:uppercase;}
  .card.playable{cursor:pointer;} .card.playable:hover{transform:translateY(-14px) scale(1.04); box-shadow:0 12px 26px rgba(217,178,74,.35); border-color:var(--gold);}
  .card.targetable{cursor:crosshair; box-shadow:0 0 0 3px #ff5555, 0 8px 20px rgba(255,0,0,.4);}
  .card.ethereal{opacity:.65;}
  .card .up{position:absolute; top:2px; right:6px; color:var(--gold2); font-size:11px; font-weight:700;}
  .panel{background:linear-gradient(180deg,var(--panel),var(--bg2)); border:1px solid var(--line); border-radius:12px; padding:18px; margin-bottom:14px; box-shadow:inset 0 0 30px rgba(0,0,0,.4);}
  .stat{display:inline-flex; flex-direction:column; align-items:center; margin-right:22px; min-width:60px;}
  .stat .v{font-size:24px; font-weight:800;} .stat .k{font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:1px;}
  .hpbar{height:14px; background:#2a0f0f; border-radius:8px; overflow:hidden; border:1px solid #4a1a1a; margin:6px 0;}
  .hpbar > div{height:100%; background:linear-gradient(90deg,#7a1414,#c0392b); transition:width .3s;}
  .hpbar.enemy > div{background:linear-gradient(90deg,#5a2a2a,#a33);}
  .blockbadge{display:inline-block; background:radial-gradient(circle at 35% 30%,#7fb4,e0ecff,#5b9dd5); color:#0a1626; font-weight:800; border-radius:6px; padding:1px 8px; font-size:12px; margin-left:6px;}
  .statusbadge{display:inline-block; font-size:11px; padding:2px 8px; border-radius:10px; margin:2px; line-height:1.5;}
  .sb-strength{background:#5a2a1a;} .sb-dexterity{background:#1a5a2a;} .sb-regen{background:#1a4a4a;} .sb-intangible{background:#3a2a5a;}
  .sb-weak{background:#4a3a1a;} .sb-vulnerable{background:#5a1a3a;} .sb-bleed{background:#5a1010;} .sb-burn{background:#5a3010;} .sb-poison{background:#2a5a1a;}
  .enemy-grid{display:flex; gap:20px; justify-content:center; flex-wrap:wrap; margin:14px 0;}
  .enemy{background:linear-gradient(180deg,#2a1414,#160a0a); border:2px solid #5a2a2a; border-radius:14px; padding:14px; min-width:150px; text-align:center; position:relative;}
  .enemy.targetable{border-color:#ff5555; box-shadow:0 0 0 3px #ff5555;}
  .enemy .eicon{font-size:44px; margin:4px 0;}
  .enemy .ename{font-weight:700; font-size:15px;}
  .intent{position:absolute; top:-14px; left:50%; transform:translateX(-50%); background:#1a1a1a; border:1px solid var(--gold); border-radius:8px; padding:3px 9px; font-size:12px; white-space:nowrap; box-shadow:0 2px 8px rgba(0,0,0,.6);}
  .intent .it{color:var(--gold2); font-weight:700;}
  .powers-zone{display:flex; gap:8px; min-height:70px; align-items:center;}
  .powerchip{width:60px; height:70px; border:2px solid var(--purple2); border-radius:8px; background:#2a1a3a; display:flex; flex-direction:column; align-items:center; justify-content:center; font-size:9px; text-align:center; padding:2px;}
  .hand{display:flex; gap:10px; justify-content:center; flex-wrap:wrap; padding:10px 0; min-height:240px; align-items:flex-end;}
  .resources{display:flex; gap:18px; align-items:center; flex-wrap:wrap;}
  .potions{display:flex; gap:8px;}
  .potion{width:46px; height:54px; border-radius:8px; border:2px solid var(--line); background:#2a1a2a; display:flex; align-items:center; justify-content:center; font-size:22px; cursor:pointer; position:relative;}
  .potion:hover{border-color:var(--gold); transform:translateY(-4px);}
  .potion .pnum{position:absolute; bottom:-4px; right:-4px; background:var(--blood); border-radius:6px; font-size:10px; padding:0 4px;}
  .node{width:74px; height:74px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:28px; cursor:pointer; border:3px solid var(--line); background:var(--panel); position:relative; transition:.15s; margin:8px auto;}
  .node:hover{transform:scale(1.08);}
  .node.enemy{border-color:#a33; color:#e77;} .node.elite{border-color:#c84; color:#fb6; box-shadow:0 0 12px rgba(255,160,40,.4);}
  .node.rest{border-color:#3c6; color:#6e9;} .node.shop{border-color:var(--gold); color:var(--gold2);} .node.event{border-color:var(--purple2); color:#c9a;}
  .node.boss{border-color:#f33; color:#f66; box-shadow:0 0 18px rgba(255,0,0,.5); font-weight:800;}
  .node.current{outline:3px solid var(--gold2); outline-offset:2px;}
  .node.done{opacity:.45;}
  .map-wrap{position:relative; min-height:520px; margin-top:10px;}
  .map-row{display:flex; justify-content:center; gap:40px; margin:6px 0;}
  .path-line{width:2px; height:14px; background:var(--line); margin:0 auto;}
  .toast{position:fixed; top:18px; left:50%; transform:translateX(-50%); background:#1a1a1a; border:1px solid var(--gold); padding:12px 22px; border-radius:10px; z-index:99; box-shadow:0 6px 20px rgba(0,0,0,.7); animation:pop .3s;}
  @keyframes pop{from{transform:translate(-50%,-10px); opacity:0;}}
  .center{text-align:center;} .muted{color:var(--muted);} .gold-t{color:var(--gold2);} .red{color:#e66;} .big{font-size:20px;}
  .classcard{width:220px; background:linear-gradient(180deg,var(--panel2),#140e1f); border:2px solid var(--line); border-radius:14px; padding:18px; cursor:pointer; transition:.15s; text-align:center;}
  .classcard:hover{border-color:var(--gold); transform:translateY(-6px); box-shadow:0 10px 26px rgba(0,0,0,.6);}
  .classcard .cicon{font-size:56px;} .classcard h3{color:var(--gold2);}
  .flex{display:flex; gap:14px; flex-wrap:wrap; justify-content:center; align-items:center;}
  .enemy-intent-big{font-size:16px; text-align:center; margin:8px 0; color:var(--gold2);}
  .log{font-size:12px; color:var(--muted); max-height:90px; overflow-y:auto; text-align:center; line-height:1.5;}
  .tag{font-size:10px; padding:1px 6px; border-radius:6px; background:#333; margin:0 2px;}
  .shop-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(168px,1fr)); gap:14px; justify-content:center;}
  .reward-card{transition:.15s;} .reward-card:hover{transform:translateY(-10px);}
  .credits{position:fixed; bottom:10px; width:100%; text-align:center; color:var(--muted); font-size:11px;}
  .banner{text-align:center; font-size:26px; font-weight:800; margin:10px 0;}
  .hp-input{width:70px; padding:6px; font-size:15px; border-radius:6px; border:1px solid var(--line); background:var(--panel); color:var(--text);}
  .section-head{display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;}
</style>
</head>
<body>
<div id="app"></div>
<div class="credits">Shadows of the Abyss — a roguelike deckbuilder</div>
<script>
"use strict";
// ============================================================
//  SHADOWS OF THE ABYSS — game data & engine
// ============================================================

// ---------- Utility ----------
const rand = (a=1,b)=> a===b ? a : Math.floor(Math.random()*(b-a+1))+a;
const pick = arr => arr[Math.floor(Math.random()*arr.length)];
const shuffle = arr => {arr=arr.slice(); for(let i=arr.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1)); [arr[i],arr[j]]=[arr[j],arr[i]];} return arr;};
const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
const $ = id => document.getElementById(id);
let toastTimer;
function toast(msg){ const t=document.createElement('div'); t.className='toast'; t.innerHTML=msg; document.body.appendChild(t); clearTimeout(toastTimer); toastTimer=setTimeout(()=>t.remove(),1600); }

// ---------- Basic cards (all classes) ----------
const BASIC = {
  strike:{name:"Strike",type:"Attack",cost:1,rarity:"Basic",up:"Deal 8 damage.",
    play:g=>{ const c=g.curCard; damageSingle(g, g.target, 6 + (c.upgraded?2:0)); }},
  defend:{name:"Defend",type:"Skill",cost:1,rarity:"Basic",up:"Gain 8 Block.",
    play:g=>{ const c=g.curCard; g.player.gainBlock(5 + (c.upgraded?3:0)); }},
};

// ---------- Blood Reaper unique cards ----------
const BR = {
  sanguine_strike:{name:"Sanguine Strike",type:"Attack",cost:1,rarity:"Common",up:"Deal 10 damage. Heal 3 HP.",
    play:g=>{ const d=8+(g.curCard.upgraded?2:0); damageSingle(g,g.target,d); const heal=2+(g.curCard.upgraded?1:0); g.player.regenAdd(heal); }},
  hemorrhage:{name:"Hemorrhage",type:"Attack",cost:1,rarity:"Common",up:"Deal 6 damage. Apply 4 Bleed.",
    play:g=>{ const d=5+(g.curCard.upgraded?1:0); damageSingle(g,g.target,d); g.target.applyBleed(3+(g.curCard.upgraded?1:0)); }},
  bloodletting:{name:"Bloodletting",type:"Attack",cost:1,rarity:"Uncommon",up:"Lose 2 HP. Deal 15 damage.",
    play:g=>{ const lose=3-(g.curCard.upgraded?1:0); const dmg=12+(g.curCard.upgraded?3:0); g.player.loseHP(lose,false); damageSingle(g,g.target,dmg); }},
  crimson_eruption:{name:"Crimson Eruption",type:"Attack",cost:2,rarity:"Rare",up:"Lose 2 HP. Deal 10 damage to ALL.",
    play:g=>{ const dmg=8+(g.curCard.upgraded?2:0); g.player.loseHP(2,false); damageAll(g,dmg); }},
  crimson_shield:{name:"Crimson Shield",type:"Skill",cost:1,rarity:"Common",up:"Gain 8 Block. Lose 2 HP.",
    play:g=>{ const b=6+(g.curCard.upgraded?2:0); g.player.gainBlock(b); g.player.loseHP(2,false); }},
  blood_ward:{name:"Blood Ward",type:"Skill",cost:1,rarity:"Common",up:"Gain 4 Block. Gain 2 Regeneration.",
    play:g=>{ const b=3+(g.curCard.upgraded?1:0); g.player.gainBlock(b); g.player.regenAdd(1+(g.curCard.upgraded?1:0)); }},
  sacrificial_barrier:{name:"Sacrificial Barrier",type:"Skill",cost:2,rarity:"Uncommon",up:"Gain 13 Block. Lose 5 HP.",
    play:g=>{ const b=10+(g.curCard.upgraded?3:0); g.player.gainBlock(b); g.player.loseHP(5,false); }},
  vital_surge:{name:"Vital Surge",type:"Skill",cost:0,rarity:"Rare",exhaust:true,up:"Gain Block equal to missing HP +3.",
    play:g=>{ const missing=g.player.maxHP-g.player.hp; const gained=missing+(g.curCard.upgraded?3:0); g.player.gainBlock(gained); }},
  life_tap:{name:"Life Tap",type:"Skill",cost:0,rarity:"Common",up:"Draw 3 cards. Lose 3 HP.",
    play:g=>{ g.player.draw(2+(g.curCard.upgraded?1:0)); g.player.loseHP(3,false); }},
  rend_flesh:{name:"Rend Flesh",type:"Skill",cost:1,rarity:"Uncommon",up:"Apply 7 Bleed to ALL. Lose 2 HP.",
    play:g=>{ const bl=5+(g.curCard.upgraded?2:0); g.player.loseHP(2,false); g.enemies.forEach(e=>e.applyBleed(bl)); }},
  soul_siphon:{name:"Soul Siphon",type:"Power",cost:2,rarity:"Uncommon",up:"Cost 1. Gain 1 Energy, lose 1 HP each turn.",
    power:g=>{ /* handled in player turn */ }},
  blood_pact:{name:"Blood Pact",type:"Power",cost:1,rarity:"Rare",up:"gain 2 Strength.",
    power:g=>{}},
};

// ---------- Shadowmancer unique cards ----------
const SM = {
  shadow_bolt:{name:"Shadow Bolt",type:"Attack",cost:1,rarity:"Common",up:"Deal 8 damage. Apply 2 Weak.",
    play:g=>{ const d=6+(g.curCard.upgraded?2:0); damageSingle(g,g.target,d); g.target.applyWeak(1+(g.curCard.upgraded?1:0)); }},
  grasp_of_despair:{name:"Grasp of Despair",type:"Attack",cost:1,rarity:"Common",up:"Deal 5 damage. Apply 2 Vulnerable.",
    play:g=>{ const d=4+(g.curCard.upgraded?1:0); damageSingle(g,g.target,d); g.target.applyVulnerable(1+(g.curCard.upgraded?1:0)); }},
  nightmare_lash:{name:"Nightmare Lash",type:"Attack",cost:2,rarity:"Uncommon",up:"Deal 9 damage. If Weak, deal 9 more.",
    play:g=>{ const c=g.curCard; const base=7+(c.upgraded?2:0); let d=base; if(g.target.hasWeak()) d+= (c.upgraded?9:7); damageSingle(g,g.target,d); }},
  eternal_darkness:{name:"Eternal Darkness",type:"Attack",cost:2,rarity:"Rare",up:"Deal 4 damage to a random enemy 3 times.",
    play:g=>{ const d=g.curCard.upgraded?4:3; for(let i=0;i<3;i++){ const e=pick(g.enemies); damageSingle(g,e,d); } }},
  cloak_of_shadows:{name:"Cloak of Shadows",type:"Skill",cost:1,rarity:"Common",up:"Gain 7 Block. Draw 1 card.",
    play:g=>{ const b=5+(g.curCard.upgraded?2:0); g.player.gainBlock(b); g.player.draw(1); }},
  haunting_mist:{name:"Haunting Mist",type:"Skill",cost:1,rarity:"Common",up:"Gain 9 Block. Apply 1 Weak to ALL.",
    play:g=>{ const b=7+(g.curCard.upgraded?2:0); g.player.gainBlock(b); g.enemies.forEach(e=>e.applyWeak(1)); }},
  phantom_armor:{name:"Phantom Armor",type:"Skill",cost:1,rarity:"Uncommon",up:"Gain 10 Block. Next turn gain 3 Dexterity.",
    play:g=>{ const b=8+(g.curCard.upgraded?2:0); g.player.gainBlock(b); g.player.dexBuff=(g.player.dexBuff||0)+(g.curCard.upgraded?3:2); }},
  whispering_shroud:{name:"Whispering Shroud",type:"Skill",cost:1,rarity:"Rare",up:"Gain 5 Block. Next turn gain 5 Block.",
    play:g=>{ const b=4+(g.curCard.upgraded?1:0); g.player.gainBlock(b); g.player.blockNextTurn += b; }},
  shadowform:{name:"Shadowform",type:"Skill",cost:2,rarity:"Uncommon",exhaust:true,up:"Gain 3 Intangible.",
    play:g=>{ g.player.intangible += 2+(g.curCard.upgraded?1:0); }},
  curse_of_misfortune:{name:"Curse of Misfortune",type:"Skill",cost:1,rarity:"Common",up:"Enemy loses 4 Strength this turn. Apply 2 Vulnerable.",
    play:g=>{ const c=g.curCard; const loss=4+(c.upgraded?1:0); g.target.tmpStrengthLoss=(g.target.tmpStrengthLoss||0)+loss; g.target.applyVulnerable(1+(c.upgraded?1:0)); }},
  dark_ritual:{name:"Dark Ritual",type:"Skill",cost:0,rarity:"Uncommon",up:"Gain 2 Energy. Shuffle a Dazed into discard pile.",
    play:g=>{ g.player.energy += 2; if(g.curCard.upgraded){ g.player.addDazedToDiscard(); } else { g.player.addDazedToDraw(); } }},
  umbral_mastery:{name:"Umbral Mastery",type:"Power",cost:2,rarity:"Rare",up:"Apply 2 Weak each turn.",
    power:g=>{}},
};

// ---------- Hellforged unique cards ----------
const HF = {
  firebolt:{name:"Firebolt",type:"Attack",cost:1,rarity:"Common",up:"Deal 9 damage. Apply 3 Burn.",
    play:g=>{ const d=7+(g.curCard.upgraded?2:0); damageSingle(g,g.target,d); g.target.applyBurn(2+(g.curCard.upgraded?1:0)); }},
  hellfire_blast:{name:"Hellfire Blast",type:"Attack",cost:2,rarity:"Uncommon",up:"Deal 12 damage to ALL.",
    play:g=>{ damageAll(g, 9+(g.curCard.upgraded?3:0)); }},
  searing_strike:{name:"Searing Strike",type:"Attack",cost:1,rarity:"Common",up:"Deal 8 damage. Gain 1 Energy next turn.",
    play:g=>{ const d=6+(g.curCard.upgraded?2:0); damageSingle(g,g.target,d); g.player.energyNextTurn += 1; }},
  immolate:{name:"Immolate",type:"Attack",cost:2,rarity:"Rare",exhaust:true,up:"Deal 16 damage.",
    play:g=>{ damageSingle(g,g.target, 12+(g.curCard.upgraded?4:0)); }},
  obsidian_skin:{name:"Obsidian Skin",type:"Skill",cost:1,rarity:"Common",up:"Gain 10 Block.",
    play:g=>{ g.player.gainBlock(7+(g.curCard.upgraded?3:0)); }},
  flame_ward:{name:"Flame Ward",type:"Skill",cost:1,rarity:"Common",up:"Gain 5 Block. Apply 3 Burn to random enemy.",
    play:g=>{ const b=5+(g.curCard.upgraded?1:0); g.player.gainBlock(b); if(g.enemies.length){ const e=pick(g.enemies); e.applyBurn(g.curCard.upgraded?3:2); } }},
  demonic_resilience:{name:"Demonic Resilience",type:"Skill",cost:1,rarity:"Uncommon",up:"Gain 5 Block. Heal 3 HP.",
    play:g=>{ g.player.gainBlock(4+(g.curCard.upgraded?1:0)); g.player.gainHP(2+(g.curCard.upgraded?1:0)); }},
  brimstone_shield:{name:"Brimstone Shield",type:"Skill",cost:1,rarity:"Rare",up:"Gain 4 Block. Gain 3 Strength this turn.",
    play:g=>{ const b=3+(g.curCard.upgraded?1:0); g.player.gainBlock(b); g.player.strBuff=(g.player.strBuff||0)+(g.curCard.upgraded?3:2); }},
  summon_imp:{name:"Summon Imp",type:"Skill",cost:1,rarity:"Common",exhaust:true,up:"Add Imp Claw+ to hand.",
    play:g=>{ const claw = g.curCard.upgraded ? makeToken("imp_claw_plus") : makeToken("imp_claw"); g.player.hand.push(claw); }},
  infernal_pact:{name:"Infernal Pact",type:"Skill",cost:0,rarity:"Uncommon",up:"Lose 1 HP. Gain 2 Energy. Add Imp Claw+.",
    play:g=>{ const lose=g.curCard.upgraded?1:2; g.player.loseHP(lose,false); g.player.energy += 2; const claw = g.curCard.upgraded ? makeToken("imp_claw_plus") : makeToken("imp_claw"); g.player.hand.push(claw); }},
  hellfire_aura:{name:"Hellfire Aura",type:"Power",cost:2,rarity:"Rare",up:"Deal 5 damage to ALL enemies each turn.",
    power:g=>{}},
  soul_burn:{name:"Soul Burn",type:"Skill",cost:1,rarity:"Uncommon",up:"Apply 10 Burn to enemy. Draw 1 card.",
    play:g=>{ const b=g.curCard.upgraded?10:8; g.target.applyBurn(b); g.player.draw(1); }},
};

// ---------- Colorless cards (shop only) ----------
const COLORLESS = {
  flash_of_insight:{name:"Flash of Insight",type:"Skill",cost:1,rarity:"Common",up:"Draw 3 cards.",
    play:g=>{ g.player.draw(2+(g.curCard.upgraded?1:0)); }},
  iron_will:{name:"Iron Will",type:"Skill",cost:1,rarity:"Common",up:"Gain 11 Block.",
    play:g=>{ g.player.gainBlock(8+(g.curCard.upgraded?3:0)); }},
  quick_slash:{name:"Quick Slash",type:"Attack",cost:1,rarity:"Common",up:"Deal 9 damage.",
    play:g=>{ damageSingle(g,g.target, 7+(g.curCard.upgraded?2:0)); }},
  potent_brew:{name:"Potent Brew",type:"Skill",cost:0,rarity:"Uncommon",exhaust:true,up:"Gain 2 Energy.",
    play:g=>{ g.player.energy += 2+(g.curCard.upgraded?1:0); }},
};

// ---------- Curses / Status ----------
function makeStatus(type){
  const base = {status:true, ethereal:true, exhaust:true, name: type==="dazed"?"Dazed":(type==="wound"?"Wound":"Doubt"), type:"Status", cost:0, rarity:"Curse", upgraded:false, isStatus:true};
  if(type==="dazed"){ return {...base, play:()=>{} }; }
  if(type==="wound"){ return {...base, play:()=>{} }; }
  if(type==="doubt"){ return {...base, ethereal:false, play:()=>{}, doubt:true }; }
  return base;
}
function makeToken(t){
  if(t==="imp_claw"){ return {name:"Imp Claw",type:"Attack",cost:0,rarity:"Status",exhaust:true,upgraded:false,isToken:true, play:g=>damageSingle(g,g.target,4)}; }
  if(t==="imp_claw_plus"){ return {name:"Imp Claw+",type:"Attack",cost:0,rarity:"Status",exhaust:true,upgraded:true,isToken:true, play:g=>damageSingle(g,g.target,6)}; }
}

// ---------- Class definitions ----------
const CLASSES = {
  blood_reaper:{
    name:"Blood Reaper", icon:"🧛", color:"#c0392b",
    desc:"Sacrifice HP for power, life steal, and Bleed.",
    deck:()=>{ const d=[]; for(let i=0;i<5;i++) d.push(makeCard("strike")); for(let i=0;i<4;i++) d.push(makeCard("defend")); d.push(makeCard("sanguine_strike")); return d; },
    pool:BR,
  },
  shadow_mancer:{
    name:"Shadowmancer", icon:"🧙", color:"#9b59b6",
    desc:"Debuffs (Weak, Vulnerable), Intangible, and card manipulation.",
    deck:()=>{ const d=[]; for(let i=0;i<5;i++) d.push(makeCard("strike")); for(let i=0;i<4;i++) d.push(makeCard("defend")); d.push(makeCard("shadow_bolt")); return d; },
    pool:SM,
  },
  hellforged:{
    name:"Hellforged", icon:"👹", color:"#e67e22",
    desc:"Fire damage, Burn, summoning Imps, energy manipulation.",
    deck:()=>{ const d=[]; for(let i=0;i<5;i++) d.push(makeCard("strike")); for(let i=0;i<4;i++) d.push(makeCard("defend")); d.push(makeCard("firebolt")); return d; },
    pool:HF,
  },
};

// ---------- Card factory ----------
const SINGLE_ATTACK = new Set(["strike","sanguine_strike","hemorrhage","bloodletting","shadow_bolt","grasp_of_despair","nightmare_lash","firebolt","searing_strike","immolate","quick_slash","soul_burn"]);
const ALL_TARGET = new Set(["crimson_eruption","hellfire_blast"]);
function makeCard(id){
  let def;
  if(BASIC[id]) def={...BASIC[id]};
  else if(BR[id]) def={...BR[id]};
  else if(SM[id]) def={...SM[id]};
  else if(HF[id]) def={...HF[id]};
  else if(COLORLESS[id]) def={...COLORLESS[id]};
  const c = {id, name:def.name, type:def.type, cost:def.cost, rarity:def.rarity, upgraded:false,
    exhaust:def.exhaust||false, ethereal:def.ethereal||false, status:def.status||false, isStatus:!!def.isStatus, isToken:!!def.isToken,
    doubt:!!def.doubt,
    desc:def.up, play:def.play, power:def.power};
  if(SINGLE_ATTACK.has(id)) c.targetType="enemy";
  else if(ALL_TARGET.has(id)) c.targetType="all";
  return c;
}

// ---------- Relics ----------
const RELICS = {
  bloodstone:{name:"Bloodstone",rarity:"Common",desc:"Whenever you lose HP from a card, heal 1 HP.",
    onCardHPLoss:(g,n)=>{ g.player.gainHP(1); }},
  demon_horns:{name:"Demon Horns",rarity:"Class",desc:"Start combat with a random Imp Claw in hand (once per combat).",
    onCombatStart:g=>{ g.player.hand.push(makeToken("imp_claw")); }},
  shadow_cloak:{name:"Shadow Cloak",rarity:"Rare",desc:"Start combat with 1 Intangible.",
    onCombatStart:g=>{ g.player.intangible += 1; }},
  cursed_amulet:{name:"Cursed Amulet",rarity:"Common",desc:"Start combat applying 1 Weak to ALL enemies.",
    onCombatStart:g=>{ g.enemies.forEach(e=>e.applyWeak(1)); }},
  tome_of_dark_rituals:{name:"Tome of Dark Rituals",rarity:"Uncommon",desc:"Whenever you play a Power card, draw 1 card.",
    onPlayPower:g=>{ g.player.draw(1); }},
  bleeding_edge:{name:"Bleeding Edge",rarity:"Common",desc:"Start of your turn, apply 1 Bleed to a random enemy.",
    onPlayerTurnStart:g=>{ if(g.enemies.length) pick(g.enemies).applyBleed(1); }},
  soul_ember:{name:"Soul Ember",rarity:"Rare",desc:"Whenever you apply Burn to an enemy, deal 2 additional damage.",
    onApplyBurn:(g,amt)=>amt+2},
  eternal_hourglass:{name:"Eternal Hourglass",rarity:"Uncommon",desc:"Start of turn, if no cards in hand, gain 1 Energy.",
    onPlayerTurnStart:g=>{ if(g.player.hand.length===0) g.player.energy += 1; }},
  sacrificial_dagger:{name:"Sacrificial Dagger",rarity:"Class",desc:"Your first Attack that costs HP each combat costs 1 less HP.",
    onCardHPLoss:(g,n)=>{ if(!g.player._daggerUsed){ g.player._daggerUsed=true; return n-1; } return n; }},
  vial_of_shadows:{name:"Vial of Shadows",rarity:"Rare",desc:"Obtained: remove a card from your deck (one-time).",
    onObtain:g=>{ askRemoveFromDeck("Vial of Shadows: remove a card from your deck."); }},
  ashen_armor:{name:"Ashen Armor",rarity:"Common",desc:"At the end of combat, heal 6 HP.",
    onCombatEnd:g=>{ g.player.gainHP(6); }},
  greedy_imp:{name:"Greedy Imp",rarity:"Common",desc:"Enemies drop 10 extra Gold.",
    goldBonus:10},
};

// ---------- Potions ----------
const POTIONS = {
  blood_vial:{name:"Blood Vial",icon:"🩸",effect:g=>{ g.player.gainHP(15); }},
  firebomb:{name:"Firebomb",icon:"💥",effect:g=>{ damageAll(g,10); }},
  dex_tonic:{name:"Dex Tonic",icon:"⚗️",effect:g=>{ g.player.dexBuff=(g.player.dexBuff||0)+3; }},
  energy_elixir:{name:"Energy Elixir",icon:"🔋",effect:g=>{ g.player.energy += 2; }},
  cursed_brew:{name:"Cursed Brew",icon:"🫗",effect:g=>{ g.enemies.forEach(e=>e.applyVulnerable(3)); }},
  bleeding_flask:{name:"Bleeding Flask",icon:"🩸",effect:g=>{ if(g.enemies.length) pick(g.enemies).applyBleed(6); }},
  imp_in_a_bottle:{name:"Imp-in-a-Bottle",icon:"🧪",effect:g=>{ g.player.hand.push(makeToken("imp_claw_plus")); }},
  smoke_bomb:{name:"Smoke Bomb",icon:"💨",effect:g=>{ endEscapeCombat(); }},
};

// ---------- Enemy bestiary ----------
// Intent types handled by combat engine:
//  attack: value = damage
//  multi: value = [d1,d2,...] each reduced by block
//  attack2: value = [damage, debuffCount] applies debuff to player (type depends on name)
//  defend: value = block
//  shieldbash: value = [damage, block]
//  shieldbash2: value = [block, heal]
//  drain: value = [damage, heal]
//  buff: value varies (string or number)
//  debuff: value varies
//  summon: value = enemy id to summon
function makeEnemy(id, name, hp, icon, pattern){
  return {
    id, name, maxHP:hp, hp, block:0, icon, pattern,
    str:0, dex:0, intangible:0, tmpStrengthLoss:0,
    _intentTurn:0,
    _weak:0,_weakTimer:0, _vuln:0,_vulnTimer:0, _bleed:0, _burn:0, _regen:0,
    hasWeak(){return this._weak>0;}, applyWeak(n){this._weak=(this._weak||0)+n;},
    hasVulnerable(){return this._vuln>0;}, applyVulnerable(n){this._vuln=(this._vuln||0)+n;},
    hasBleed(){return this._bleed>0;}, applyBleed(n){this._bleed=(this._bleed||0)+n;},
    hasBurn(){return this._burn>0;}, applyBurn(n){this._burn=(this._burn||0)+n;},
    hasRegen(){return this._regen>0;}, applyRegen(n){this._regen=(this._regen||0)+n;},
    gainBlock(n){this.block+=n;},
    intent(){ return this.pattern[this._intentTurn % this.pattern.length]; },
    startNewCombat(){ this.hp=this.maxHP; this.block=0; this.str=0; this.dex=0; this.intangible=0; this._intentTurn=0; this._weak=0;this._weakTimer=0;this._vuln=0;this._vulnTimer=0;this._bleed=0;this._burn=0;this._regen=0; this.tmpStrengthLoss=0; }
  };
}

// Intent helpers
const itAttack=(n)=>({type:"attack",value:n, text:"Attack "+n});
const itDefend=(n)=>({type:"defend",value:n, text:"Defend "+n});
const itBuff=(t,v)=>({type:"buff",value:v, text:t});
const itDebuff=(t,v)=>({type:"debuff",value:v, text:t});
const itMulti=(arr)=>({type:"multi",value:arr, text:arr.join("×")+" Attack"});
const itAttack2=(arr)=>({type:"attack2",value:arr, text:"Attack "+arr[0]+", Apply "+arr[1]});
const itSummon=(t)=>({type:"summon",value:t||null, text:"Summon"});
const itHeal=(n)=>({type:"buff",value:"heal "+n, text:"Heal "+n});

// ---- Act 1 ----
const ENEMY_DEF = {
  skeleton:{name:"Skeleton Warrior",hp:22,icon:"💀",pattern:[itAttack(6),itAttack(6),itDefend(8)]},
  zombie:{name:"Zombie Brute",hp:28,icon:"🧟",pattern:[itAttack(8),itDebuff("Apply 2 Weak",2),itAttack(8)]},
  cultist:{name:"Dark Cultist",hp:20,icon:"🧑‍🦰",pattern:[itBuff("Gain 3 Strength",3),itAttack(5),itAttack(5)]},
  ratswarm:{name:"Rat Swarm",hp:15,icon:"🐀",pattern:[itMulti([3,3]),itMulti([3,3])]},
  wraith:{name:"Wraith",hp:30,icon:"👻",pattern:[itAttack2([7,1]),itAttack(7),itDefend(10)]},
  beast:{name:"Corrupted Beast",hp:38,icon:"🐗",pattern:[itAttack(12),itBuff("Gain 5 Block & Vulnerable",1),itAttack(12)]},
  thorned:{name:"Thorned Horror",hp:25,icon:"🌿",pattern:[itMulti([4,4,4]),itDebuff("Apply 3 Bleed",3),itAttack(8)]},
  witchdoctor:{name:"Witch Doctor",hp:22,icon:"🧙‍♀️",pattern:[itBuff("Gain 5 Block & 2 Regen",1),itAttack2([6,2]),itAttack(6)]},
  hellhound:{name:"Hellhound",hp:26,icon:"🐕",pattern:[itAttack2([8,2]),itAttack(8),itDefend(9)]},
  shadowdemon:{name:"Shadow Demon",hp:30,icon:"😈",pattern:[itAttack2([7,1]),itAttack(7),itBuff("Gain Intangible",1)]},
  infernalimp:{name:"Infernal Imp",hp:18,icon:"🦇",pattern:[itAttack(5),itAttack(5),itSummon("infernalimp")]},
  abyssalcultist:{name:"Abyssal Cultist",hp:24,icon:"🕯️",pattern:[itBuff("Gain 2 Strength to all",2),itAttack(7),itAttack(7)]},
  tombguardian:{name:"Tomb Guardian",hp:42,icon:"🗿",elite:true,pattern:[{type:"shieldbash",value:[6,8],text:"Attack 6, Shield 8"},itMulti([4,4,4]),itAttack(12)]},
  bloodwitch:{name:"Blood Witch",hp:36,icon:"🩰",elite:true,pattern:[{type:"drain",value:[5,4],text:"Drain 5 (heal 4)"},itDebuff("Vulnerable & Weak",1),{type:"drain",value:[7,4],text:"Blood Ritual 7 (heal 4)"}]},
  treant:{name:"Treant Protector",hp:50,icon:"🌳",elite:true,pattern:[{type:"shieldbash",value:[9,6],text:"Attack 9, Shield 6"},itDebuff("Apply 2 Vulnerable",2),itMulti([10,10])]},
  plagueshaman:{name:"Plague Shaman",hp:42,icon:"☠️",elite:true,pattern:[itDebuff("Apply 4 Poison",4),itSummon("plagerat"),itAttack(8)]},
  flamespawn:{name:"Flamespawn",hp:48,icon:"🔥",elite:true,pattern:[itAttack2([9,3]),itBuff("Gain 2 Strength",2),itMulti([8,8])]},
  dreadknight:{name:"Dread Knight",hp:55,icon:"⚔️",elite:true,pattern:[itAttack(12),itDebuff("Apply 3 Bleed",3),{type:"shieldbash2",value:[12,4],text:"Defend 12, Heal 4"}]},
  plagerat:{name:"Plague Rat",hp:18,icon:"🐀",pattern:[itAttack(5)]},
  shadow_thief:{name:"Shadow Thief",hp:40,icon:"🗡️",elite:true,pattern:[itAttack(8),itDebuff("Apply 1 Weak",1),itAttack(10)]},
};

function makeEnemyInstance(id){
  const d=ENEMY_DEF[id]; if(!d) return null;
  return makeEnemy(id, d.name, d.hp, d.icon, d.pattern);
}

// Boss definitions (phase-based)
const BOSSES = {
  lichking:{name:"Lich King",hp:65,icon:"🧟‍♂️",boss:true,act:1},
  eldritchtreant:{name:"Eldritch Treant",hp:75,icon:"🌲",boss:true,act:2},
  archdemon:{name:"Archdemon",hp:85,icon:"👿",boss:true,act:3},
};

// Boss intent patterns (phase-based). Returns intent for a given phase & turn.
function bossIntent(name, isPhase2, turn){
  if(name==="lichking"){
    if(isPhase2){ const p=[itAttack(10),itAttack(10),itDefend(15)]; return p[turn%3]; }
    return [itSummon("skeleton"),itAttack(8),itDefend(12)][turn%3];
  }
  if(name==="eldritchtreant"){
    if(isPhase2){ const p=[{type:"attack2",value:[14,1],text:"Attack 14, Apply 1 Vulnerable"},{type:"shieldbash",value:[20,5],text:"Defend 20, Heal 5"},{type:"attack2",value:[14,1],text:"Attack 14, Apply 1 Vulnerable"}]; return p[turn%3]; }
    return [itAttack2([10,2]),itBuff("Defend 15 & Regen 2",1),itMulti([8,8])][turn%3];
  }
  if(name==="archdemon"){
    if(isPhase2){ const p=[itAttack(16),itMulti([7,7,7]),itAttack(16)]; return p[turn%3]; }
    return [itSummon("hellhound"),itAttack2([12,2]),{type:"shieldbash2",value:[15,3],text:"Defend 15, Gain 3 Strength"}][turn%3];
  }
  return itAttack(0);
}

function makeBossInstance(id){
  const d=BOSSES[id];
  const e=makeEnemy(id, d.name, d.hp, d.icon, []);
  e.isBoss=true; e.act=d.act; e._intentTurn=0;
  e.getBossIntent=()=>bossIntent(id, e.phase2===true, e._intentTurn);
  return e;
}

// ---------- Map definitions (per act) ----------
// Each floor: {type, options:[{name, enemies:[...], icon}]}
// type: enemy | elite | rest | shop | event | boss
// For elite: {type:"elite", name, icon, group:[...], alt:[...]}
const ACTS = [
  {
    name:"Act 1 — The Crypts",
    floors:[
      {type:"enemy", name:"Skeleton", icon:"💀", group:["skeleton"]},
      {type:"enemy", name:"Zombie Brute", icon:"🧟", group:["zombie"]},
      {type:"enemy", name:"Dark Cultist", icon:"🧑‍🦰", group:["cultist"]},
      {type:"event", name:"The Forgotten Altar", icon:"🕯️"},
      {type:"shop", name:"Shop", icon:"🏪"},
      {type:"enemy", name:"Rat Swarm", icon:"🐀", group:["ratswarm"]},
      {type:"rest", name:"Rest", icon:"🔥"},
      {type:"enemy", name:"Skeleton Group", icon:"💀", group:["skeleton","skeleton"]},
      {type:"enemy", name:"Zombie Brute", icon:"🧟", group:["zombie"]},
      {type:"elite", name:"Tomb Guardian", icon:"🗿", group:["tombguardian"], alt:["skeleton","ratswarm"]},
      {type:"event", name:"Shadowy Peddler", icon:"🎭"},
      {type:"enemy", name:"Cultist & Rat", icon:"🧑‍🦰", group:["cultist","ratswarm"]},
      {type:"elite", name:"Blood Witch", icon:"🩰", group:["bloodwitch"], alt:["zombie","skeleton"]},
      {type:"enemy", name:"Cultist Group", icon:"🧑‍🦰", group:["cultist","cultist"]},
      {type:"rest", name:"Rest", icon:"🔥"},
      {type:"boss", name:"Lich King", icon:"🧟‍♂️", group:["lichking"]},
    ],
  },
  {
    name:"Act 2 — Blighted Woods",
    floors:[
      {type:"enemy", name:"Wraith", icon:"👻", group:["wraith"]},
      {type:"enemy", name:"Corrupted Beast", icon:"🐗", group:["beast"]},
      {type:"enemy", name:"Thorned Horror", icon:"🌿", group:["thorned"]},
      {type:"event", name:"Cursed Spring", icon:"⛲"},
      {type:"shop", name:"Shop", icon:"🏪"},
      {type:"enemy", name:"Witch Doctor", icon:"🧙‍♀️", group:["witchdoctor"]},
      {type:"rest", name:"Rest", icon:"🔥"},
      {type:"enemy", name:"Corrupted Beast", icon:"🐗", group:["beast"]},
      {type:"enemy", name:"Wraith", icon:"👻", group:["wraith"]},
      {type:"elite", name:"Treant Protector", icon:"🌳", group:["treant"], alt:["wraith","beast"]},
      {type:"event", name:"Restless Dead", icon:"👁️"},
      {type:"enemy", name:"Beast & Wraith", icon:"🐗", group:["beast","wraith"]},
      {type:"elite", name:"Plague Shaman", icon:"☠️", group:["plagueshaman"], alt:["beast","wraith"]},
      {type:"enemy", name:"Witch Doctor Group", icon:"🧙‍♀️", group:["witchdoctor","witchdoctor"]},
      {type:"rest", name:"Rest", icon:"🔥"},
      {type:"boss", name:"Eldritch Treant", icon:"🌲", group:["eldritchtreant"]},
    ],
  },
  {
    name:"Act 3 — Abyssal Spire",
    floors:[
      {type:"enemy", name:"Hellhound", icon:"🐕", group:["hellhound"]},
      {type:"enemy", name:"Shadow Demon", icon:"😈", group:["shadowdemon"]},
      {type:"enemy", name:"Infernal Imp", icon:"🦇", group:["infernalimp"]},
      {type:"event", name:"Impish Gambler", icon:"🃏"},
      {type:"shop", name:"Shop", icon:"🏪"},
      {type:"enemy", name:"Abyssal Cultist", icon:"🕯️", group:["abyssalcultist"]},
      {type:"rest", name:"Rest", icon:"🔥"},
      {type:"enemy", name:"Hellhound & Imp", icon:"🐕", group:["hellhound","infernalimp"]},
      {type:"enemy", name:"Shadow Demon", icon:"😈", group:["shadowdemon"]},
      {type:"elite", name:"Flamespawn", icon:"🔥", group:["flamespawn"], alt:["hellhound","shadowdemon"]},
      {type:"event", name:"Ancient Library", icon:"📚"},
      {type:"enemy", name:"Cultist & Imp", icon:"🕯️", group:["abyssalcultist","infernalimp"]},
      {type:"elite", name:"Dread Knight", icon:"⚔️", group:["dreadknight"], alt:["hellhound","shadowdemon"]},
      {type:"enemy", name:"Abyssal Cultist Group", icon:"🕯️", group:["abyssalcultist","abyssalcultist"]},
      {type:"rest", name:"Rest", icon:"🔥"},
      {type:"boss", name:"Archdemon", icon:"👿", group:["archdemon"]},
    ],
  },
];

// ---------- Events ----------
const EVENTS = [
  {
    id:"altar", name:"The Forgotten Altar", icon:"🕯️",
    text:"A blood-stained altar hums with forbidden power.",
    choices:[
      {label:"Offer 15 HP → Rare relic", run:g=>{ g.player.loseHP(15,false); const r=makeRareRelic(); grantRelic(r); g.finishEvent(); }},
      {label:"Pray → Heal 10, gain Doubt", run:g=>{ g.player.gainHP(10); g.player.deck.push(makeStatus("doubt")); g.finishEvent(); }},
      {label:"Leave", run:g=>{ g.finishEvent(); }},
    ],
  },
  {
    id:"peddler", name:"Shadowy Peddler", icon:"🎭",
    text:"A hooded figure offers you a trade.",
    choices:[
      {label:"Trade a card → 100 Gold", run:g=>{ askRemoveFromDeck("Shadowy Peddler: trade a card for 100 gold."); g.player.gold += 100; }},
      {label:"Buy wares → -50 Gold, Potion + Relic", run:g=>{ if(g.player.gold<50){ toast("Not enough gold."); return; } g.player.gold-=50; grantPotion(randPotion()); grantRelic(makeCommonRelic()); g.finishEvent(); }},
      {label:"Attack → Combat (+75 Gold on win)", run:g=>{ startCombat(["shadow_thief"], "Shadow Thief", true, ()=>{g.player.gold+=75;}); }},
    ],
  },
  {
    id:"spring", name:"Cursed Spring", icon:"⛲",
    text:"A spring bubbles with dark, tempting water.",
    choices:[
      {label:"Drink → -10 HP, remove all Curses", run:g=>{ g.player.loseHP(10,false); g.player.deck = g.player.deck.filter(c=>!c.status||c.isStatus); g.finishEvent(); }},
      {label:"Bottle it → Cursed Brew", run:g=>{ grantPotion("cursed_brew"); g.finishEvent(); }},
      {label:"Leave", run:g=>{ g.finishEvent(); }},
    ],
  },
  {
    id:"restlessdead", name:"Restless Dead", icon:"👁️",
    text:"Ghosts offer you a bargain in raspy whispers.",
    choices:[
      {label:"Accept → -20 HP, upgrade 2 cards", run:g=>{ g.player.loseHP(20,false); upgradeRandomCards(g.player.deck,2); g.finishEvent(); }},
      {label:"Refuse", run:g=>{ g.finishEvent(); }},
    ],
  },
  {
    id:"gambler", name:"Impish Gambler", icon:"🃏",
    text:"A grinning imp challenges you to a game of chance.",
    choices:[
      {label:"Bet 30 Gold → 50% double or lose", run:g=>{ if(g.player.gold<30){ toast("Not enough gold."); return; } g.player.gold-=30; if(Math.random()<0.5){ g.player.gold+=60; toast("🎉 You won 60 gold!"); } else toast("💥 You lost 30 gold."); g.finishEvent(); }},
      {label:"Wager a card → remove random card", run:g=>{ if(g.player.deck.length===0){ toast("No cards to wager."); return; } const c=pick(g.player.deck.slice()); g.player.deck = g.player.deck.filter(x=>x!==c); if(c.status){ g.player.gold+=100; toast("It was a Curse! +100 gold!"); } else toast("You lost "+c.name+"."); g.finishEvent(); }},
      {label:"Leave", run:g=>{ g.finishEvent(); }},
    ],
  },
  {
    id:"library", name:"Ancient Library", icon:"📚",
    text:"Dusty tomes contain forgotten knowledge.",
    choices:[
      {label:"Study → +1 Energy (next combat), -5 HP", run:g=>{ g.player.loseHP(5,false); g.player.tempEnergyNextCombat = (g.player.tempEnergyNextCombat||0)+1; g.finishEvent(); }},
      {label:"Read forbidden page → Power + Doubt", run:g=>{ const pool=g.player.classDef.pool; const keys=Object.keys(pool); const id=pick(keys); const card=makeCard(id); card.cost=0; g.player.deck.push(card); g.player.deck.push(makeStatus("doubt")); g.finishEvent(); }},
      {label:"Leave", run:g=>{ g.finishEvent(); }},
    ],
  },
];

// ============================================================
//  GAME STATE & COMBAT ENGINE
// ============================================================
const state = {
  mode:"classselect",
  player:null,
  enemies:[],
  actIndex:0,
  floorIndex:1, // index into ACTS[actIndex].floors (floor 2 = index 0)
  gold:99,
  combatLabel:"",
  combatSpecial:false,
  goldBonus:0,
  curCard:null,
  targeting:null,
  rewardQueue:null,
  won:false,
  _pendingCb:null,
};

// ---- Player object ----
function newPlayer(classId){
  const def = CLASSES[classId];
  const p = {
    classId, classDef:def,
    deck: def.deck(),
    draw:[], discard:[], hand:[], exhaust:[],
    maxHP:70, hp:70, block:0, energy:0,
    dexBuff:0, strBuff:0, regen:0, intangible:0,
    strPower:0,
    // debuffs (player)
    _weak:0,_weakTimer:0, _vuln:0,_vulnTimer:0, _bleed:0, _burn:0, _poison:0,
    energyNextTurn:0,
    blockNextTurn:0,
    tempEnergyNextCombat:0,
    potions:[],
    relics:[],
    _daggerUsed:false,
    _soulSiphon:false,
  };
  // Player status effects
  p.applyWeak=n=>{ p._weak=(p._weak||0)+n; };
  p.applyVulnerable=n=>{ p._vuln=(p._vuln||0)+n; };
  p.applyBleed=n=>{ p._bleed=(p._bleed||0)+n; };
  p.applyBurn=n=>{ p._burn=(p._burn||0)+n; };
  p.applyPoison=n=>{ p._poison=(p._poison||0)+n; };
  p.gainAdd=n=>{ p.dexBuff=(p.dexBuff||0)+n; };
  p.gainBlock=n=>{ p.block += n + (p.dexBuff||0); };
  p.regenAdd=n=>{ p.regen=(p.regen||0)+n; };
  return p;
}

function deckNoExhaust(p){ return p.deck.filter(c=>!c.exhaust && !c.isStatus); }

// ---- Deck shuffling ----
function buildDrawPile(p){
  const cards = deckNoExhaust(p).map(c=>({...c}));
  // remove status/doubt from draw pile (they shouldn't be there normally)
  p.draw = shuffle(cards);
  p.discard = []; p.exhaust = []; p.hand = [];
}
function drawCards(p, n){
  for(let i=0;i<n;i++){
    if(p.draw.length===0){
      if(p.discard.length===0) break;
      p.draw = shuffle(p.discard);
      p.discard = [];
    }
    if(p.draw.length>0) p.hand.push(p.draw.pop());
  }
}
function addDazedToDraw(){ state.player.draw.push(makeStatus("dazed")); }
function addDazedToDiscard(){ state.player.discard.push(makeStatus("dazed")); }

// ---- HP ----
function gainHP(p, n){ p.hp = Math.min(p.maxHP, p.hp+n); }
function loseHP(p, amount, fromCard){
  let amt = amount;
  if(p.intangible>0) amt = 1; // Intangible reduces ALL HP loss to 1
  if(fromCard){
    // Sacrificial Dagger
    if(p.relics.includes("sacrificial_dagger") && !p._daggerUsed){ amt = Math.max(0, amt-1); p._daggerUsed = true; }
    amt = Math.max(0, Math.floor(amt));
    p.hp = Math.max(0, p.hp - amt);
    // Bloodstone
    if(p.relics.includes("bloodstone")) gainHP(p,1);
    // Blood Pact
    if(p.relics.includes("blood_pact")) p.strPower += (RELICS.blood_pact.upgraded?2:1);
  }
}

// ---- Damage ----
function damageSingle(g, enemy, baseDmg){
  if(!enemy || enemy.hp<=0) return;
  const p = g.player;
  let dmg = baseDmg + p.strBuff + p.strPower;
  if(p._weak>0) dmg = Math.floor(dmg*0.75);
  if(enemy._vuln>0) dmg = Math.floor(dmg*1.5);
  if(enemy.block>0) dmg = Math.max(0, dmg - enemy.block);
  enemy.hp = Math.max(0, enemy.hp - dmg);
}
function damageAll(g, baseDmg){
  g.enemies.forEach(e=>{ if(e.hp>0) damageSingle(g,e,baseDmg); });
}
function applyBurn(g, enemy, n){
  enemy.applyBurn(n);
  if(g.player.relics.includes("soul_ember")) enemy.hp = Math.max(0, enemy.hp - 2);
}
function applyBleedOnApply(g, enemy, n){ enemy.applyBleed(n); }

// ---- Targetable helper ----
function validTargets(g, type){
  if(type==="enemy") return g.enemies.filter(e=>e.hp>0);
  if(type==="all") return g.enemies.filter(e=>e.hp>0);
  return [];
}

// ============================================================
//  COMBAT FLOW
// ============================================================
function canPlayCard(c){
  if(c.status || c.isStatus){
    if(c.doubt) return true; // doubt can be played? It's unplayable per spec but stays. Actually "Unplayable". Keep unplayable.
    return false;
  }
  if(c.cost > state.player.energy) return false;
  if(c.cost>0) return true;
  return true; // 0-cost cards always playable (except status)
}

function startCombat(enemyIds, label, special, cb){
  state.mode = "combat";
  state.enemies = enemyIds.map(id=> makeEnemyInstance(id));
  if(special && enemyIds[0].indexOf!==-1){}
  state.combatLabel = label;
  state.combatSpecial = !!special;
  state._pendingCb = cb || null;
  state.goldBonus = state.player.relics.includes("greedy_imp")?10:0;
  const p = state.player;
  p.hp = Math.min(p.maxHP, p.hp); // ensure at max
  p.block=0; p.energy=0; p.dexBuff=0; p.strBuff=0; p.regen=0; p.intangible=0;
  p._weak=0;p._weakTimer=0;p._vuln=0;p._vulnTimer=0;p._bleed=0;p._burn=0;p._poison=0;
  p._daggerUsed=false; p._soulSiphon=false;
  p.strPower=0; p.energyNextTurn=0; p.blockNextTurn=0;
  p._umbralMastery=0; p._hellfireAura=false; p._hellfireAuraUpgraded=false;
  buildDrawPile(p);
  if(p.tempEnergyNextCombat>0){ p.energy = p.tempEnergyNextCombat; p.tempEnergyNextCombat=0; }
  // Relics on combat start
  p.relics.forEach(rid=>{ const r=RELICS[rid]; if(r.onCombatStart) r.onCombatStart(state); });
  // Start first player turn (draw 5, energy 3, status effects)
  playerTurnStart();
  // Soul Siphon power check handled via played power cards
  drawCards(p, 0);
  state.curCard=null; state.targeting=null;
  render();
}

function endEscapeCombat(){
  state.mode="map";
  toast("💨 You escaped with no rewards.");
  render();
}

function endPlayerTurn(){
  const p = state.player;
  p.energy = p.energyNextTurn + 3;
  p.energyNextTurn = 0;
  // discard hand
  p.discard.push(...p.hand);
  p.hand = [];
  p.block = 0;
  p.blockNextTurn = 0;
  state.curCard=null; state.targeting=null;
  render();
  setTimeout(enemyTurn, 500);
}

// ---- Player turn start ----
function playerTurnStart(){
  const p = state.player;
  p.block = 0;
  p.energy = p.energyNextTurn + 3;
  p.energyNextTurn = 0;
  // Block from next turn
  if(p.blockNextTurn>0){ p.block += p.blockNextTurn; p.blockNextTurn=0; }
  drawCards(p,5);
  // Relic: Bleeding Edge
  p.relics.forEach(rid=>{ const r=RELICS[rid]; if(r.onPlayerTurnStart) r.onPlayerTurnStart(state); });
  // Powers: Soul Siphon
  if(p._soulSiphon){ p.energy += 1; loseHP(p,1,true); }
  // Powers: Hellfire Aura
  if(p._hellfireAura){ const d = p._hellfireAuraUpgraded ? 5 : 3; state.enemies.forEach(e=>{ if(e.hp>0) damageSingle(state,e,d); }); }
  // Status effects at start of player's turn: reduce durations
  p._weakTimer = Math.max(0,(p._weakTimer||0)-1); if(p._weakTimer===0) p._weak=0;
  p._vulnTimer = Math.max(0,(p._vulnTimer||0)-1); if(p._vulnTimer===0) p._vuln=0;
  // Intangible decrements
  if(p.intangible>0){ p.intangible = Math.max(0, p.intangible-1); }
  // Regen: heal X then reduce by 1
  if(p.regen>0){ gainHP(p, p.regen); p.regen = Math.max(0, p.regen-1); }
  // Bleed: lose HP equal stacks, then halve
  if(p._bleed>0){ loseHP(p, p._bleed, true); p._bleed = Math.floor(p._bleed/2); if(p._bleed===0) p._bleed=0; }
  // Burn: handled at END of turn. Poison: start of turn.
  if(p._poison>0){ loseHP(p, p._poison, true); p._poison = Math.max(0, p._poison-1); }
  p.dexBuff = Math.max(0,(p.dexBuff||0)-1);
  p.strBuff = Math.max(0,(p.strBuff||0)-1);
}

// ---- Play a card ----
function playCard(card, target){
  const p = state.player;
  if(state.curCard !== card) return;
  if(!canPlayCard(card)) return;
  // Validate target
  if(card.targetType==="enemy" && (!target || target.hp<=0 || state.enemies.indexOf(target)===-1)){
    toast("Select a valid enemy target."); return;
  }
  if(card.targetType==="all"){
    // no target needed
  }
  // Pay cost
  p.energy -= card.cost;
  // Set current target
  state.target = target;
  // Execute
  if(card.type==="Power"){
    applyPower(card);
    if(p.relics.includes("tome_of_dark_rituals")) p.draw(1);
  } else if(card.play){
    card.play(state);
  }
  // Exhaust / discard
  if(card.exhaust){ p.exhaust.push(card); } else { p.discard.push(card); }
  // remove from hand
  p.hand = p.hand.filter(c=>c!==card);
  state.curCard=null; state.targeting=null;
  if(state.player.hp<=0){ checkPlayerDeath(); return; }
  render();
}

function applyPower(card){
  const p = state.player;
  if(card.id==="soul_siphon"){ p._soulSiphon = true; }
  if(card.id==="blood_pact"){ p.strPower += (card.upgraded?2:1); }
  if(card.id==="umbral_mastery"){ p._umbralMastery = card.upgraded ? 2 : 1; }
  if(card.id==="hellfire_aura"){ p._hellfireAura = true; p._hellfireAuraUpgraded = card.upgraded; }
}

// ---- End turn (player) ----
function confirmEndTurn(){
  endPlayerTurn();
}

// ---- Enemy turn ----
function enemyTurn(){
  const p = state.player;
  // Burn at end of player's turn (before enemies act)
  if(p._burn>0){ loseHP(p, p._burn, true); p._burn = Math.floor(p._burn/2); if(p._burn===0) p._burn=0; }
  render();
  setTimeout(()=>{
    state.enemies.forEach(e=>{ if(e.hp>0) e._intentTurn++; });
    // Act enemies left to right
    let idx=0;
    function actNext(){
      if(state.mode!=="combat"){ render(); return; }
      const e = state.enemies[idx];
      if(e && e.hp>0){
        if(e.isBoss) bossPhaseStrength(e);
        resolveEnemyIntent(e);
        render();
        idx++;
        setTimeout(actNext, 600);
      } else { idx++; actNext(); }
    }
    actNext();
  }, 300);
}

// Resolve the enemy's CURRENT intent (pattern[_intentTurn]), then advance.
function resolveEnemyIntent(e){
  const p = state.player;
  let intent;
  if(e.isBoss) intent = e.getBossIntent();
  else intent = e.intent();
  const v = intent.value;
  switch(intent.type){
    case "attack": {
      let d = v + (e.str||0);
      if(p._vuln>0) d = Math.floor(d*1.5);
      if(p._weak>0) d = Math.floor(d*0.75);
      if(p.block>0) d = Math.max(0, d - p.block);
      p.hp = Math.max(0, p.hp - d);
      break;
    }
    case "multi": {
      v.forEach(dmg=>{
        let d = dmg + (e.str||0);
        if(p._vuln>0) d = Math.floor(d*1.5);
        if(p._weak>0) d = Math.floor(d*0.75);
        if(p.block>0) d = Math.max(0, d - p.block);
        p.hp = Math.max(0, p.hp - d);
      });
      break;
    }
    case "attack2": {
      let d = v[0] + (e.str||0);
      if(p._vuln>0) d = Math.floor(d*1.5);
      if(p._weak>0) d = Math.floor(d*0.75);
      if(p.block>0) d = Math.max(0, d - p.block);
      p.hp = Math.max(0, p.hp - d);
      if(intent.text.includes("Weak")) p.applyWeak(v[1]);
      else if(intent.text.includes("Burn")) p.applyBurn(v[1]);
      else if(intent.text.includes("Poison")) p.applyPoison(v[1]);
      break;
    }
    case "defend": e.gainBlock(v); break;
    case "shieldbash": {
      let d = v[0] + (e.str||0);
      if(p._vuln>0) d = Math.floor(d*1.5);
      if(p._weak>0) d = Math.floor(d*0.75);
      if(p.block>0) d = Math.max(0, d - p.block);
      p.hp = Math.max(0, p.hp - d);
      e.gainBlock(v[1]);
      break;
    }
    case "shieldbash2": e.gainBlock(v[0]); if(v[1]) gainHP(e, v[1]); break;
    case "drain": {
      let d = v[0] + (e.str||0);
      if(p._vuln>0) d = Math.floor(d*1.5);
      if(p._weak>0) d = Math.floor(d*0.75);
      if(p.block>0) d = Math.max(0, d - p.block);
      p.hp = Math.max(0, p.hp - d);
      gainHP(e, v[1]);
      break;
    }
    case "debuff":
      applyEnemyDebuff(e, intent.text);
      break;
    case "buff":
      applyEnemyBuff(e, intent);
      break;
    case "summon":
      summonEnemy(e, v);
      break;
  }
  e._intentTurn++;
  if(e.isBoss && !e.phase2 && e.hp <= e.maxHP/2){ e.phase2 = true; }
}

function applyEnemyDebuff(e, text){
  const p = state.player;
  if(text.includes("Weak")) p.applyWeak(1);
  else if(text.includes("Vulnerable")) p.applyVulnerable(1);
  else if(text.includes("Bleed")) p.applyBleed(1);
  else if(text.includes("Poison")) p.applyPoison(1);
}
function applyEnemyBuff(e, intent){
  const text = intent.text;
  const v = intent.value;
  if(text.includes("Strength")){ e.str += (typeof v==="number"?v:0); }
  else if(text.includes("Block")) e.gainBlock(5);
  else if(text.includes("Regen")) e.applyRegen(2);
  else if(text.includes("Intangible")) e.intangible = 1;
  else if(text.includes("Vulnerable")) e.applyVulnerable(1);
}
function summonEnemy(source, id){
  state.enemies.push(makeEnemyInstance(id));
}

// ---- Boss phase strength applied at start of boss's own turn ----
function bossPhaseStrength(e){
  if(!e.isBoss || !e.phase2) return;
  if(e.id==="lichking" || e.id==="archdemon") e.str += 2;
  else if(e.id==="eldritchtreant") e.str += 1;
}

// ---- Check combat end ----
function checkCombatEnd(){
  if(state.mode!=="combat") return;
  // Player dead?
  if(state.player.hp<=0){ checkPlayerDeath(); return; }
  // All enemies dead?
  if(state.enemies.every(e=>e.hp<=0)){
    endCombatVictory();
  }
}

// ---- Player death ----
function checkPlayerDeath(){
  if(state.mode!=="combat") return;
  if(state.player.hp>0) return;
  state.mode="death";
  render();
}

// ---- Combat victory ----
function endCombatVictory(){
  const p = state.player;
  const isBoss = state.enemies.some(e=>e.isBoss);
  const isElite = state.enemies.some(e=>ENEMY_DEF[e.id] && ENEMY_DEF[e.id].elite);
  const cb = state._pendingCb;
  state._pendingCb = null;
  // Ashen Armor
  if(p.relics.includes("ashen_armor")) gainHP(p,6);
  // Relic on combat end
  p.relics.forEach(rid=>{ const r=RELICS[rid]; if(r.onCombatEnd) r.onCombatEnd(state); });
  state.mode = "reward";
  buildReward(isBoss, isElite);
  render();
  if(cb) cb();
}

// ---- Rewards ----
function rewardChoices(kind){
  // returns array of {card}
  const p = state.player;
  const pool = p.classDef.pool;
  const keys = Object.keys(pool);
  const offers = [];
  const count = 3;
  for(let i=0;i<count;i++){
    const roll = Math.random();
    let rar;
    if(kind==="boss"){ rar = i===0 ? "Rare" : (Math.random()<0.5?"Common":"Uncommon"); }
    else if(kind==="elite"){ rar = Math.random()<0.2 ? "Rare" : (Math.random()<0.5?"Common":"Uncommon"); }
    else { rar = Math.random()<0.6?"Common":(Math.random()<0.5?"Uncommon":"Rare"); }
    const avail = keys.filter(k=>pool[k].rarity===rar);
    const id = pick(avail.length?avail:keys);
    offers.push(makeCard(id));
  }
  return offers;
}

function buildReward(isBoss, isElite){
  const p = state.player;
  let gold = 0;
  if(isBoss) gold = 50;
  else if(isElite) gold = rand(25,35);
  else gold = rand(10,20);
  gold += state.goldBonus;
  p.gold += gold;

  const rewards = [];
  rewards.push({kind:"gold", amount:gold});

  // Card choice
  if(!isBoss){
    rewards.push({kind:"card", offers:rewardChoices(isElite?"elite":"normal")});
  } else {
    rewards.push({kind:"card", offers:rewardChoices("boss")});
  }

  // Potion(s)
  if(isBoss){
    rewards.push({kind:"potions", count:2});
  } else if(isElite){
    if(Math.random()<0.7) rewards.push({kind:"potion", count:1});
  } else {
    if(Math.random()<0.4) rewards.push({kind:"potion", count:1});
  }

  // Relic
  if(isElite){
    rewards.push({kind:"relic"});
  }
  // Boss relic choice
  if(isBoss){
    rewards.push({kind:"bossrelic"});
  }
  state.rewardQueue = rewards;
}

function grantPotion(id){
  const p = state.player;
  if(p.potions.length>=3){ toast("Potion slots full — potion lost."); return; }
  p.potions.push(id);
}
function grantRelic(id){
  const p = state.player;
  p.relics.push(id);
  const r = RELICS[id];
  if(r.onObtain) r.onObtain(state);
}
function makeCommonRelic(){
  const commons = ["bloodstone","cursed_amulet","bleeding_edge","ashen_armor","greedy_imp"];
  return pick(commons);
}
function makeRareRelic(){
  const rares = ["shadow_cloak","tome_of_dark_rituals","soul_ember","eternal_hourglass","vial_of_shadows"];
  return pick(rares);
}
function upgradeRandomCards(deck, n){
  const upgradable = deck.filter(c=>!c.status && !c.upgraded && !c.isToken);
  for(let i=0;i<n && upgradable.length;i++){
    const c = pick(upgradable);
    c.upgraded = true;
    upgradable.splice(upgradable.indexOf(c),1);
  }
}

function nextReward(){
  if(!state.rewardQueue || state.rewardQueue.length===0){
    afterReward(); return;
  }
  const r = state.rewardQueue[0];
  if(r.kind==="gold"){
    toast("+" + r.amount + " Gold");
    state.rewardQueue.shift(); nextReward();
  } else if(r.kind==="card"){
    state.curReward = {kind:"card", offers:r.offers}; render();
  } else if(r.kind==="potions"){
    for(let i=0;i<r.count;i++) grantPotion(randPotion());
    toast("Got potions!");
    state.rewardQueue.shift(); nextReward();
  } else if(r.kind==="potion"){
    grantPotion(randPotion());
    toast("Got a potion!");
    state.rewardQueue.shift(); nextReward();
  } else if(r.kind==="relic"){
    grantRelic(makeCommonRelic());
    toast("Got a Relic!");
    state.rewardQueue.shift(); nextReward();
  } else if(r.kind==="bossrelic"){
    state.curReward = {kind:"bossrelic"}; render();
  }
}
function randPotion(){
  const ids = Object.keys(POTIONS).filter(k=>k!=="smoke_bomb");
  return pick(ids);
}
function afterReward(){
  // boss defeated? advance act
  const isBoss = state.enemies.some(e=>e.isBoss);
  if(isBoss){
    endAct();
  } else {
    state.mode = "map";
    render();
  }
}

function endAct(){
  const p = state.player;
  // Full heal
  p.hp = p.maxHP;
  state.actIndex++;
  state.floorIndex = 0;
  if(state.actIndex >= ACTS.length){
    state.mode = "victory";
    state.won = true;
  } else {
    state.mode = "map";
  }
  render();
}

// ============================================================
//  UI RENDERING
// ============================================================
function h(){ return $("app"); }

function render(){
  const app = $("app");
  if(!app) return;
  app.innerHTML = "";
  app.appendChild(titleBar());
  switch(state.mode){
    case "classselect": renderClassSelect(app); break;
    case "map": renderMap(app); break;
    case "combat": renderCombat(app); break;
    case "reward": renderReward(app); break;
    case "shop": renderShop(app); break;
    case "event": renderEvent(app); break;
    case "rest": renderRest(app); break;
    case "death": renderDeath(app); break;
    case "victory": renderVictory(app); break;
    default: app.innerHTML += "<p>Unknown mode</p>";
  }
}

function titleBar(){
  const bar = document.createElement("div");
  bar.className = "title-logo";
  bar.textContent = "Shadows of the Abyss";
  const sub = document.createElement("div");
  sub.className = "title-sub";
  sub.textContent = "A Roguelike Deckbuilder";
  const wrap = document.createElement("div");
  wrap.appendChild(bar); wrap.appendChild(sub);
  if(state.mode!=="classselect"){
    const info = document.createElement("div");
    info.style.textAlign="center"; info.style.marginTop="6px";
    if(state.player){
      info.innerHTML = `<span class="muted">Class: </span><b style="color:${state.player.classDef.color}">${state.player.classDef.name}</b>
        <span class="muted"> · Gold: </span><span class="gold-t">${state.player.gold}</span>
        <span class="muted"> · HP: </span><span class="red">${state.player.hp}/${state.player.maxHP}</span>`;
    }
    wrap.appendChild(info);
  }
  return wrap;
}

// ---------- Class Select ----------
function renderClassSelect(app){
  const panel = el("panel", {style:"text-align:center; margin-top:20px;"});
  panel.innerHTML = `<h2 class="muted">Choose your cursed fate</h2>
    <p class="muted">Each begins with 70 HP, 3 energy, 99 gold, and a starter deck.</p>`;
  app.appendChild(panel);
  const row = el("div",{style:"display:flex; gap:24px; justify-content:center; flex-wrap:wrap; margin-top:20px;"});
  // fix: CLASSES values don't have classId; pass key
  row.innerHTML="";
  Object.keys(CLASSES).forEach(k=>{
    const c = CLASSES[k];
    const card = el("div",{className:"classcard", style:`border-color:${c.color}`});
    card.innerHTML = `<div class="cicon">${c.icon}</div>
      <h3 style="color:${c.color}">${c.name}</h3>
      <p class="muted" style="font-size:13px">${c.desc}</p>
      <div style="font-size:11px; margin-top:8px; color:var(--gold2)">
        Signature: ${Object.keys(c.pool).find(id=>["sanguine_strike","shadow_bolt","firebolt"].includes(id))}
      </div>`;
    card.onclick = ()=> startRun(k);
    row.appendChild(card);
  });
  app.appendChild(row);
}

function startRun(classId){
  const p = newPlayer(classId);
  p.gold = 99;
  state.player = p;
  state.actIndex = 0;
  state.floorIndex = 0;
  state.mode = "map";
  render();
}

// ---------- Map ----------
function renderMap(app){
  const act = ACTS[state.actIndex];
  const panel = el("panel");
  const head = el("div",{className:"section-head"});
  head.innerHTML = `<h2>${act.name}</h2>
    <div>
      <span class="muted">Floor </span><b>${state.floorIndex+2}</b><span class="muted"> / 15</span>
      <span class="muted" style="margin-left:16px">Relics: ${state.player.relics.length}</span>
    </div>`;
  panel.appendChild(head);
  app.appendChild(panel);

  // Potions row
  const pinfo = el("div",{style:"margin:8px 0;"});
  pinfo.innerHTML = `<b>Potions (${state.player.potions.length}/3):</b> ` +
    state.player.potions.map(id=>`<span class="tag">${POTIONS[id].icon} ${POTIONS[id].name}</span>`).join(" ") + " ";
  panel.appendChild(pinfo);

  const map = el("div",{className:"map-wrap"});
  act.floors.forEach((f, i)=>{
    const floorNum = i+2;
    const isPast = floorNum < state.floorIndex+2;
    const isCurrent = floorNum === state.floorIndex+2;
    const node = el("div",{className:`node ${NODE_ICONS[f.type]} ${isCurrent?"current":""} ${isPast?"done":""}`});
    node.innerHTML = f.icon;
    node.title = f.name;
    if(isCurrent){
      node.onclick = ()=> enterFloor(i);
    } else if(isPast){
      node.style.cursor="default";
    } else {
      node.style.opacity=".5";
      node.style.cursor="not-allowed";
    }
    const label = el("div",{style:"font-size:11px; text-align:center; color:var(--muted); margin-top:2px;"});
    label.textContent = "F"+floorNum;
    const col = el("div",{style:"display:flex; flex-direction:column; align-items:center;"});
    col.appendChild(node); col.appendChild(label);
    map.appendChild(col);
    if(i < act.floors.length-1){
      const line = el("div",{className:"path-line"});
      map.appendChild(line);
    }
  });
  panel.appendChild(map);
}

const NODE_ICONS = {enemy:"enemy", elite:"elite", rest:"rest", shop:"shop", event:"event", boss:"boss"};

function enterFloor(i){
  const act = ACTS[state.actIndex];
  const f = act.floors[i];
  state.floorIndex = i;
  switch(f.type){
    case "enemy":
    case "elite":
      startCombat(f.group, f.name, false);
      break;
    case "boss":
      startCombat(f.group, f.name, true);
      break;
    case "shop":
      state.currentFloor = f; state.mode="shop"; render(); break;
    case "event":
      const ev = pick(EVENTS.filter(e=>e.id!==undefined));
      state.currentEvent = ev; state.mode="event"; render(); break;
    case "rest":
      state.currentFloor = f; state.mode="rest"; render(); break;
    default:
      startCombat(f.group, f.name, false);
  }
}

// ---------- Combat ----------
function renderCombat(app){
  const p = state.player;
  const head = el("div",{className:"panel"});
  head.innerHTML = `<div class="flex" style="justify-content:space-between;">
    <span><b>${state.combatLabel}</b> ${state.combatSpecial?'<span class="tag">special</span>':''}</span>
    <span class="muted">Act ${state.actIndex+1}, Floor ${state.floorIndex+2}</span>
  </div>`;
  app.appendChild(head);

  // Enemies
  const eg = el("div",{className:"enemy-grid"});
  state.enemies.forEach((e,idx)=>{
    const intent = e.isBoss ? e.getBossIntent() : e.intent();
    const targetable = state.targeting && state.targeting===e;
    const div = el("div",{className:`enemy ${targetable?"targetable":""}`});
    div.innerHTML = `
      ${e.isBoss?'<div class="tag">'+(e.phase2?"PHASE 2":"PHASE 1")+'</div>':''}
      <div class="intent"><span class="it">${intentText(intent)}</span></div>
      <div class="eicon">${e.icon}</div>
      <div class="ename">${e.name}</div>
      <div class="hpbar"><div style="width:${100*e.hp/e.maxHP}%"></div></div>
      <div class="muted">${e.hp}/${e.maxHP}</div>
      ${e.block>0?`<span class="blockbadge">🛡 ${e.block}</span>`:''}
      <div>${enemyStatusBadges(e)}</div>`;
    div.onclick = ()=> onEnemyClick(e);
    eg.appendChild(div);
  });
  app.appendChild(eg);

  // Powers + status row
  const info = el("div",{className:"panel"});
  info.innerHTML = `<div class="flex" style="justify-content:space-between; align-items:flex-start;">
    <div>
      <div class="muted" style="font-size:11px; text-transform:uppercase; letter-spacing:1px">Your Status</div>
      <div>${playerStatusBadges(p)}</div>
    </div>
    <div>
      <div class="muted" style="font-size:11px; text-transform:uppercase; letter-spacing:1px">Powers</div>
      <div class="powers-zone">${powersZone(p)}</div>
    </div>
  </div>`;
  app.appendChild(info);

  // Player stats
  const stats = el("div",{className:"panel"});
  stats.innerHTML = `<div class="resources">
    <div class="stat"><span class="v" style="color:#e66">${p.hp}</span><span class="k">HP</span></div>
    <div class="stat"><span class="v" style="color:#6ec1ff">${p.energy}</span><span class="k">Energy</span></div>
    <div class="stat"><span class="v">${p.block}</span><span class="k">Block</span></div>
    <div class="stat"><span class="v gold-t">${p.gold}</span><span class="k">Gold</span></div>
    <div class="muted" style="flex:1"></div>
    <div class="potions">${potionsRow(p)}</div>
  </div>`;
  app.appendChild(stats);

  // Hand
  const hand = el("div",{className:"hand"});
  p.hand.forEach(card=>{
    hand.appendChild(cardElement(card, p.energy>=card.cost && !card.status && !card.isStatus));
  });
  app.appendChild(hand);

  // End turn + draw/discard counts
  const foot = el("div",{className:"flex", style:"justify-content:space-between; margin-top:4px;"});
  foot.innerHTML = `<span class="muted">Draw: ${p.draw.length} · Discard: ${p.discard.length} · Exhaust: ${p.exhaust.length}</span>
    <div>
      <button id="btnDrawInfo" class="gold" onclick="showDeck()">📖 Deck</button>
      <button class="primary" id="btnEndTurn" onclick="onEndTurn()">End Turn</button>
    </div>`;
  app.appendChild(foot);
}

function intentText(intent){
  const v = intent.value;
  switch(intent.type){
    case "multi": return "⚔️ " + v.join("×") + " dmg";
    case "attack2": return "⚔️ " + v[0] + " + " + intent.text.split(",")[1];
    case "shieldbash": return "⚔️" + v[0] + " 🛡" + v[1];
    case "shieldbash2": return "🛡" + v[0] + " ❤" + v[1];
    case "drain": return "⚔️ " + v[0] + " ❤" + v[1];
    case "summon": return "✨ Summon";
    case "debuff": return "✦ " + intent.text;
    case "buff": return "✦ " + intent.text;
    default: return (intent.text||"");
  }
}

function enemyStatusBadges(e){
  let s = "";
  if(e.hasWeak()) s+=`<span class="statusbadge sb-weak">Weak ${e._weak}</span>`;
  if(e.hasVulnerable()) s+=`<span class="statusbadge sb-vulnerable">Vuln ${e._vuln}</span>`;
  if(e.hasBleed()) s+=`<span class="statusbadge sb-bleed">Bleed ${e._bleed}</span>`;
  if(e.hasBurn()) s+=`<span class="statusbadge sb-burn">Burn ${e._burn}</span>`;
  if(e.hasRegen()) s+=`<span class="statusbadge sb-regen">Regen ${e._regen}</span>`;
  if(e.intangible>0) s+=`<span class="statusbadge sb-intangible">Intangible ${e.intangible}</span>`;
  return s;
}

function playerStatusBadges(p){
  let s = "";
  if(p._weak>0) s+=`<span class="statusbadge sb-weak">Weak ${p._weak}</span>`;
  if(p._vuln>0) s+=`<span class="statusbadge sb-vulnerable">Vuln ${p._vuln}</span>`;
  if(p._bleed>0) s+=`<span class="statusbadge sb-bleed">Bleed ${p._bleed}</span>`;
  if(p._burn>0) s+=`<span class="statusbadge sb-burn">Burn ${p._burn}</span>`;
  if(p._poison>0) s+=`<span class="statusbadge sb-poison">Poison ${p._poison}</span>`;
  if(p.strBuff>0) s+=`<span class="statusbadge sb-strength">Str ${p.strBuff}</span>`;
  if(p.dexBuff>0) s+=`<span class="statusbadge sb-dexterity">Dex ${p.dexBuff}</span>`;
  if(p.regen>0) s+=`<span class="statusbadge sb-regen">Regen ${p.regen}</span>`;
  if(p.intangible>0) s+=`<span class="statusbadge sb-intangible">Intangible ${p.intangible}</span>`;
  if(p._soulSiphon) s+=`<span class="statusbadge" style="background:#3a2a5a">Soul Siphon</span>`;
  if(p._hellfireAura) s+=`<span class="statusbadge" style="background:#5a3010">Hellfire Aura</span>`;
  return s;
}

function powersZone(p){
  const chips = [];
  if(p._soulSiphon) chips.push(powerChip("Soul Siphon","🔮"));
  if(p._hellfireAura) chips.push(powerChip("Hellfire Aura", p._hellfireAuraUpgraded?"🔥":"🔥"));
  if(p._umbralMastery) chips.push(powerChip("Umbral Mastery","🌑"));
  if(p.strPower>0) chips.push(powerChip("Strength","💪"));
  if(chips.length===0) return '<span class="muted" style="font-size:12px">none</span>';
  return chips.join("");
}
function powerChip(name,icon){ return `<div class="powerchip"><span style="font-size:16px">${icon}</span>${name}</div>`; }

function potionsRow(p){
  return p.potions.map(id=>`<div class="potion" title="${POTIONS[id].name}" onclick="onPotion('${id}')">${POTIONS[id].icon}<span class="pnum">${p.potions.length}</span></div>`).join("");
}

// ---------- Card element ----------
function cardElement(card, playable){
  const div = el("div",{className:`card ${card.type.toLowerCase()}` + (playable?" playable":"") + (card.ethereal||card.isStatus?" ethereal":"")});
  if(card.upgraded) div.innerHTML += `<span class="up">+</span>`;
  div.innerHTML += `<div class="cost">${card.cost}</div>
    <div class="cname">${card.name}${card.upgraded?"+":""}</div>
    <div class="ctype">${card.type}${card.isStatus?" / Status":card.isToken?" / Token":""}</div>
    <div class="cdesc">${card.desc}${card.exhaust?" <br><span class='tag'>Exhaust</span>":""}${card.doubt?" <br><span class='tag'>Doubt</span>":""}</div>
    <div class="rarity">${card.rarity}${card.targetType==="enemy"?" · Target":card.targetType==="all"?" · All":""}</div>`;
  if(playable){
    div.onclick = ()=> onCardClick(card);
  }
  return div;
}

// ---------- Card / enemy / potion clicks ----------
function onCardClick(card){
  const p = state.player;
  if(state.mode!=="combat") return;
  if(card.status || card.isStatus) return;
  if(!canPlayCard(card)){ toast("Can't play that card."); return; }
  if(card.cost>0){
    // Already selected?
    if(state.curCard===card){ state.curCard=null; state.targeting=null; render(); return; }
    if(card.targetType==="enemy"){
      if(state.curCard===card){
        // switch selection: play it (no target) -> for single attacks, need target.
        // Instead just select.
      }
      state.curCard = card;
      state.targeting = null;
      // If targeting required, show targetable enemies
      render();
      // Highlight enemies
      highlightEnemyTargets();
    } else {
      // No target needed
      state.curCard = card;
      state.targeting = null;
      playCard(card, null);
    }
  } else {
    state.curCard = card;
    state.targeting = null;
    playCard(card, null);
  }
}

function highlightEnemyTargets(){
  // re-render and add targetable class
  render();
  document.querySelectorAll(".enemy.targetable").forEach(el=>{
    el.onclick = ()=>{
      const idx = [...document.querySelectorAll(".enemy")].indexOf(el);
      const target = state.enemies[idx];
      playCard(state.curCard, target);
    };
  });
}

function onEnemyClick(e){
  if(state.targeting && state.curCard){
    playCard(state.curCard, e);
  }
}

function onPotion(id){
  if(state.mode!=="combat") return;
  const potion = POTIONS[id];
  potion.effect(state);
  // remove from potions
  const idx = state.player.potions.indexOf(id);
  if(idx>=0) state.player.potions.splice(idx,1);
  toast(potion.name + " used!");
  checkCombatEnd();
  render();
}

function onEndTurn(){
  if(state.mode!=="combat") return;
  // Doubt at end of turn
  const p = state.player;
  p.hand.filter(c=>c.doubt).forEach(c=>{ loseHP(p,1,true); });
  if(p.hp<=0){ checkPlayerDeath(); return; }
  endPlayerTurn();
}

// ---------- Reward screen ----------
function renderReward(app){
  const panel = el("panel",{style:"text-align:center; margin-top:20px;"});
  panel.innerHTML = `<h2>Victory!</h2>`;
  app.appendChild(panel);
  renderRewardQueue(app);
}

function renderRewardQueue(app){
  const queue = state.rewardQueue;
  const panel = app.querySelector(".panel") || el("panel",{style:"text-align:center; margin-top:20px;"});
  // clear existing reward children
  while(panel.children.length>1) panel.removeChild(panel.lastChild);
  if(!queue || queue.length===0){
    // all done
    const btn = el("div",{className:"btn-row"});
    const go = document.createElement("button");
    go.className="primary"; go.textContent = state.enemies.some(e=>e.isBoss) ? "Continue" : "Continue";
    go.onclick = ()=>{ nextReward(); };
    // Actually nextReward handles shifting; when empty, afterReward:
    // We'll route via nextReward's afterReward. But here queue empty means done.
    go.onclick = ()=> afterReward();
    btn.appendChild(go);
    panel.appendChild(btn);
    return;
  }
  const r = queue[0];
  if(r.kind==="gold"){
    const d = el("div",{style:"font-size:22px; margin:20px 0;"});
    d.innerHTML = `<span class="gold-t">💰 +${r.amount} Gold</span>`;
    panel.appendChild(d);
    const b = el("div",{className:"btn-row"});
    const go = document.createElement("button"); go.className="primary"; go.textContent="Continue";
    go.onclick=()=>{ state.rewardQueue.shift(); renderRewardQueue(app); };
    b.appendChild(go); panel.appendChild(b);
  } else if(r.kind==="card"){
    const grid = el("div",{className:"shop-grid"});
    r.offers.forEach(card=>{
      const ce = cardElement(card, true);
      ce.classList.add("reward-card");
      const wrap = el("div",{style:"display:flex; flex-direction:column; align-items:center;"});
      wrap.appendChild(ce);
      const buy = document.createElement("button");
      buy.className="gold"; buy.style.marginTop="8px"; buy.textContent="Take card";
      buy.onclick=()=>{ state.player.deck.push(card); state.rewardQueue.shift(); renderRewardQueue(app); };
      wrap.appendChild(buy);
      const skip = document.createElement("button");
      skip.style.marginTop="4px"; skip.style.padding="4px 12px"; skip.textContent="Skip";
      skip.onclick=()=>{ state.rewardQueue.shift(); renderRewardQueue(app); };
      wrap.appendChild(skip);
      grid.appendChild(wrap);
    });
    panel.appendChild(grid);
  } else if(r.kind==="potions" || r.kind==="potion"){
    const d = el("div",{style:"font-size:20px; margin:20px 0;"});
    d.innerHTML = `🧪 You received ${r.kind==="potions"?r.count+" potions":"a potion"}!`;
    panel.appendChild(d);
    const b = el("div",{className:"btn-row"});
    const go = document.createElement("button"); go.className="primary"; go.textContent="Continue";
    go.onclick=()=>{ state.rewardQueue.shift(); renderRewardQueue(app); };
    b.appendChild(go); panel.appendChild(b);
  } else if(r.kind==="relic"){
    const id = makeCommonRelic();
    const d = el("div",{style:"font-size:20px; margin:20px 0;"});
    d.innerHTML = `🏅 You received <b>${RELICS[id].name}</b>!<br><span class="muted" style="font-size:13px">${RELICS[id].desc}</span>`;
    panel.appendChild(d);
    const b = el("div",{className:"btn-row"});
    const go = document.createElement("button"); go.className="primary"; go.textContent="Continue";
    go.onclick=()=>{ state.rewardQueue.shift(); renderRewardQueue(app); };
    b.appendChild(go); panel.appendChild(b);
  } else if(r.kind==="bossrelic"){
    const opts = bossRelicOptions();
    const grid = el("div",{className:"shop-grid"});
    opts.forEach(rid=>{
      const wrap = el("div",{style:"width:180px; border:2px solid var(--gold); border-radius:12px; padding:14px; text-align:center; background:var(--panel2);"});
      wrap.innerHTML = `<div style="font-size:24px">👑</div><b style="color:var(--gold2)">${RELICS_BOSSES[rid].name}</b>
        <div class="muted" style="font-size:12px; margin:8px 0">${RELICS_BOSSES[rid].desc}</div>`;
      const take = document.createElement("button");
      take.className="gold"; take.style.marginTop="6px"; take.textContent="Take relic";
      take.onclick=()=>{ grantRelic(rid); state.rewardQueue.shift(); renderRewardQueue(app); };
      wrap.appendChild(take);
      grid.appendChild(wrap);
    });
    panel.appendChild(grid);
  }
}

// boss relics
const RELICS_BOSSES = {
  cursed_crown:{name:"Cursed Crown",desc:"Gain 1 Energy at start of each turn. Curses can no longer be removed.",downside:true},
  ectoplasm:{name:"Ectoplasm",desc:"Gain 1 Energy. Lose all Gold and gain no more Gold this run.",downside:true},
  philosophers_stone:{name:"Philosopher's Stone",desc:"Gain 1 Energy. All enemies start combat with 1 Strength.",downside:true},
  black_blood:{name:"Black Blood",desc:"At start of combat, lose 10% max HP. Whenever you play an Attack, heal 1 HP.",downside:true},
  shadow_amulet:{name:"Shadow Amulet",desc:"Start each turn with 1 Intangible. Reduce max HP by 15.",downside:true},
};
function bossRelicOptions(){
  const keys = Object.keys(RELICS_BOSSES);
  return shuffle(keys).slice(0,3);
}

// ---------- Helpers ----------
function el(tag, attrs){
  const e = document.createElement(tag);
  if(attrs){
    Object.keys(attrs).forEach(k=>{
      if(k==="className") e.className=attrs[k];
      else if(k==="innerHTML") e.innerHTML=attrs[k];
      else if(k.startsWith("on")) e.addEventListener(k.slice(2), attrs[k]);
      else if(k==="style") e.style.cssText=attrs[k];
      else e.setAttribute(k, attrs[k]);
    });
  }
  return e;
}

// ---------- Deck view ----------
function showDeck(){
  const p = state.player;
  const panel = el("panel");
  panel.style.maxWidth="900px"; panel.style.margin="10px auto";
  panel.innerHTML = `<div class="section-head"><h3>Your Deck</h3>
    <button onclick="render()">Close</button></div>
    <div class="muted">Draw: ${p.draw.length} · Discard: ${p.discard.length} · Exhaust: ${p.exhaust.length} · Total unique cards: ${deckNoExhaust(p).length}</div>`;
  const grid = el("div",{className:"shop-grid"});
  deckNoExhaust(p).forEach(card=>{ grid.appendChild(cardElement(card, false)); });
  document.getElementById("app").appendChild(panel);
  document.getElementById("app").appendChild(grid);
}

// ---------- Remove card from deck (events/rewards) ----------
function askRemoveFromDeck(reason){
  const p = state.player;
  const panel = el("panel",{style:"margin-top:20px;"});
  panel.style.maxWidth="900px"; panel.style.margin="20px auto";
  panel.innerHTML = `<h3>${reason}</h3><p class="muted">Choose a card to remove permanently.</p>`;
  const grid = el("div",{className:"shop-grid"});
  const upgradable = deckNoExhaust(p);
  if(upgradable.length===0){
    panel.innerHTML += `<p class="muted">No cards to remove.</p>
      <div class="btn-row"><button class="primary" onclick="finishRemove()">Done</button></div>`;
    document.getElementById("app").appendChild(panel);
    return;
  }
  upgradable.forEach(card=>{
    const wrap = el("div",{style:"display:flex; flex-direction:column; align-items:center;"});
    wrap.appendChild(cardElement(card, true));
    const rm = document.createElement("button");
    rm.className="primary"; rm.style.marginTop="6px"; rm.textContent="Remove";
    rm.onclick=()=>{
      p.deck = p.deck.filter(c=>c!==card);
      toast("Removed "+card.name+".");
      render();
    };
    wrap.appendChild(rm);
    grid.appendChild(wrap);
  });
  panel.appendChild(grid);
  document.getElementById("app").appendChild(panel);
}

// ---------- Shop ----------
function renderShop(app){
  const p = state.player;
  const panel = el("panel",{style:"margin-top:10px;"});
  panel.innerHTML = `<div class="section-head"><h2>🏪 Shop</h2>
    <div><span class="gold-t" style="font-size:20px">💰 ${p.gold}</span> · <button class="primary" onclick="leaveShop()">Leave</button></div></div>`;
  app.appendChild(panel);

  const wares = [];
  // 3 class cards
  for(let i=0;i<3;i++){ wares.push({card:pickCardFromPool(), price:cardPrice(pickCardFromPool())}); }
  // 1 Colorless
  const colorless = pick(Object.keys(COLORLESS));
  // 2 Relics
  const relics = [makeCommonRelic(), makeRareRelic()];
  // 3 Potions
  const pots = [randPotion(), randPotion(), randPotion()];

  // Cards
  const ch = el("div",{className:"section-head"});
  ch.innerHTML = `<h3>Cards</h3>`;
  panel.appendChild(ch);
  const grid = el("div",{className:"shop-grid"});
  const offered = [pickCardFromPool(), pickCardFromPool(), pickCardFromPool(), makeCard(colorless)];
  offered.forEach(card=>{
    const wrap = el("div",{style:"display:flex; flex-direction:column; align-items:center;"});
    wrap.appendChild(cardElement(card, true));
    const price = cardPrice(card);
    const buy = document.createElement("button");
    buy.className="gold"; buy.style.marginTop="6px"; buy.textContent=`Buy — ${price}g`;
    buy.disabled = p.gold < price;
    buy.onclick=()=>{ if(p.gold>=price){ p.gold-=price; p.deck.push(card); toast("Bought "+card.name+"."); render(); } };
    wrap.appendChild(buy);
    grid.appendChild(wrap);
  });
  panel.appendChild(grid);

  // Relics
  const rh = el("div",{className:"section-head"});
  rh.innerHTML = `<h3>Relics</h3>`;
  panel.appendChild(rh);
  const rg = el("div",{className:"shop-grid"});
  relics.forEach(rid=>{
    const r = RELICS[rid];
    const wrap = el("div",{style:"width:180px; border:2px solid var(--gold); border-radius:12px; padding:14px; text-align:center; background:var(--panel2);"});
    wrap.innerHTML = `<div style="font-size:24px">🏅</div><b style="color:var(--gold2)">${r.name}</b>
      <div class="muted" style="font-size:12px; margin:8px 0">${r.desc}</div>`;
    const buy = document.createElement("button");
    buy.className="gold"; buy.style.marginTop="6px"; buy.textContent="Buy — 150g";
    buy.disabled = p.gold<150 || p.relics.includes(rid);
    buy.onclick=()=>{ if(p.gold>=150 && !p.relics.includes(rid)){ p.gold-=150; grantRelic(rid); toast("Got "+r.name+"."); render(); } };
    wrap.appendChild(buy);
    rg.appendChild(wrap);
  });
  panel.appendChild(rg);

  // Potions
  const ph = el("div",{className:"section-head"});
  ph.innerHTML = `<h3>Potions</h3>`;
  panel.appendChild(ph);
  const pg = el("div",{className:"shop-grid"});
  pots.forEach(pid=>{
    const pot = POTIONS[pid];
    const wrap = el("div",{style:"width:150px; text-align:center;"});
    wrap.innerHTML = `<div style="font-size:30px">${pot.icon}</div><b>${pot.name}</b>
      <div class="muted" style="font-size:12px">${pot.desc||""}</div>`;
    const buy = document.createElement("button");
    buy.className="gold"; buy.style.marginTop="6px"; buy.textContent="Buy — 50g";
    buy.disabled = p.gold<50 || p.potions.length>=3;
    buy.onclick=()=>{ if(p.gold>=50 && p.potions.length<3){ p.gold-=50; grantPotion(pid); toast("Bought "+pot.name+"."); render(); } };
    wrap.appendChild(buy);
    pg.appendChild(wrap);
  });
  panel.appendChild(pg);

  // Card removal service
  const rem = el("div",{className:"panel", style:"margin-top:10px; text-align:center;"});
  const canRemove = p.deck.length>10;
  rem.innerHTML = `<h3>Card Removal Service</h3>
    <p class="muted">Remove 1 card from your deck permanently (once per visit).</p>
    <div class="btn-row"><button class="gold" ${canRemove?"":"disabled"} onclick="shopRemove()">Remove a card — 75g</button></div>`;
  panel.appendChild(rem);
}

function cardPrice(card){
  if(card.rarity==="Common") return 50;
  if(card.rarity==="Uncommon") return 75;
  if(card.rarity==="Rare") return 150;
  return 60;
}
function pickCardFromPool(){
  const p = state.player;
  const pool = p.classDef.pool;
  const keys = Object.keys(pool);
  const roll = Math.random();
  let rar = roll<0.6?"Common":(roll<0.9?"Uncommon":"Rare");
  const avail = keys.filter(k=>pool[k].rarity===rar);
  const id = pick(avail.length?avail:keys);
  return makeCard(id);
}
function shopRemove(){
  const p = state.player;
  if(p.gold<75){ toast("Not enough gold."); return; }
  p.gold-=75;
  askRemoveFromDeck("Shop card removal (75g)");
}
function leaveShop(){ state.mode="map"; render(); }

// ---------- Event ----------
function renderEvent(app){
  const ev = state.currentEvent;
  const panel = el("panel",{style:"max-width:700px; margin:20px auto;"});
  panel.innerHTML = `<div style="text-align:center; font-size:56px">${ev.icon}</div>
    <h2 style="text-align:center">${ev.name}</h2>
    <p class="big" style="text-align:center">${ev.text}</p>`;
  app.appendChild(panel);
  const b = el("div",{className:"btn-row"});
  ev.choices.forEach(ch=>{
    const btn = document.createElement("button");
    btn.className="primary"; btn.textContent = ch.label;
    btn.onclick = ()=> ch.run(state);
    b.appendChild(btn);
  });
  panel.appendChild(b);
}

// ---------- Rest ----------
function renderRest(app){
  const p = state.player;
  const panel = el("panel",{style:"text-align:center;"});
  const healAmt = Math.floor(p.maxHP*0.25);
  panel.innerHTML = `<div style="font-size:56px">🔥</div>
    <h2>Rest Site</h2>
    <p class="muted">A moment of safety in the abyss.</p>
    <div class="btn-row">
      <button class="primary" onclick="restHeal()">Rest — Heal ${healAmt} HP (${Math.floor(p.maxHP*0.25)} HP)</button>
      <button class="gold" onclick="restForge()">Forge — Upgrade a card</button>
      <button onclick="restMeditate()">Meditate — Remove a card</button>
    </div>
    <div class="btn-row"><button onclick="leaveRest()">Leave</button></div>`;
  app.appendChild(panel);
}
function restHeal(){
  const p = state.player;
  gainHP(p, Math.floor(p.maxHP*0.25));
  toast("You rest and recover.");
  state.mode="map"; render();
}
function restForge(){
  const p = state.player;
  const upgradable = deckNoExhaust(p).filter(c=>!c.upgraded);
  if(upgradable.length===0){ toast("No cards to upgrade."); return; }
  const panel = el("panel",{style:"text-align:center;"});
  panel.innerHTML = `<h2>Forge — choose a card to upgrade</h2>
    <p class="muted">Upgraded cards are permanently improved.</p>`;
  app.appendChild(panel);
  const grid = el("div",{className:"shop-grid"});
  upgradable.forEach(card=>{
    const wrap = el("div",{style:"display:flex; flex-direction:column; align-items:center;"});
    wrap.appendChild(cardElement(card, true));
    const up = document.createElement("button");
    up.className="gold"; up.style.marginTop="6px"; up.textContent="Upgrade";
    up.onclick=()=>{ card.upgraded=true; toast(card.name+" upgraded!"); state.mode="map"; render(); };
    wrap.appendChild(up);
    grid.appendChild(wrap);
  });
  panel.appendChild(grid);
  const back = document.createElement("button");
  back.className="primary"; back.style.marginTop="10px"; back.textContent="Back";
  back.onclick=()=>renderRest(document.getElementById("app"));
  panel.appendChild(back);
}
function restMeditate(){
  askRemoveFromDeck("Meditate — remove a card from your deck");
}
function leaveRest(){ state.mode="map"; render(); }

// ---------- Death ----------
function renderDeath(app){
  const panel = el("panel",{style:"text-align:center; margin-top:40px;"});
  panel.innerHTML = `<div style="font-size:72px">💀</div>
    <h2 class="red">You Have Died</h2>
    <p class="big">The Abyss claims another soul...</p>
    <p class="muted">Act ${state.actIndex+1}, Floor ${state.floorIndex+2}</p>
    <div class="btn-row"><button class="primary" onclick="restart()">Start a New Run</button></div>`;
  app.appendChild(panel);
}
function restart(){ state.mode="classselect"; render(); }

// ---------- Victory ----------
function renderVictory(app){
  const panel = el("panel",{style:"text-align:center; margin-top:40px;"});
  panel.innerHTML = `<div style="font-size:72px">👑</div>
    <h2 class="gold-t">Victory!</h2>
    <p class="big">You have defeated the Archdemon and conquered the Abyss.</p>
    <p class="muted">Your cursed journey ends in triumph.</p>
    <div class="btn-row"><button class="primary" onclick="restart()">Play Again</button></div>`;
  app.appendChild(panel);
}
</script>
<script>
render();
</script>
</html>
<!-- agent-meta {"model":"bartowski/Ornith-1.5-35B-A3B","provider":"brigid","persona":"full","sessionId":"1c9266dd-a2e3-42fb-9ae0-f4559c20fd22","tokensIn":9847301,"tokensOut":101088,"tokensTotal":9948389,"cost":0,"turns":125,"toolCalls":124,"failedToolCalls":4,"timestamp":"2026-08-23T22:51:41.755Z"} -->