← Shadows results

Shadows

muse spark 1.2 · basic

◈Preview unavailable — try playing the result

Browser check: Preview unavailable or timed out. This is a technical check, not a quality review.

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 77,241 bytes · SHA-256 2de2afdd7432
<!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>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700;900&family=Cinzel+Decorative:wght@700&family=JetBrains+Mono:wght@400;700&family=IM+Fell+English:ital@0;1&display=swap" rel="stylesheet">
<script type="importmap">{"imports":{"three":"https://unpkg.com/three@0.160.0/build/three.module.js"}}</script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{--bg:#0a0a0f;--panel:#14141e;--panel2:#1d1d2b;--border:#2a2a3a;--gold:#d4a44c;--blood:#b91c1c;--shadow:#7c3aed;--fire:#ea580c;--hp:#dc2626;--block:#3b82f6;--text:#e7e5e4;--muted:#a8a29e}
body{background:var(--bg);color:var(--text);font-family:'JetBrains Mono',monospace;overflow-x:hidden}
#bg3d{position:fixed;inset:0;z-index:-1;opacity:0.45}
h1,h2,h3{font-family:'Cinzel',serif;letter-spacing:0.08em}
.decor{font-family:'Cinzel Decorative',serif}
.topbar{position:sticky;top:0;z-index:20;background:rgba(10,10,15,0.92);backdrop-filter:blur(8px);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:10px 16px;gap:12px;flex-wrap:wrap}
.topbar .left{display:flex;gap:14px;align-items:center;flex-wrap:wrap}
.pill{border:1px solid var(--border);background:var(--panel);padding:6px 10px;border-radius:999px;font-size:12px;display:flex;gap:8px;align-items:center}
.pill b{color:var(--gold)}
.hpbar{width:140px;height:10px;background:#1f1f2a;border-radius:999px;overflow:hidden;border:1px solid #333}
.hpbar i{display:block;height:100%;background:linear-gradient(90deg,#991b1b,#ef4444);transition:width .4s}
.blockbar{width:80px;height:10px;background:#1e293b;border-radius:999px;overflow:hidden;border:1px solid #334155}
.blockbar i{display:block;height:100%;background:#3b82f6}
.btn{border:1px solid var(--border);background:linear-gradient(180deg,#1e1e2e,#151520);color:var(--text);padding:10px 14px;border-radius:10px;cursor:pointer;font-weight:700;font-size:12px;letter-spacing:0.06em;text-transform:uppercase;transition:.15s}
.btn:hover{transform:translateY(-1px);border-color:#3a3a4a;filter:brightness(1.1)}
.btn:disabled{opacity:.45;cursor:not-allowed;transform:none}
.btn-gold{background:linear-gradient(180deg,#3a2f1a,#2a2110);border-color:#6b5a2e;color:var(--gold)}
.btn-blood{background:linear-gradient(180deg,#3a1212,#1f0a0a);border-color:#7f1d1d;color:#fca5a5}
.btn-fire{background:linear-gradient(180deg,#3a1f0f,#1f120a);border-color:#9a3412;color:#fdba74}
.container{max-width:1200px;margin:0 auto;padding:18px}
.card{border:1px solid var(--border);background:linear-gradient(180deg,rgba(20,20,30,0.96),rgba(14,14,20,0.98));border-radius:16px;overflow:hidden}
.card-hdr{padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;gap:10px}
.badge{font-size:10px;padding:4px 8px;border-radius:999px;border:1px solid var(--border);background:#0f0f14;color:var(--muted)}
.grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
@media(max-width:900px){.grid3{grid-template-columns:1fr}.pill{font-size:11px}}
.classCard{padding:16px;cursor:pointer;position:relative;overflow:hidden;transition:.2s}
.classCard:hover{transform:translateY(-2px);border-color:#444}
.classCard.selected{border-color:var(--gold);box-shadow:0 0 0 1px rgba(212,164,76,.4),0 10px 30px rgba(0,0,0,.5)}
.classIcon{width:56px;height:56px;border-radius:12px;display:grid;place-items:center;font-size:28px;border:1px solid var(--border);background:var(--panel2)}
.mapWrap{display:grid;grid-template-columns:260px 1fr;gap:16px}
@media(max-width:900px){.mapWrap{grid-template-columns:1fr}}
.mapCol{padding:12px}
.floor{position:relative;display:flex;gap:8px;align-items:center;padding:8px 10px;border-radius:12px;border:1px solid transparent;margin-bottom:8px;background:rgba(255,255,255,0.02)}
.floor.active{border-color:var(--gold);background:rgba(212,164,76,0.08)}
.floor.done{opacity:.5}
.nodeBtn{flex:1;display:flex;justify-content:space-between;align-items:center;gap:8px;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:var(--panel2);cursor:pointer;text-align:left}
.nodeBtn:disabled{opacity:.5;cursor:not-allowed}
.ntype{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.combatArea{display:grid;grid-template-columns:1fr 340px;gap:16px}
@media(max-width:1000px){.combatArea{grid-template-columns:1fr}}
.enemies{display:flex;gap:12px;flex-wrap:wrap;min-height:180px}
.enemyBox{flex:1;min-width:180px;max-width:260px;border:1px solid var(--border);background:linear-gradient(180deg,#1a1a25,#13131a);border-radius:14px;padding:12px;position:relative}
.enemyBox.target{outline:1px solid var(--gold);box-shadow:0 0 18px rgba(212,164,76,.25)}
.intent{font-size:11px;padding:6px 8px;border-radius:999px;border:1px solid var(--border);background:#0f0f14;display:inline-flex;gap:6px;align-items:center;margin-top:8px}
.hand{display:flex;gap:10px;flex-wrap:wrap;min-height:150px;padding:10px;background:rgba(0,0,0,.25);border-radius:12px;border:1px dashed #2a2a3a}
.gcard{width:148px;min-height:168px;border-radius:12px;border:1px solid var(--border);background:linear-gradient(180deg,#1f1f2e,#111119);padding:10px;display:flex;flex-direction:column;gap:6px;cursor:pointer;position:relative;transition:.15s}
.gcard:hover{transform:translateY(-4px) scale(1.02);border-color:#555}
.gcard.unplayable{opacity:.45;filter:grayscale(.3);cursor:not-allowed}
.gcard .cost{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;background:#0a0a0f;border:1px solid var(--gold);color:var(--gold);font-weight:900;font-size:13px}
.gcard .type{font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
.gcard h4{font-family:'Cinzel',serif;font-size:11px;line-height:1.2}
.gcard p{font-size:10px;line-height:1.35;color:#c9c6c2}
.rarityCommon{border-color:#3f3f46}
.rarityUncommon{border-color:#0ea5e9}
.rarityRare{border-color:#d4a44c}
.statusRow{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
.status{font-size:10px;padding:3px 6px;border-radius:999px;border:1px solid var(--border);background:#0a0a0f}
.log{max-height:140px;overflow:auto;font-size:11px;line-height:1.5;background:#0a0a0f;border:1px solid var(--border);border-radius:10px;padding:8px}
.shopGrid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
@media(max-width:800px){.shopGrid{grid-template-columns:1fr}}
.kbd{font-size:10px;border:1px solid #333;border-bottom-width:2px;background:#111;padding:2px 6px;border-radius:6px}
.potion{width:42px;height:42px;border-radius:10px;border:1px solid var(--border);background:var(--panel2);display:grid;place-items:center;cursor:pointer;font-size:20px}
.potion.empty{opacity:.25;cursor:default}
a{color:var(--gold)}
</style>
</head>
<body>
<canvas id="bg3d"></canvas>
<div class="topbar">
  <div class="left">
    <div class="decor" style="font-size:18px;letter-spacing:.14em">SHADOWS<span style="color:var(--gold)"> OF THE ABYSS</span></div>
    <div class="pill"><span>❤ HP</span><b id="hpText">70/70</b><div class="hpbar"><i id="hpFill" style="width:100%"></i></div><span style="color:#60a5fa" id="blockText">0 Block</span></div>
    <div class="pill">⚡ <b id="energyText">3/3</b> Energy</div>
    <div class="pill">🪙 <b id="goldText">99</b> Gold &nbsp; <span id="actText">Act 1</span> <span id="floorText">Floor 1</span></div>
  </div>
  <div style="display:flex;gap:8px;align-items:center">
    <div id="relicBar" style="display:flex;gap:6px;flex-wrap:wrap"></div>
    <div id="potionBar" style="display:flex;gap:6px"></div>
    <button class="btn" onclick="toggleHelp()">?</button>
    <button class="btn btn-gold" onclick="resetRun()">New Run</button>
  </div>
</div>

<div class="container" id="app"></div>

<script type="module">
import * as THREE from 'three';
/* 3D background */
const canvas=document.getElementById('bg3d');
const renderer=new THREE.WebGLRenderer({canvas,alpha:true,antialias:true});
const scene=new THREE.Scene();
const camera=new THREE.PerspectiveCamera(55,innerWidth/innerHeight,0.1,100);
camera.position.set(0,2,8);
renderer.setSize(innerWidth,innerHeight);
scene.fog=new THREE.Fog(0x0a0a0f,6,18);
const geo=new THREE.PlaneGeometry(30,30,22,22);
const mat=new THREE.MeshStandardMaterial({color:0x1a1a2a,wireframe:false,transparent:true,opacity:0.55});
const plane=new THREE.Mesh(geo,mat);plane.rotation.x=-Math.PI/2;plane.position.y=-1.5;scene.add(plane);
const light=new THREE.PointLight(0xd4a44c,2,20);light.position.set(3,4,2);scene.add(light);
const light2=new THREE.PointLight(0x7c3aed,1.2,20);light2.position.set(-4,3,-2);scene.add(light2);
scene.add(new THREE.AmbientLight(0x404060,0.8));
let t=0;function animate(){requestAnimationFrame(animate);t+=0.006;const pos=geo.attributes.position;for(let i=0;i<pos.count;i++){const x=pos.getX(i),y=pos.getY(i);pos.setZ(i,Math.sin(x*0.6+t)*0.35+Math.cos(y*0.5-t)*0.35)}pos.needsUpdate=true;plane.rotation.z=Math.sin(t*0.2)*0.06;renderer.render(scene,camera)}animate();
addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight)});

/* Game Data */
const BASIC={Strike:{type:'Attack',cost:1,rarity:'Basic',desc:'Deal 6 damage.',up:'Deal 8 damage.',dmg:6,upDmg:8},
Defend:{type:'Skill',cost:1,rarity:'Basic',desc:'Gain 5 Block.',up:'Gain 8 Block.',blk:5,upBlk:8}};

const CLASSES={
 BloodReaper:{color:'#dc2626',icon:'🩸',hp:70, relic:'Sacrificial Dagger', cards:[
  {n:'Sanguine Strike',type:'Attack',rarity:'Common',cost:1,desc:'Deal 8 damage. Heal 2 HP.',up:'Deal 10 heal 3.',dmg:8,upDmg:10,heal:2,upHeal:3},
  {n:'Hemorrhage',type:'Attack',rarity:'Common',cost:1,desc:'Deal 5 dmg. Apply 3 Bleed.',up:'6 dmg 4 Bleed',dmg:5,upDmg:6,bleed:3,upBleed:4},
  {n:'Bloodletting',type:'Attack',rarity:'Uncommon',cost:1,desc:'Lose 3 HP. Deal 12 damage.',up:'Lose 2 Deal 15',dmg:12,upDmg:15,lose:3,upLose:2},
  {n:'Crimson Eruption',type:'Attack',rarity:'Rare',cost:2,desc:'Deal 8 to ALL. Lose 2 HP.',up:'Deal 10 to ALL',dmg:8,upDmg:10,lose:2,aoe:true},
  {n:'Crimson Shield',type:'Skill',rarity:'Common',cost:1,desc:'Gain 6 Block. Lose 2 HP.',up:'Gain 8 Block',blk:6,upBlk:8,lose:2},
  {n:'Blood Ward',type:'Skill',rarity:'Common',cost:1,desc:'Gain 3 Block. Gain 1 Regen.',up:'4 Block 2 Regen',blk:3,upBlk:4,regen:1,upRegen:2},
  {n:'Sacrificial Barrier',type:'Skill',rarity:'Uncommon',cost:2,desc:'Gain 10 Block. Lose 5 HP.',up:'Gain 13',blk:10,upBlk:13,lose:5},
  {n:'Vital Surge',type:'Skill',rarity:'Rare',cost:'X',desc:'Gain Block = missing HP. Exhaust.',up:'+3 Block. Exhaust.',exhaust:true},
  {n:'Life Tap',type:'Skill',rarity:'Common',cost:0,desc:'Draw 2. Lose 3 HP.',up:'Draw 3',draw:2,upDraw:3,lose:3},
  {n:'Rend Flesh',type:'Skill',rarity:'Uncommon',cost:1,desc:'Apply 5 Bleed to ALL. Lose 2 HP.',up:'7 Bleed',bleed:5,upBleed:7,lose:2,aoe:true},
  {n:'Soul Siphon',type:'Power',rarity:'Uncommon',cost:2,upCost:1,desc:'At start gain 1 Energy lose 1 HP.',up:'Cost 1'},
  {n:'Blood Pact',type:'Power',rarity:'Rare',cost:1,desc:'When you lose HP from card gain 1 Str.',up:'Gain 2 Str',str:1,upStr:2},
 ]},
 Shadowmancer:{color:'#7c3aed',icon:'🌑',hp:70,cards:[
  {n:'Shadow Bolt',type:'Attack',rarity:'Common',cost:1,desc:'Deal 6 dmg. Apply 1 Weak.',up:'8 dmg 2 Weak',dmg:6,upDmg:8,weak:1,upWeak:2},
  {n:'Grasp of Despair',type:'Attack',rarity:'Common',cost:1,desc:'Deal 4 dmg. Apply 1 Vulnerable.',up:'5 dmg 2 Vulnerable',dmg:4,upDmg:5,vuln:1,upVuln:2},
  {n:'Nightmare Lash',type:'Attack',rarity:'Uncommon',cost:2,desc:'Deal 7 (+7 if Weak).',up:'9 (+9)',dmg:7,upDmg:9},
  {n:'Eternal Darkness',type:'Attack',rarity:'Rare',cost:2,desc:'Deal 3 dmg random 3 times.',up:'4 dmg x3',dmg:3,upDmg:4,hits:3},
  {n:'Cloak of Shadows',type:'Skill',rarity:'Common',cost:1,desc:'Gain 5 Block. Draw 1.',up:'7 Block',blk:5,upBlk:7,draw:1},
  {n:'Haunting Mist',type:'Skill',rarity:'Common',cost:1,desc:'Gain 7 Block. Apply 1 Weak to ALL.',up:'9 Block',blk:7,upBlk:9,weak:1,aoe:true},
  {n:'Phantom Armor',type:'Skill',rarity:'Uncommon',cost:1,desc:'Gain 8 Block. Next turn +2 Dex.',up:'10 Block +3 Dex',blk:8,upBlk:10,dexNext:2,upDexNext:3},
  {n:'Whispering Shroud',type:'Skill',rarity:'Rare',cost:1,desc:'Gain 4 Block. Next turn gain 4 Block.',up:'5 Block',blk:4,upBlk:5,nextBlk:4,upNextBlk:5},
  {n:'Shadowform',type:'Skill',rarity:'Uncommon',cost:2,desc:'Gain 2 Intangible. Exhaust.',up:'3 Intangible',intangible:2,upIntangible:3,exhaust:true},
  {n:'Curse of Misfortune',type:'Skill',rarity:'Common',cost:1,desc:'Enemy -3 Str this turn. Apply 1 Vuln.',up:'-4 Str 2 Vuln',vuln:1,upVuln:2,minusStr:3,upMinusStr:4},
  {n:'Dark Ritual',type:'Skill',rarity:'Uncommon',cost:0,desc:'Gain 2 Energy. Shuffle Dazed into draw.',up:'Into discard',energy:2},
  {n:'Umbral Mastery',type:'Power',rarity:'Rare',cost:2,desc:'At start apply 1 Weak to ALL.',up:'2 Weak',weak:1,upWeak:2},
 ]},
 Hellforged:{color:'#ea580c',icon:'🔥',hp:70,cards:[
  {n:'Firebolt',type:'Attack',rarity:'Common',cost:1,desc:'Deal 7 dmg. Apply 2 Burn.',up:'9 dmg 3 Burn',dmg:7,upDmg:9,burn:2,upBurn:3},
  {n:'Hellfire Blast',type:'Attack',rarity:'Uncommon',cost:2,desc:'Deal 9 to ALL.',up:'12 to ALL',dmg:9,upDmg:12,aoe:true},
  {n:'Searing Strike',type:'Attack',rarity:'Common',cost:1,desc:'Deal 6 dmg. Next turn +1 Energy.',up:'8 dmg',dmg:6,upDmg:8,nextEnergy:1},
  {n:'Immolate',type:'Attack',rarity:'Rare',cost:2,desc:'Deal 12 dmg. Exhaust.',up:'16 dmg',dmg:12,upDmg:16,exhaust:true},
  {n:'Obsidian Skin',type:'Skill',rarity:'Common',cost:1,desc:'Gain 7 Block.',up:'Gain 10',blk:7,upBlk:10},
  {n:'Flame Ward',type:'Skill',rarity:'Common',cost:1,desc:'Gain 5 Block. Apply 2 Burn random.',up:'6 Block 3 Burn',blk:5,upBlk:6,burn:2,upBurn:3},
  {n:'Demonic Resilience',type:'Skill',rarity:'Uncommon',cost:1,desc:'Gain 4 Block. Heal 2.',up:'5 Block Heal 3',blk:4,upBlk:5,heal:2,upHeal:3},
  {n:'Brimstone Shield',type:'Skill',rarity:'Rare',cost:1,desc:'Gain 3 Block. +2 Str this combat.',up:'4 Block +3 Str',blk:3,upBlk:4,str:2,upStr:3},
  {n:'Summon Imp',type:'Skill',rarity:'Common',cost:1,desc:'Add Imp Claw to hand. Exhaust.',up:'Imp Claw+',exhaust:true},
  {n:'Infernal Pact',type:'Skill',rarity:'Uncommon',cost:0,desc:'Lose 2 HP. Gain 2 Energy. Add Imp Claw.',up:'Lose 1 HP Add Imp+',lose:2,upLose:1,energy:2},
  {n:'Hellfire Aura',type:'Power',rarity:'Rare',cost:2,desc:'At start deal 3 to ALL.',up:'5 to ALL',dmg:3,upDmg:5},
  {n:'Soul Burn',type:'Skill',rarity:'Uncommon',cost:1,desc:'Apply 8 Burn. Draw 1.',up:'10 Burn',burn:8,upBurn:10,draw:1},
 ]}
};

const COLORLESS=[
 {n:'Flash of Insight',type:'Skill',rarity:'Common',cost:1,desc:'Draw 2.',up:'Draw 3',draw:2,upDraw:3},
 {n:'Iron Will',type:'Skill',rarity:'Common',cost:1,desc:'Gain 8 Block.',up:'11 Block',blk:8,upBlk:11},
 {n:'Quick Slash',type:'Attack',rarity:'Common',cost:1,desc:'Deal 7 dmg.',up:'9 dmg',dmg:7,upDmg:9},
 {n:'Potent Brew',type:'Skill',rarity:'Uncommon',cost:0,desc:'Gain 1 Energy. Exhaust.',up:'Gain 2',energy:1,upEnergy:2,exhaust:true},
];

const CURSES={
 Wound:{n:'Wound',type:'Curse',cost:99,desc:'Unplayable. Ethereal. Exhaust.',unplayable:true,ethereal:true,exhaust:true},
 Dazed:{n:'Dazed',type:'Status',cost:99,desc:'Unplayable. Ethereal. Exhaust.',unplayable:true,ethereal:true,exhaust:true},
 Doubt:{n:'Doubt',type:'Curse',cost:99,desc:'Unplayable. End of turn lose 1 HP.',unplayable:true},
 ImpClaw:{n:'Imp Claw',type:'Attack',cost:0,desc:'Deal 4 dmg. Exhaust.',dmg:4,exhaust:true},
 ImpClawPlus:{n:'Imp Claw+',type:'Attack',cost:0,desc:'Deal 6 dmg. Exhaust.',dmg:6,exhaust:true},
};

const RELICS=[
 {id:'Bloodstone',name:'Bloodstone',desc:'When you lose HP from a card, heal 1.',rarity:'Common'},
 {id:'Cursed Amulet',name:'Cursed Amulet',desc:'Start combat: apply 1 Weak to ALL.',rarity:'Common'},
 {id:'Bleeding Edge',name:'Bleeding Edge',desc:'Start of turn: 1 Bleed to random enemy.',rarity:'Common'},
 {id:'Ashen Armor',name:'Ashen Armor',desc:'End combat heal 6 HP.',rarity:'Common'},
 {id:'Greedy Imp',name:'Greedy Imp',desc:'Enemies drop +10 Gold.',rarity:'Common'},
 {id:'Tome of Dark Rituals',name:'Tome of Dark Rituals',desc:'When you play Power, draw 1.',rarity:'Uncommon'},
 {id:'Eternal Hourglass',name:'Eternal Hourglass',desc:'If hand empty at start, +1 Energy.',rarity:'Uncommon'},
 {id:'Soul Ember',name:'Soul Ember',desc:'When you apply Burn, deal 2 extra.',rarity:'Rare'},
 {id:'Shadow Cloak',name:'Shadow Cloak',desc:'Start combat: 1 Intangible.',rarity:'Rare'},
 {id:'Demon Horns',name:'Demon Horns',desc:'Start combat: add Imp Claw to hand.',rarity:'Class'},
 {id:'Sacrificial Dagger',name:'Sacrificial Dagger',desc:'First HP-cost Attack costs 1 less HP.',rarity:'Class'},
];

const BOSS_RELICS=[
 {id:'Cursed Crown',name:'Cursed Crown',desc:'+1 Energy each turn. Curses cannot be removed.'},
 {id:'Ectoplasm',name:'Ectoplasm',desc:'+1 Energy. Lose all Gold, gain no more.'},
 {id:'Philosopher Stone',name:"Philosopher's Stone",desc:'+1 Energy. Enemies start with 1 Strength.'},
 {id:'Black Blood',name:'Black Blood',desc:'Start combat -10% HP, Attacks heal 1.'},
 {id:'Shadow Amulet',name:'Shadow Amulet',desc:'Start turn 1 Intangible. -15 Max HP.'},
];

const POTIONS=[
 {id:'Blood Vial',name:'Blood Vial',icon:'🧪',desc:'Heal 15 HP.'},
 {id:'Firebomb',name:'Firebomb',icon:'💣',desc:'Deal 10 to ALL.'},
 {id:'Dex Tonic',name:'Dex Tonic',icon:'🍶',desc:'Gain 3 Dex 1 turn.'},
 {id:'Energy Elixir',name:'Energy Elixir',icon:'⚡',desc:'Gain 2 Energy.'},
 {id:'Cursed Brew',name:'Cursed Brew',icon:'☠️',desc:'Apply 3 Vulnerable to ALL.'},
 {id:'Bleeding Flask',name:'Bleeding Flask',icon:'🩸',desc:'Apply 6 Bleed.'},
 {id:'Imp-in-a-Bottle',name:'Imp-in-a-Bottle',icon:'👹',desc:'Add Imp Claw+ to hand.'},
 {id:'Smoke Bomb',name:'Smoke Bomb',icon:'💨',desc:'Escape combat.'},
];

const ENEMIES={
 Skeleton:{hp:22,name:'Skeleton Warrior',acts:[{t:'Attack',v:6},{t:'Attack',v:6},{t:'Defend',v:8}]},
 Zombie:{hp:28,name:'Zombie Brute',acts:[{t:'Attack',v:8},{t:'Debuff',v:'2 Weak'},{t:'Attack',v:8}]},
 Cultist:{hp:20,name:'Dark Cultist',acts:[{t:'Buff',v:'3 Strength'},{t:'Attack',v:5},{t:'Attack',v:5}]},
 Rat:{hp:15,name:'Rat Swarm',acts:[{t:'Multi',v:'2x3'},{t:'Multi',v:'2x3'}]},
 TombGuardian:{hp:42,name:'Tomb Guardian',acts:[{t:'Attack',v:6,block:8},{t:'Multi',v:'3x4'},{t:'Attack',v:12}],elite:true},
 BloodWitch:{hp:36,name:'Blood Witch',acts:[{t:'Attack',v:5,heal:4},{t:'Debuff',v:'Weak+Vuln'},{t:'Attack',v:7,heal:4}],elite:true},
 Wraith:{hp:30,name:'Wraith',acts:[{t:'Attack',v:7,weak:1},{t:'Attack',v:7},{t:'Defend',v:10}]},
 Beast:{hp:38,name:'Corrupted Beast',acts:[{t:'Attack',v:12},{t:'Defend',v:5,selfVuln:1},{t:'Attack',v:12}]},
 Horror:{hp:25,name:'Thorned Horror',acts:[{t:'Multi',v:'3x4'},{t:'Debuff',v:'3 Bleed'},{t:'Attack',v:8}]},
 Lich:{hp:65,name:'Lich King',boss:true,acts:[{t:'Summon'},{t:'Attack',v:8},{t:'Defend',v:12}]},
 Treant:{hp:75,name:'Eldritch Treant',boss:true,acts:[{t:'Attack',v:10,weak:2},{t:'Defend',v:15,regen:2},{t:'Multi',v:'2x8'}]},
 Archdemon:{hp:85,name:'Archdemon',boss:true,acts:[{t:'Summon'},{t:'Attack',v:12,burn:2},{t:'Defend',v:15,str:3}]},
 Flamespawn:{hp:48,name:'Flamespawn',acts:[{t:'Attack',v:9,burn:3},{t:'Defend',v:10,str:2},{t:'Multi',v:'2x8'}],elite:true},
 DreadKnight:{hp:55,name:'Dread Knight',acts:[{t:'Attack',v:12},{t:'Debuff',v:'3 Bleed'},{t:'Defend',v:12,heal:4}],elite:true},
 Hellhound:{hp:26,name:'Hellhound',acts:[{t:'Attack',v:8,burn:2},{t:'Attack',v:8},{t:'Defend',v:9}]},
 ShadowDemon:{hp:30,name:'Shadow Demon',acts:[{t:'Attack',v:7,weak:1},{t:'Attack',v:7},{t:'Buff',v:'Intangible'}]},
 ImpEnemy:{hp:18,name:'Infernal Imp',acts:[{t:'Attack',v:5},{t:'Attack',v:5},{t:'Summon'}]},
};

const FLOORS=[
 {floor:1,type:'Start'},
 {floor:2,type:'Enemy',branch:true},
 {floor:3,type:'Enemy'},
 {floor:4,type:'Event'},
 {floor:5,type:'Shop'},
 {floor:6,type:'Enemy'},
 {floor:7,type:'Rest'},
 {floor:8,type:'Enemy',branch:true},
 {floor:9,type:'Elite',alt:'Enemy'},
 {floor:10,type:'Event'},
 {floor:11,type:'Enemy'},
 {floor:12,type:'Elite',alt:'Enemy'},
 {floor:13,type:'Enemy'},
 {floor:14,type:'Rest'},
 {floor:15,type:'Boss'},
];

let state={
 cls:null, gold:99, maxHp:70, hp:70, deck:[], relics:[], potions:[], act:1, floor:1, visited:new Set(),
 bossRelics:[], mapChoice:null, inCombat:false, combat:null, log:[], nextTurnBuffs:{dex:0,block:0,energy:0}, ectoplasm:false, cursedCrown:false
};

function clone(o){return JSON.parse(JSON.stringify(o))}

function makeCard(name, upgraded=false){
 let def=null;
 for(let k in BASIC) if(k===name) def=BASIC[k];
 if(!def) for(let cls in CLASSES) for(let c of CLASSES[cls].cards) if(c.n===name) def=c;
 if(!def) for(let c of COLORLESS) if(c.n===name) def=c;
 if(!def) for(let k in CURSES) if(CURSES[k].n===name) def=CURSES[k];
 if(!def) def={n:name,type:'Skill',cost:1,desc:'?',rarity:'Common'};
 let card={id:Math.random().toString(36).slice(2,8),name:def.n||name,type:def.type,cost:def.cost,rarity:def.rarity||'Common',desc:def.desc,upDesc:def.up,def:def,upgraded:upgraded};
 if(upgraded && def.upCost!==undefined) card.cost=def.upCost;
 return card;
}

function startingDeck(cls){
 let d=[];
 for(let i=0;i<5;i++) d.push(makeCard('Strike'));
 for(let i=0;i<4;i++) d.push(makeCard('Defend'));
 let sig={BloodReaper:'Sanguine Strike',Shadowmancer:'Shadow Bolt',Hellforged:'Firebolt'}[cls];
 d.push(makeCard(sig));
 return d;
}

function rarityPick(isElite,isBoss){
 if(isBoss) return Math.random()<0.33?'Rare':(Math.random()<0.5?'Uncommon':'Common');
 if(isElite) return Math.random()<0.2?'Rare':(Math.random()<0.375?'Uncommon':'Common');
 const r=Math.random(); if(r<0.6) return 'Common'; if(r<0.9) return 'Uncommon'; return 'Rare';
}
function randomCards(count,isElite,isBoss){
 let pool=CLASSES[state.cls].cards;
 let res=[];
 for(let i=0;i<count;i++){
   let rar=rarityPick(isElite,isBoss);
   let candidates=pool.filter(c=>c.rarity===rar);
   if(candidates.length===0) candidates=pool;
   let pick=candidates[Math.floor(Math.random()*candidates.length)];
   res.push(makeCard(pick.n, false));
 }
 return res;
}

function initRun(cls){
 state.cls=cls; state.gold=99; state.maxHp=70; state.hp=70; state.deck=startingDeck(cls); state.relics=[]; state.potions=[]; state.act=1; state.floor=1; state.visited=new Set(); state.bossRelics=[]; state.ectoplasm=false; state.cursedCrown=false; state.nextTurnBuffs={dex:0,block:0,energy:0};
 render();
}

function renderTop(){
 document.getElementById('hpText').textContent=state.hp+'/'+state.maxHp;
 document.getElementById('hpFill').style.width=(Math.max(0,state.hp/state.maxHp*100))+'%';
 document.getElementById('blockText').textContent=(state.combat?state.combat.block:0)+' Block';
 document.getElementById('goldText').textContent=state.gold;
 document.getElementById('actText').textContent='Act '+state.act;
 document.getElementById('floorText').textContent='Floor '+state.floor;
 document.getElementById('energyText').textContent= state.combat ? state.combat.energy+'/3' : '3/3';
 const rb=document.getElementById('relicBar'); rb.innerHTML='';
 state.relics.forEach(r=>{let s=document.createElement('span');s.className='badge';s.title=r.desc;s.textContent=r.name;rb.appendChild(s)});
 const pb=document.getElementById('potionBar'); pb.innerHTML='';
 for(let i=0;i<3;i++){let p=state.potions[i];let d=document.createElement('div');d.className='potion'+(p?'':' empty');d.textContent=p? p.icon : '+';d.title=p? p.name+': '+p.desc : 'Empty slot'; if(p) d.onclick=()=>usePotion(i); pb.appendChild(d);}
}

function render(){
 renderTop();
 const app=document.getElementById('app');
 if(!state.cls){ app.innerHTML=classSelectHTML(); return; }
 if(state.hp<=0){ app.innerHTML=`<div class="card"><div class="card-hdr"><h2>💀 Run Over</h2></div><div style="padding:24px;text-align:center"><p style="color:var(--muted);margin-bottom:16px">Your soul is consumed by the Abyss.</p><button class="btn btn-blood" onclick="resetRun()">Try Again</button></div></div>`; return;}
 if(state.combat && state.inCombat){ app.innerHTML=combatHTML(); attachCombatHandlers(); return;}
 // map / event / shop / rest / reward
 const floorInfo=FLOORS.find(f=>f.floor===state.floor);
 if(state.floor>15){
   if(state.act<3){ state.act++; state.floor=1; state.visited=new Set(); state.hp=state.maxHp; render(); return;}
   else{ app.innerHTML=`<div class="card" style="padding:32px;text-align:center"><h1 class="decor" style="font-size:36px;color:var(--gold)">VICTORY</h1><p style="margin:12px 0;color:var(--muted)">You have defeated the Archdemon and escaped the Abyss.</p><button class="btn btn-gold" onclick="resetRun()">New Run</button></div>`; return;}
 }
 // check if we need to show current node type screen
 let nodeType=floorInfo.type;
 if(nodeType==='Start'){ app.innerHTML=startHTML(); return;}
 if(nodeType==='Rest'){ app.innerHTML=restHTML(); return;}
 if(nodeType==='Shop'){ app.innerHTML=shopHTML(); return;}
 if(nodeType==='Event'){ app.innerHTML=eventHTML(); return;}
 if(nodeType==='Enemy' || nodeType==='Elite' || nodeType==='Boss'){
   app.innerHTML=preCombatHTML(floorInfo);
   return;
 }
}

function classSelectHTML(){
 return `<div class="card"><div class="card-hdr"><h2>Choose Your Cursed Archetype</h2><span class="badge">Roguelike Deckbuilder • Permadeath</span></div>
 <div style="padding:16px" class="grid3">
  ${Object.entries(CLASSES).map(([k,v])=>`
   <div class="card classCard" onclick="pickClass('${k}')">
    <div style="display:flex;gap:12px;align-items:center;margin-bottom:10px">
     <div class="classIcon" style="color:${v.color};border-color:${v.color}40">${v.icon}</div>
     <div><h3 style="color:${v.color}">${k}</h3><div class="ntype">${k==='BloodReaper'?'Sacrifice • Bleed':k==='Shadowmancer'?'Debuff • Intangible':'Burn • Imps'}</div></div>
    </div>
    <p style="font-size:12px;color:var(--muted);line-height:1.5">${k==='BloodReaper'?'Wield forbidden hemomancy. Lose HP to unleash devastating attacks and bleed foes.':k==='Shadowmancer'?'Master of curses and veils. Weaken enemies, evade with Intangible.':'Forge pacts with infernal fire. Burn enemies and summon imps.'}</p>
    <div style="margin-top:10px;display:flex;gap:6px;flex-wrap:wrap">${v.cards.slice(0,4).map(c=>`<span class="badge">${c.n}</span>`).join('')}</div>
    <div style="margin-top:12px"><span class="btn btn-gold" style="font-size:11px">Choose ${k}</span></div>
   </div>`).join('')}
 </div>
 <div style="padding:12px 16px;border-top:1px solid var(--border);display:flex;gap:12px;flex-wrap:wrap;color:var(--muted);font-size:11px">
  <span>70 HP • 3 Energy • 5 Draw • 99 Gold</span><span>•</span><span>15 Floors per Act • 3 Acts</span>
 </div></div>`;
}
function startHTML(){
 return `<div class="mapWrap">
  <div class="card mapCol"><h3>Act ${state.act} Map</h3><div style="margin-top:10px">${FLOORS.map(f=>`<div class="floor ${f.floor===state.floor?'active':''} ${state.visited.has(f.floor)?'done':''}"><span class="ntype">F${f.floor}</span><span style="font-size:12px">${f.type}</span><span style="margin-left:auto;font-size:10px;color:var(--muted)">${f.type==='Elite'?'★':f.type==='Boss'?'👑':f.type==='Rest'?'⛺':f.type==='Shop'?'🛒':f.type==='Event'?'?': '⚔️'}</span></div>`).join('')}</div><div style="margin-top:12px"><button class="btn btn-gold" onclick="advance()">Begin Climb →</button></div></div>
  <div class="card" style="padding:20px"><h2 class="decor">The Crypts Await</h2><p style="color:var(--muted);margin:10px 0;line-height:1.6">You stand at the threshold, cursed and alone. Choose your path wisely — each floor offers a different fate. Block resets each turn. Intents are predictable. Manage your deck, your HP, and your greed.</p>
  <div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px"><span class="badge">Deck: ${state.deck.length} cards</span><span class="badge">Relics: ${state.relics.length}</span><span class="badge">Max HP 70</span></div>
  <div style="margin-top:18px" class="grid3"><div class="card" style="padding:12px"><b>Energy 3</b><p style="font-size:11px;color:var(--muted)">Gain 3 each turn. Unspent is lost.</p></div><div class="card" style="padding:12px"><b>Draw 5</b><p style="font-size:11px;color:var(--muted)">Shuffle discard when empty. Exhaust removed.</p></div><div class="card" style="padding:12px"><b>Intents</b><p style="font-size:11px;color:var(--muted)">Enemy next move is shown. Plan block.</p></div></div>
  <button class="btn btn-blood" style="margin-top:16px" onclick="advance()">Enter Floor 2 →</button></div>
 </div>`;
}
function preCombatHTML(floorInfo){
 let isElite=floorInfo.type==='Elite', isBoss=floorInfo.type==='Boss';
 let title=isBoss?'Boss Encounter':isElite?'Elite Encounter':'Enemy Encounter';
 let enemiesDesc=isBoss? (state.act===1?'Lich King + minions':state.act===2?'Eldritch Treant':'Archdemon') : isElite?'Tomb Guardian / Blood Witch':'Crypt Denizens';
 return `<div class="card"><div class="card-hdr"><h2>${title}</h2><span class="badge">Floor ${state.floor} • Act ${state.act}</span></div>
 <div style="padding:16px;display:grid;grid-template-columns:1fr 320px;gap:16px">
  <div>
    <div style="height:160px;border-radius:12px;background:linear-gradient(180deg,#1a1a25,#0a0a0f);border:1px solid var(--border);display:grid;place-items:center;font-size:48px">${isBoss?'👑':isElite?'👹':'💀'}<div style="font-size:11px;color:var(--muted);margin-top:8px">${enemiesDesc}</div></div>
    <p style="margin-top:12px;color:var(--muted);font-size:12px;line-height:1.6">Combat is turn-based. You act first. Energy refreshes, you draw 5. Block resets after your turn. Enemy block persists. Watch intents!</p>
    <div style="margin-top:12px;display:flex;gap:8px"><button class="btn btn-blood" onclick="startCombat()">⚔️ Enter Combat</button>${!isBoss?`<button class="btn" onclick="trySmokeEscape()">💨 Smoke Bomb (if owned)</button>`:''}</div>
  </div>
  <div class="card" style="padding:12px"><h3>Floor Choice</h3>
   ${floorInfo.branch?`<p style="font-size:11px;color:var(--muted);margin:6px 0">Branching path — choose enemy group:</p><button class="nodeBtn" style="width:100%;margin-bottom:8px" onclick="setBranch('left')">${state.act===1?'Skeleton Warrior (22 HP)':'Wraith (30 HP)'} — Left</button><button class="nodeBtn" style="width:100%" onclick="setBranch('right')">${state.act===1?'Zombie Brute (28 HP)':'Corrupted Beast (38 HP)'} — Right</button>`:''}
   ${floorInfo.alt?`<p style="font-size:11px;color:var(--muted);margin:8px 0">Elite path is optional:</p><button class="nodeBtn" style="width:100%;margin-bottom:8px" onclick="startCombat(true)">★ Take Elite</button><button class="nodeBtn" style="width:100%" onclick="startCombat(false)">⚔️ Take Safe Enemy</button>`:''}
   ${!floorInfo.branch && !floorInfo.alt?`<p style="font-size:12px;color:var(--muted);margin-top:8px">No branching here. Press Enter Combat.</p>`:''}
   <div style="margin-top:12px;font-size:11px;color:var(--muted)">Deck size ${state.deck.length} • Gold ${state.gold}</div>
  </div>
 </div></div>`;
}

/* Combat */
function buildEnemiesForFloor(){
 let f=FLOORS.find(x=>x.floor===state.floor);
 let act=state.act, isBoss=f.type==='Boss', isElite=f.type==='Elite' && (state.mapChoice!=='safe');
 if(isBoss){
  if(act===1) return [makeEnemy('Lich')];
  if(act===2) return [makeEnemy('Treant')];
  return [makeEnemy('Archdemon')];
 }
 if(isElite){
  if(act===1) return Math.random()<0.5?[makeEnemy('TombGuardian')]:[makeEnemy('BloodWitch')];
  if(act===2) return Math.random()<0.5?[makeEnemy('Treant')]:[makeEnemy('Wraith'),makeEnemy('Beast')];
  return Math.random()<0.5?[makeEnemy('Flamespawn')]:[makeEnemy('DreadKnight')];
 }
 // normal
 if(act===1){
  const table={
   2: state.mapChoice==='right'?[makeEnemy('Zombie')]:[makeEnemy('Skeleton')],
   3:[makeEnemy('Cultist')],
   6:[makeEnemy('Rat')],
   8: state.mapChoice==='right'?[makeEnemy('Zombie')]:[makeEnemy('Skeleton'),makeEnemy('Skeleton')],
   11:[makeEnemy('Cultist'),makeEnemy('Rat')],
   13:[makeEnemy('Cultist'),makeEnemy('Cultist')],
  };
  return table[state.floor]||[makeEnemy('Skeleton')];
 }
 if(act===2){
  if(state.floor===2) return state.mapChoice==='right'?[makeEnemy('Beast')]:[makeEnemy('Wraith')];
  if(state.floor===3) return [makeEnemy('Horror')];
  if(state.floor===6) return [makeEnemy('Wraith'),makeEnemy('Rat')];
  if(state.floor===8) return [makeEnemy('Horror'),makeEnemy('Beast')];
  if(state.floor===11) return [makeEnemy('Wraith'),makeEnemy('Horror')];
  if(state.floor===13) return [makeEnemy('Beast'),makeEnemy('Beast')];
  return [makeEnemy('Wraith')];
 }
 // act3
 if(state.floor===2) return state.mapChoice==='right'?[makeEnemy('ShadowDemon')]:[makeEnemy('Hellhound')];
 if(state.floor===6) return [makeEnemy('ImpEnemy'),makeEnemy('ImpEnemy')];
 if(state.floor===8) return [makeEnemy('Hellhound'),makeEnemy('ShadowDemon')];
 if(state.floor===11) return [makeEnemy('Flamespawn')];
 if(state.floor===13) return [makeEnemy('Hellhound'),makeEnemy('ImpEnemy')];
 return [makeEnemy('Hellhound')];
}
function makeEnemy(key){
 let base=ENEMIES[key];
 let e=clone(base); e.key=key; e.maxHp=e.hp; e.block=0; e.turn=0; e.str=0; e.weak=0; e.vuln=0; e.bleed=0; e.burn=0; e.poison=0; e.intangible=0; e.regen=0; e.alive=true; e.dex=0;
 return e;
}
function startCombat(forceElite=null){
 if(forceElite===false) state.mapChoice='safe';
 if(forceElite===true) state.mapChoice='elite';
 let enemies=buildEnemiesForFloor();
 // apply philosopher stone
 if(state.relics.some(r=>r.id==='Philosopher Stone')||state.bossRelics.includes("Philosopher's Stone")){
   enemies.forEach(en=>en.str+=1);
 }
 let draw=[...state.deck].sort(()=>Math.random()-0.5);
 state.combat={enemies, drawPile:draw, discard:[], hand:[], exhaust:[], energy:3, block:0, hp:state.hp, maxHp:state.maxHp, str:0, dex:0, weak:0, vuln:0, intangible:0, regen:0, bleed:0, burn:0, poison:0, powers:[], turn:1, nextEnergy:0, nextBlock:0, nextDex:0, selected:0, goldReward:0};
 // relics start of combat
 if(state.relics.some(r=>r.id==='Shadow Cloak')) state.combat.intangible+=1;
 if(state.relics.some(r=>r.id==='Cursed Amulet')) enemies.forEach(en=>en.weak+=1);
 if(state.relics.some(r=>r.id==='Demon Horns')){ state.combat.hand.push(makeCard('Imp Claw')); }
 if(state.bossRelics.includes('Black Blood')) state.combat.hp=Math.max(1,state.combat.hp-Math.floor(state.maxHp*0.1));
 if(state.bossRelics.includes('Shadow Amulet')){ state.maxHp=Math.max(1,state.maxHp-15); state.combat.maxHp=state.maxHp; state.combat.intangible+=1; }
 // ectoplasm etc handled elsewhere
 state.inCombat=true; state.log=['Combat started!'];
 // initial draw
 drawCards(5);
 // if bloodstone etc
 render();
}
function drawCards(n){
 let c=state.combat;
 for(let i=0;i<n;i++){
  if(c.drawPile.length===0){
   if(c.discard.length===0) break;
   c.drawPile=[...c.discard].sort(()=>Math.random()-0.5); c.discard=[];
  }
  let card=c.drawPile.pop();
  // ethereal check? if card ethereal and would be discarded handled at end turn
  c.hand.push(card);
  // doubt? no immediate
  if(card.name==='Wound' || card.name==='Dazed'){ /* unplayable */ }
 }
}
function intentText(enemy){
 let act=enemy.acts[enemy.turn % enemy.acts.length];
 if(act.t==='Attack') return `⚔️ Attack ${act.v}`;
 if(act.t==='Multi') return `⚔️ Multi ${act.v}`;
 if(act.t==='Defend') return `🛡️ Block ${act.v}`;
 if(act.t==='Buff') return `✨ Buff ${act.v}`;
 if(act.t==='Debuff') return `💀 Debuff ${act.v}`;
 if(act.t==='Summon') return `👹 Summon`;
 return act.t;
}
function combatHTML(){
 let c=state.combat;
 return `<div class="card"><div class="card-hdr"><h2>Combat — Turn ${c.turn}</h2><div style="display:flex;gap:8px;align-items:center"><span class="badge">Energy ${c.energy}/3</span><span class="badge">Draw ${c.drawPile.length} • Discard ${c.discard.length} • Exhaust ${c.exhaust.length}</span><button class="btn btn-gold" onclick="endTurn()">End Turn →</button></div></div>
 <div style="padding:14px" class="combatArea">
  <div>
    <div class="enemies">${c.enemies.map((e,i)=>`
      <div class="enemyBox ${i===c.selected?'target':''} ${!e.alive?'':''}" onclick="selectEnemy(${i})" style="${!e.alive?'opacity:.35':''}">
        <div style="display:flex;justify-content:space-between"><b style="font-size:12px">${e.name}</b><span style="font-size:11px">${e.alive?e.hp+'/'+e.maxHp:'DEFEATED'}</span></div>
        <div class="hpbar" style="width:100%;margin:6px 0"><i style="width:${Math.max(0,e.hp/e.maxHp*100)}%"></i></div>
        <div style="display:flex;gap:4px;flex-wrap:wrap;font-size:10px"><span class="badge">🛡️ ${e.block}</span>${e.str?`<span class="badge" style="color:#f87171">Str ${e.str}</span>`:''}${e.weak?`<span class="badge">Weak ${e.weak}</span>`:''}${e.vuln?`<span class="badge">Vuln ${e.vuln}</span>`:''}${e.bleed?`<span class="badge">Bleed ${e.bleed}</span>`:''}${e.burn?`<span class="badge">Burn ${e.burn}</span>`:''}${e.poison?`<span class="badge">Poison ${e.poison}</span>`:''}${e.intangible?`<span class="badge">Intangible ${e.intangible}</span>`:''}</div>
        ${e.alive?`<div class="intent">${intentText(e)}</div>`:''}
      </div>`).join('')}</div>
    <div style="margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
      <span class="badge">You: ❤ ${c.hp}/${c.maxHp} 🛡️ ${c.block}</span>
      ${c.str?`<span class="badge">Str ${c.str}</span>`:''}${c.dex?`<span class="badge">Dex ${c.dex}</span>`:''}${c.weak?`<span class="badge">Weak ${c.weak}</span>`:''}${c.vuln?`<span class="badge">Vuln ${c.vuln}</span>`:''}${c.intangible?`<span class="badge">Intangible ${c.intangible}</span>`:''}${c.regen?`<span class="badge">Regen ${c.regen}</span>`:''}${c.bleed?`<span class="badge">Bleed ${c.bleed}</span>`:''}${c.burn?`<span class="badge">Burn ${c.burn}</span>`:''}${c.poison?`<span class="badge">Poison ${c.poison}</span>`:''}
    </div>
    <div class="log" style="margin-top:10px">${state.log.slice(-8).map(l=>`<div>• ${l}</div>`).join('')}</div>
    <h3 style="margin:14px 0 8px">Hand — click to play (target selected enemy)</h3>
    <div class="hand">${c.hand.length? c.hand.map((card,idx)=>handCardHTML(card,idx)).join('') : '<span style="color:var(--muted);font-size:12px">No cards in hand.</span>'}</div>
    <div style="margin-top:10px;display:flex;gap:8px"><button class="btn" onclick="endTurn()">End Turn (E)</button><button class="btn" onclick="viewDeck()">View Deck</button></div>
  </div>
  <div class="card" style="padding:12px;height:fit-content">
    <h3>Powers</h3><div style="font-size:11px;color:var(--muted);margin:6px 0">${c.powers.length? c.powers.map(p=>`<span class="badge">${p}</span>`).join(' ') : 'No powers active.'}</div>
    <h3 style="margin-top:12px">Potions</h3><div style="display:flex;gap:6px;margin-top:6px">${state.potions.map((p,i)=>`<div class="potion" title="${p.name}" onclick="usePotion(${i})">${p.icon}</div>`).join('') || '<span style="font-size:11px;color:var(--muted)">No potions</span>'}</div>
    <h3 style="margin-top:12px">Tips</h3><ul style="font-size:11px;color:var(--muted);margin:6px 0 0 16px;line-height:1.6"><li>Block resets at start of your turn.</li><li>Bleed/Burn halve each turn (round down).</li><li>Intangible reduces all damage to 1.</li><li>X-cost Vital Surge = missing HP.</li></ul>
  </div>
 </div></div>`;
}
function handCardHTML(card,idx){
 let def=card.def;
 let cost=card.cost==='X'? 'X' : card.cost;
 let unplayable=def.unplayable || (cost!=='X' && cost>state.combat.energy);
 let rarityCls=def.rarity==='Rare'?'rarityRare':def.rarity==='Uncommon'?'rarityUncommon':'rarityCommon';
 let desc=card.upgraded? (def.up||def.desc) : def.desc;
 return `<div class="gcard ${rarityCls} ${unplayable?'unplayable':''}" onclick="playCard(${idx})">
  <div style="display:flex;justify-content:space-between;align-items:center"><span class="cost">${cost===99?'—':cost}</span><span class="type">${card.type}</span></div>
  <h4>${card.name}${card.upgraded?' +':''}</h4>
  <p>${desc}</p>
  <div style="margin-top:auto;display:flex;gap:4px;flex-wrap:wrap"><span class="badge" style="font-size:9px">${def.rarity||'Common'}</span>${def.exhaust?'<span class="badge" style="font-size:9px">Exhaust</span>':''}</div>
 </div>`;
}
function selectEnemy(i){ if(!state.combat) return; state.combat.selected=i; render(); }
function attachCombatHandlers(){}
window.selectEnemy=selectEnemy;
window.playCard=playCard;
window.endTurn=endTurn;
window.viewDeck=()=>{alert('Deck: '+state.deck.map(c=>c.name+(c.upgraded?'+':'')).join(', '))}

function calcDamage(base, attacker, defender){
 let dmg=base + (attacker.str||0);
 if(attacker.weak) dmg=Math.floor(dmg*0.75);
 if(defender.vuln) dmg=Math.floor(dmg*1.5);
 if(defender.intangible) dmg=Math.min(dmg,1);
 return Math.max(0,dmg);
}
function applyDamage(target, amount, isHpLoss=false){
 if(target.intangible) amount=Math.min(amount,1);
 if(!isHpLoss){
   if(target.block>=amount){ target.block-=amount; amount=0; } else { amount-=target.block; target.block=0; }
 }
 target.hp-=amount;
 if(target.hp<0) target.hp=0;
 return amount;
}
function heal(target, amt){ target.hp=Math.min(target.maxHp, target.hp+amt); }

function playCard(idx){
 let c=state.combat; let card=c.hand[idx]; if(!card) return;
 let def=card.def; if(def.unplayable){ state.log.push(`${card.name} is unplayable.`); render(); return;}
 let cost=card.cost; let pay=0;
 if(cost==='X'){ pay=c.energy; // Vital Surge X
 } else if(cost===99) return;
 else pay=cost;
 if(pay>c.energy){ state.log.push('Not enough Energy.'); render(); return;}
 // pay energy
 c.energy-=pay;
 // remove from hand
 c.hand.splice(idx,1);
 // handle exhaust vs discard later
 let usedExhaust=!!def.exhaust;
 let isPower=card.type==='Power';
 // effects
 let target=c.enemies[c.selected] && c.enemies[c.selected].alive ? c.enemies[c.selected] : c.enemies.find(e=>e.alive);
 let upgraded=card.upgraded;
 // helper to trigger blood pact etc on HP loss
 function loseHp(amt){
   if(c.intangible) amt=1;
   // sacrificial dagger first HP cost reduction
   if(state.relics.some(r=>r.id==='Sacrificial Dagger') && !c.daggerUsed){ amt=Math.max(0,amt-1); c.daggerUsed=true; }
   applyDamage(c, amt, true);
   state.hp=c.hp;
   // bloodstone
   if(state.relics.some(r=>r.id==='Bloodstone')){ heal(c,1); state.hp=c.hp; }
   // blood pact strength
   if(c.powers.includes('Blood Pact')||c.powers.includes('Blood Pact+')){
     let gain=c.powers.includes('Blood Pact+')?2:1;
     c.str+=gain; state.log.push(`Blood Pact +${gain} Strength`);
   }
 }
 // execute card specifics by name
 let name=card.name.replace('+','');
 switch(name){
  case 'Strike': {let dmg=upgraded?def.upDmg:def.dmg; let d=calcDamage(dmg,c,target); applyDamage(target,d); state.log.push(`Strike deals ${d}`); break;}
  case 'Defend': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk + (c.dex||0); state.log.push(`Gain ${blk} Block`); break;}
  case 'Sanguine Strike': {let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); let h=upgraded?def.upHeal:def.heal; heal(c,h); state.hp=c.hp; state.log.push(`Sanguine Strike ${d} dmg heal ${h}`); break;}
  case 'Hemorrhage': {let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); let b=upgraded?def.upBleed:def.bleed; target.bleed=(target.bleed||0)+b; if(state.relics.some(r=>r.id==='Soul Ember')){/* not burn */} state.log.push(`Hemorrhage ${d} +${b} Bleed`); break;}
  case 'Bloodletting': {let lose=upgraded?def.upLose:def.lose; loseHp(lose); let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); state.log.push(`Bloodletting lose ${lose} deal ${d}`); break;}
  case 'Crimson Eruption': {let lose=def.lose; loseHp(lose); let dmg=upgraded?def.upDmg:def.dmg; c.enemies.forEach(e=>{if(e.alive){let d=calcDamage(dmg,c,e); applyDamage(e,d);}}); state.log.push(`Crimson Eruption ${dmg} to ALL`); break;}
  case 'Crimson Shield': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); loseHp(def.lose); state.log.push(`Crimson Shield +${blk} Block`); break;}
  case 'Blood Ward': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); c.regen+=(upgraded?def.upRegen:def.regen); state.log.push(`Blood Ward +${blk} Block +Regen`); break;}
  case 'Sacrificial Barrier': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); loseHp(def.lose); state.log.push(`Sacrificial Barrier +${blk}`); break;}
  case 'Vital Surge': {let missing=c.maxHp - c.hp; let blk=missing + (upgraded?3:0); c.block+=blk; state.log.push(`Vital Surge +${blk} Block`); usedExhaust=true; break;}
  case 'Life Tap': {let lose=def.lose; loseHp(lose); let dr=upgraded?def.upDraw:def.draw; drawCards(dr); state.log.push(`Life Tap draw ${dr} lose ${lose}`); break;}
  case 'Rend Flesh': {let b=upgraded?def.upBleed:def.bleed; c.enemies.forEach(e=>{if(e.alive) e.bleed=(e.bleed||0)+b}); loseHp(def.lose); state.log.push(`Rend Flesh ${b} Bleed to ALL`); break;}
  case 'Soul Siphon': {c.powers.push(upgraded?'Soul Siphon+':'Soul Siphon'); state.log.push('Soul Siphon Power active'); if(state.relics.some(r=>r.id==='Tome of Dark Rituals')){drawCards(1);} break;}
  case 'Blood Pact': {c.powers.push(upgraded?'Blood Pact+':'Blood Pact'); state.log.push('Blood Pact Power'); if(state.relics.some(r=>r.id==='Tome of Dark Rituals')) drawCards(1); break;}
  case 'Shadow Bolt': {let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); target.weak=(target.weak||0)+(upgraded?def.upWeak:def.weak); state.log.push(`Shadow Bolt ${d} +Weak`); break;}
  case 'Grasp of Despair': {let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); target.vuln=(target.vuln||0)+(upgraded?def.upVuln:def.vuln); state.log.push(`Grasp ${d} +Vuln`); break;}
  case 'Nightmare Lash': {let base=upgraded?def.upDmg:def.dmg; let d=calcDamage(base,c,target); applyDamage(target,d); let extra=0; if(target.weak>0){ extra=calcDamage(base,c,target); applyDamage(target,extra);} state.log.push(`Nightmare Lash ${d}${extra?`+${extra}`:''}`); break;}
  case 'Eternal Darkness': {let dmg=upgraded?def.upDmg:def.dmg; for(let k=0;k<3;k++){let e=c.enemies.filter(x=>x.alive); if(!e.length) break; let t=e[Math.floor(Math.random()*e.length)]; let d=calcDamage(dmg,c,t); applyDamage(t,d);} state.log.push(`Eternal Darkness 3 hits`); break;}
  case 'Cloak of Shadows': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); drawCards(1); state.log.push(`Cloak +${blk} Block draw1`); break;}
  case 'Haunting Mist': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); let w=def.weak; c.enemies.forEach(e=>{if(e.alive) e.weak=(e.weak||0)+w}); state.log.push(`Haunting Mist +${blk} +Weak ALL`); break;}
  case 'Phantom Armor': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); c.nextDex=(c.nextDex||0)+(upgraded?def.upDexNext:def.dexNext); state.log.push(`Phantom Armor +${blk} next +Dex`); break;}
  case 'Whispering Shroud': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); c.nextBlock=(c.nextBlock||0)+(upgraded?def.upNextBlk:def.nextBlk); state.log.push(`Whispering Shroud +${blk} next +${c.nextBlock}`); break;}
  case 'Shadowform': {let ig=upgraded?def.upIntangible:def.intangible; c.intangible+=ig; state.log.push(`Shadowform +${ig} Intangible`); usedExhaust=true; break;}
  case 'Curse of Misfortune': {target.vuln=(target.vuln||0)+(upgraded?def.upVuln:def.vuln); let ms=upgraded?def.upMinusStr:def.minusStr; target.str=Math.max(-99,(target.str||0)-ms); target._minusStrDur=1; state.log.push(`Curse -${ms} Str +Vuln`); break;}
  case 'Dark Ritual': {c.energy+=2; let cardToAdd=makeCard('Dazed'); if(upgraded) c.discard.push(cardToAdd); else c.drawPile.splice(Math.floor(Math.random()* (c.drawPile.length+1)),0,cardToAdd); state.log.push('Dark Ritual +2 Energy Dazed shuffled'); break;}
  case 'Umbral Mastery': {c.powers.push(upgraded?'Umbral Mastery+':'Umbral Mastery'); state.log.push('Umbral Mastery Power'); if(state.relics.some(r=>r.id==='Tome of Dark Rituals')) drawCards(1); break;}
  case 'Firebolt': {let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); let b=upgraded?def.upBurn:def.burn; target.burn=(target.burn||0)+b; if(state.relics.some(r=>r.id==='Soul Ember')){let e=2; let dd=calcDamage(e,c,target); applyDamage(target,dd); state.log.push(`Soul Ember +${e}`);} state.log.push(`Firebolt ${d} +${b} Burn`); break;}
  case 'Hellfire Blast': {let dmg=upgraded?def.upDmg:def.dmg; c.enemies.forEach(e=>{if(e.alive){let d=calcDamage(dmg,c,e); applyDamage(e,d);}}); state.log.push(`Hellfire Blast ${dmg} ALL`); break;}
  case 'Searing Strike': {let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); c.nextEnergy=(c.nextEnergy||0)+1; state.log.push(`Searing Strike ${d} next +1 Energy`); break;}
  case 'Immolate': {let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); state.log.push(`Immolate ${d}`); usedExhaust=true; break;}
  case 'Obsidian Skin': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); state.log.push(`Obsidian Skin +${blk}`); break;}
  case 'Flame Ward': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); let b=upgraded?def.upBurn:def.burn; let alive=c.enemies.filter(e=>e.alive); if(alive.length){let t=alive[Math.floor(Math.random()*alive.length)]; t.burn=(t.burn||0)+b; if(state.relics.some(r=>r.id==='Soul Ember')) applyDamage(t,2);} state.log.push(`Flame Ward +${blk} +Burn`); break;}
  case 'Demonic Resilience': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); let h=upgraded?def.upHeal:def.heal; heal(c,h); state.hp=c.hp; state.log.push(`Demonic Resilience +${blk} heal ${h}`); break;}
  case 'Brimstone Shield': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); let s=upgraded?def.upStr:def.str; c.str+=s; state.log.push(`Brimstone +${blk} +${s} Str`); break;}
  case 'Summon Imp': {let imp=makeCard(upgraded?'Imp Claw+':'Imp Claw'); c.hand.push(imp); state.log.push(`Summon Imp ${imp.name}`); usedExhaust=true; break;}
  case 'Infernal Pact': {let lose=upgraded?def.upLose:def.lose; loseHp(lose); c.energy+=2; let imp=makeCard(upgraded?'Imp Claw+':'Imp Claw'); c.hand.push(imp); state.log.push(`Infernal Pact +2 Energy Imp`); break;}
  case 'Hellfire Aura': {c.powers.push(upgraded?'Hellfire Aura+':'Hellfire Aura'); state.log.push('Hellfire Aura Power'); if(state.relics.some(r=>r.id==='Tome of Dark Rituals')) drawCards(1); break;}
  case 'Soul Burn': {let b=upgraded?def.upBurn:def.burn; target.burn=(target.burn||0)+b; if(state.relics.some(r=>r.id==='Soul Ember')) applyDamage(target,2); drawCards(1); state.log.push(`Soul Burn +${b}`); break;}
  case 'Flash of Insight': {let dr=upgraded?def.upDraw:def.draw; drawCards(dr); state.log.push(`Flash draw ${dr}`); break;}
  case 'Iron Will': {let blk=upgraded?def.upBlk:def.blk; c.block+=blk+(c.dex||0); break;}
  case 'Quick Slash': {let d=calcDamage(upgraded?def.upDmg:def.dmg,c,target); applyDamage(target,d); break;}
  case 'Potent Brew': {let en=upgraded?def.upEnergy:def.energy; c.energy+=en; usedExhaust=true; break;}
  case 'Imp Claw': case 'Imp Claw+': {let d=calcDamage(def.dmg,c,target); applyDamage(target,d); usedExhaust=true; break;}
  default: state.log.push(`${card.name} played`);
 }
 if(isPower){
   // powers go to powers zone, not discard/exhaust
 } else if(usedExhaust){
   c.exhaust.push(card);
 } else {
   // check ethereal? no, normal discard after play immediately? Actually powers not discarded, others go to discard after play? In STS powers exhaust, attacks/skills go to discard immediately. We'll put to discard now but also treat endTurn discard.
   c.discard.push(card);
 }
 // check enemies dead
 checkVictory();
 render();
}
function checkVictory(){
 let c=state.combat; if(!c) return;
 let alive=c.enemies.filter(e=>e.alive && e.hp>0);
 // mark dead
 c.enemies.forEach(e=>{if(e.hp<=0) e.alive=false;});
 if(alive.length===0 || c.enemies.every(e=>!e.alive)){
   winCombat();
 }
}
function winCombat(){
 let c=state.combat;
 state.hp=c.hp; if(state.hp<=0) state.hp=1;
 state.inCombat=false;
 let isElite=FLOORS.find(f=>f.floor===state.floor).type==='Elite';
 let isBoss=FLOORS.find(f=>f.floor===state.floor).type==='Boss';
 let gold=isBoss?50:isElite? 25+Math.floor(Math.random()*11) : 10+Math.floor(Math.random()*11);
 if(state.relics.some(r=>r.id==='Greedy Imp')) gold+=10;
 if(state.ectoplasm) gold=0;
 if(!state.ectoplasm) state.gold+=gold;
 if(state.relics.some(r=>r.id==='Ashen Armor')){ healPlayer(6); }
 // store reward for next screen
 state.pendingReward={gold, isElite, isBoss, cards: isBoss? randomCards(3,false,true) : randomCards(3,isElite,false), potion: Math.random() < (isBoss?1:(isElite?0.7:0.4)) ? POTIONS[Math.floor(Math.random()*POTIONS.length)] : null, relic: isElite? RELICS[Math.floor(Math.random()*RELICS.length)] : null, bossChoices: isBoss? shuffle([...BOSS_RELICS]).slice(0,3): null };
 state.combat=null;
 state.floor++; state.visited.add(state.floor-1);
 // full heal after boss
 if(isBoss){ state.hp=state.maxHp; }
 renderReward();
}
function healPlayer(amt){ state.hp=Math.min(state.maxHp, state.hp+amt); }

function endTurn(){
 let c=state.combat; if(!c) return;
 // end of player turn: Doubt damage, regen? regen at end? spec says at end heal then reduce. We'll do at start of turn.
 // Doubt
 let doubts=c.hand.filter(cd=>cd.name==='Doubt').length + c.discard.filter(cd=>cd.name==='Doubt').length; // actually doubts in deck etc but spec: if in hand at end lose 1 per Doubt? We'll count hand only
 let doubtInHand=c.hand.filter(x=>x.name==='Doubt').length;
 if(doubtInHand){ applyDamage(c, doubtInHand, true); state.hp=c.hp; state.log.push(`Doubt -${doubtInHand} HP`); }
 // discard hand to discard (except ethereal/exhaust)
 let toDiscard=[...c.hand];
 toDiscard.forEach(card=>{
   if(card.def.ethereal){ c.exhaust.push(card); } else if(card.def.exhaust){ c.exhaust.push(card); } else if(card.type==='Power'){ /* stays? powers already removed */ } else c.discard.push(card);
 });
 c.hand=[];
 c.block=0;
 // enemy turn
 enemyTurn();
 if(!state.inCombat) return;
 // start of player turn
 c.turn++; c.energy=3 + (c.nextEnergy||0); c.nextEnergy=0;
 if(c.nextBlock){ c.block+=c.nextBlock; c.nextBlock=0; }
 if(c.nextDex){ c.dex+=c.nextDex; c.nextDex=0; }
 // status ticks for player
 if(c.regen){ heal(c,c.regen); state.hp=c.hp; c.regen=Math.max(0,c.regen-1); state.log.push(`Regen heal ${c.regen+1}`); }
 if(c.bleed){ applyDamage(c,c.bleed,true); state.hp=c.hp; c.bleed=Math.floor(c.bleed/2); state.log.push(`Bleed -${c.bleed}`); }
 if(c.burn){ /* burn at end of turn per spec, but we handle at end of player turn? We'll apply now as start */ }
 if(c.poison){ applyDamage(c,c.poison,true); state.hp=c.hp; c.poison=Math.max(0,c.poison-1); }
 if(c.weak) c.weak=Math.max(0,c.weak-1);
 if(c.vuln) c.vuln=Math.max(0,c.vuln-1);
 if(c.intangible) c.intangible=Math.max(0,c.intangible-1);
 // bleed/burn halving for enemies at start of their turn handled in enemyTurn, poison etc
 // powers at start
 if(c.powers.includes('Soul Siphon')||c.powers.includes('Soul Siphon+')){ c.energy+=1; applyDamage(c,1,true); state.hp=c.hp; if(state.relics.some(r=>r.id==='Bloodstone')){heal(c,1);state.hp=c.hp} state.log.push('Soul Siphon +1 Energy -1 HP'); }
 if(c.powers.includes('Umbral Mastery')||c.powers.includes('Umbral Mastery+')){ let amt=c.powers.includes('Umbral Mastery+')?2:1; c.enemies.forEach(e=>{if(e.alive) e.weak=(e.weak||0)+amt}); state.log.push(`Umbral Mastery +${amt} Weak ALL`); }
 if(c.powers.includes('Hellfire Aura')||c.powers.includes('Hellfire Aura+')){ let dmg=c.powers.includes('Hellfire Aura+')?5:3; c.enemies.forEach(e=>{if(e.alive){let d=calcDamage(dmg,c,e); applyDamage(e,d);}}); state.log.push(`Hellfire Aura ${dmg} to ALL`); }
 if(c.powers.includes('Blood Pact') ) {} // handled
 // relic start of turn
 if(state.relics.some(r=>r.id==='Bleeding Edge')){ let alive=c.enemies.filter(e=>e.alive); if(alive.length){let e=alive[Math.floor(Math.random()*alive.length)]; e.bleed=(e.bleed||0)+1; state.log.push('Bleeding Edge +1 Bleed');}}
 if(state.relics.some(r=>r.id==='Eternal Hourglass') && c.hand.length===0){ c.energy+=1; state.log.push('Hourglass +1 Energy');}
 if(state.bossRelics.includes('Cursed Crown')) c.energy+=1;
 if(state.bossRelics.includes('Ectoplasm')||state.bossRelics.includes("Philosopher's Stone")){ /* +1 already */ if(state.bossRelics.includes("Philosopher's Stone")||state.bossRelics.includes('Ectoplasm')||state.bossRelics.includes('Cursed Crown')) c.energy+=1; }
 if(state.bossRelics.includes('Black Blood')){} // heal on attack handled via playCard? add hook: we heal on attack play already? Need to add: if Black Blood, when play Attack heal 1 - we handle in playCard after damage
 // black blood heal: handled separately in playCard if needed
 // check hp
 if(c.hp<=0){ state.hp=0; state.inCombat=false; state.combat=null; render(); return;}
 // bleed for enemies at start handled? We'll apply burn/poison? Actually bleed at start of affected's turn, burn at end. We'll apply bleed now for player already done. For enemies bleed is applied at start of enemy turn (we do in enemyTurn start). So after powers, draw
 drawCards(5);
 // ethereal? already
 render();
 if(c.enemies.every(e=>!e.alive)) winCombat();
}

function enemyTurn(){
 let c=state.combat; if(!c) return;
 // for each enemy
 for(let e of c.enemies){
  if(!e.alive) continue;
  // start of enemy turn: bleed, poison, regen
  if(e.bleed){ applyDamage(e,e.bleed,true); e.bleed=Math.floor(e.bleed/2); state.log.push(`${e.name} Bleed ${e.bleed}`); if(e.hp<=0){e.alive=false; continue;}}
  if(e.poison){ applyDamage(e,e.poison,true); e.poison=Math.max(0,e.poison-1); if(e.hp<=0){e.alive=false; continue;}}
  if(e.regen){ heal(e,e.regen); e.regen=Math.max(0,e.regen-1); }
  if(e.weak) e.weak=Math.max(0,e.weak-1);
  if(e.vuln) e.vuln=Math.max(0,e.vuln-1);
  if(e.intangible) e.intangible=Math.max(0,e.intangible-1);
  if(!e.alive) continue;
  let act=e.acts[e.turn % e.acts.length];
  // special boss summon logic
  if(act.t==='Summon'){
    if(e.name==='Lich King' || e.name==='Archdemon'){
      let count=c.enemies.filter(x=>x.alive).length;
      if(count>=3){ // skip summon
        let d=calcDamage(8,e,c); applyDamage(c,d); state.hp=c.hp; state.log.push(`${e.name} attacks ${d} (summon blocked)`); }
      else {
        let minion=e.name==='Lich King'?makeEnemy('Skeleton'):makeEnemy('Hellhound');
        minion.hp=15; minion.maxHp=15; c.enemies.push(minion); state.log.push(`${e.name} summons ${minion.name}`);
      }
    } else if(e.name==='Infernal Imp'){
      let imp=makeEnemy('ImpEnemy'); imp.hp=18; c.enemies.push(imp); state.log.push(`Imp summons another Imp`);
    } else {
      state.log.push(`${e.name} summons`);
    }
  } else if(act.t==='Attack'){
    let d=calcDamage(act.v,e,c); applyDamage(c,d); state.hp=c.hp; state.log.push(`${e.name} hits ${d}`);
    if(act.burn) {c.burn=(c.burn||0)+act.burn; state.log.push(`+${act.burn} Burn`);}
    if(act.weak) c.weak=(c.weak||0)+act.weak;
    if(act.heal) heal(e,act.heal);
  } else if(act.t==='Multi'){
    let [hits,dmg]=act.v.split('x').map(Number); for(let k=0;k<hits;k++){let d=calcDamage(dmg,e,c); applyDamage(c,d); state.hp=c.hp; state.log.push(`${e.name} multi ${d}`); if(c.hp<=0) break;}
  } else if(act.t==='Defend'){
    e.block+=act.v; if(act.str) e.str+=act.str; if(act.heal) heal(e,act.heal); if(act.regen) e.regen+=act.regen; state.log.push(`${e.name} defends +${act.v}`);
  } else if(act.t==='Buff'){
    if(act.v.includes('Strength')){ let v=parseInt(act.v); e.str+=v; state.log.push(`${e.name} +${v} Str`); }
    else if(act.v==='Intangible'){ e.intangible+=1; }
    else { e.block+=4; }
  } else if(act.t==='Debuff'){
    if(act.v==='2 Weak'){ c.weak=(c.weak||0)+2; }
    else if(act.v==='Weak+Vuln'){ c.weak+=1; c.vuln+=1; }
    else if(act.v==='3 Bleed'){ c.bleed=(c.bleed||0)+3; }
    else if(act.v.includes('Poison')){ c.poison=(c.poison||0)+2; }
    else if(act.v==='3 Burn'){ c.burn=(c.burn||0)+3; }
  }
  // burn at end of enemy turn
  if(e.burn){ applyDamage(e,e.burn,true); e.burn=Math.floor(e.burn/2); state.log.push(`${e.name} Burn ${e.burn}`); if(e.hp<=0) e.alive=false; }
  e.turn++;
  if(c.hp<=0){ state.hp=0; state.inCombat=false; state.combat=null; state.log.push('You died...'); render(); return; }
 }
 // after enemies, check player burn at end of enemy turn? Actually burn at end of affected's turn. For player, burn should tick at end of player turn? Spec: Burn at end of affected entity's turn. So player burn ticks at end of player turn, enemy burn at end of enemy turn (done). Player burn tick should be at endTurn start? We'll apply after enemyTurn for player burn:
 let pb=c.burn||0;
 if(pb){ applyDamage(c,pb,true); state.hp=c.hp; c.burn=Math.floor(pb/2); state.log.push(`Burn -${pb}`); if(c.hp<=0){state.hp=0; state.inCombat=false; state.combat=null; render(); return;}}
 // remove dead
 c.enemies.forEach(e=>{if(e.hp<=0) e.alive=false;});
 checkVictory();
}

/* Reward / Shop / Rest / Event */
function renderReward(){
 const r=state.pendingReward;
 const app=document.getElementById('app');
 let html=`<div class="card"><div class="card-hdr"><h2>Rewards</h2><span class="badge">+${r.gold} Gold</span></div><div style="padding:16px">
  <p style="color:var(--muted);font-size:12px">Choose 1 card (or Skip). ${r.potion?`Potion: ${r.potion.name}`:''} ${r.relic?`• Relic: ${r.relic.name}`:''}</p>
  <div class="hand" style="margin-top:12px">${r.cards.map((c,i)=>`<div class="gcard" onclick="pickRewardCard(${i})"><div style="display:flex;justify-content:space-between"><span class="cost">${c.cost}</span><span class="type">${c.type}</span></div><h4>${c.name}</h4><p>${c.desc}</p><span class="badge">${c.rarity}</span></div>`).join('')}<div class="gcard" onclick="pickRewardCard(-1)" style="display:grid;place-items:center"><b>Skip</b></div></div>
  ${r.potion?`<div style="margin-top:12px"><button class="btn btn-gold" onclick="takePotionReward()">Take ${r.potion.icon} ${r.potion.name} — ${r.potion.desc}</button> ${state.potions.length>=3?'<span style="color:#f87171;font-size:11px">Potion slots full (3 max) — will be lost if skipped</span>':''}</div>`:''}
  ${r.relic?`<div style="margin-top:12px" class="card" style="padding:10px"><b>Relic found:</b> ${r.relic.name} — ${r.relic.desc} <button class="btn btn-gold" onclick="takeRelicReward()" style="margin-left:8px">Take Relic</button></div>`:''}
  ${r.bossChoices?`<div style="margin-top:12px"><h3>Choose Boss Relic</h3><div class="grid3" style="margin-top:8px">${r.bossChoices.map((b,i)=>`<div class="card" style="padding:12px"><b>${b.name}</b><p style="font-size:11px;color:var(--muted);margin:6px 0">${b.desc}</p><button class="btn btn-gold" onclick="pickBossRelic(${i})">Choose</button></div>`).join('')}</div></div>`:''}
  <div style="margin-top:16px"><button class="btn" onclick="continueAfterReward()">Continue →</button></div>
 </div></div>`;
 app.innerHTML=html; renderTop();
}
window.pickRewardCard=(i)=>{
 if(i>=0){ state.deck.push(state.pendingReward.cards[i]); state.log.push(`Added ${state.pendingReward.cards[i].name}`); }
 document.querySelectorAll('.hand .gcard').forEach(el=>el.style.opacity='.4');
 renderReward();
};
window.takePotionReward=()=>{
 if(state.potions.length<3){ state.potions.push(state.pendingReward.potion); state.pendingReward.potion=null; renderReward(); } else alert('Potion slots full!');
};
window.takeRelicReward=()=>{
 state.relics.push(state.pendingReward.relic); state.pendingReward.relic=null; renderReward();
};
window.pickBossRelic=(i)=>{
 let choice=state.pendingReward.bossChoices[i];
 state.bossRelics.push(choice.name);
 if(choice.name==='Cursed Crown') state.cursedCrown=true;
 if(choice.name==='Ectoplasm'){ state.ectoplasm=true; state.gold=0; }
 if(choice.name==="Philosopher's Stone"){ state.bossRelics.push("Philosopher's Stone"); }
 if(choice.name==='Shadow Amulet'){ state.maxHp=Math.max(1,state.maxHp-15); state.hp=Math.min(state.hp,state.maxHp); }
 state.pendingReward.bossChoices=null; renderReward();
};
window.continueAfterReward=()=>{
 state.pendingReward=null; render();
};
function shopHTML(){
 if(!state.shop) {
   let clsCards=randomCards(3,false,false);
   let colorless=COLORLESS[Math.floor(Math.random()*COLORLESS.length)];
   let colCard=makeCard(colorless.n);
   let relics=[RELICS[Math.floor(Math.random()*RELICS.length)],RELICS[Math.floor(Math.random()*RELICS.length)]];
   let pots=[POTIONS[Math.floor(Math.random()*POTIONS.length)],POTIONS[Math.floor(Math.random()*POTIONS.length)],POTIONS[Math.floor(Math.random()*POTIONS.length)]];
   state.shop={clsCards,colCard,relics,pots,removalUsed:false};
 }
 let s=state.shop;
 return `<div class="card"><div class="card-hdr"><h2>🛒 Shop — Floor ${state.floor}</h2><span class="badge">Gold ${state.gold}</span></div>
 <div style="padding:16px">
  <div class="shopGrid">${s.clsCards.map((c,i)=>`<div class="card" style="padding:10px"><div style="display:flex;justify-content:space-between"><span class="cost">${c.cost}</span><span class="type">${c.rarity}</span></div><h4>${c.name}</h4><p style="font-size:11px;color:var(--muted)">${c.desc}</p><div style="margin-top:8px;display:flex;justify-content:space-between;align-items:center"><span class="badge">${c.rarity==='Rare'?150:c.rarity==='Uncommon'?75:50}g</span><button class="btn btn-gold" onclick="buyCard(${i})">Buy</button></div></div>`).join('')}
   <div class="card" style="padding:10px;border-color:var(--gold)"><h4>${s.colCard.name} <span style="font-size:10px;color:var(--gold)">Colorless</span></h4><p style="font-size:11px;color:var(--muted)">${s.colCard.desc}</p><div style="margin-top:8px;display:flex;justify-content:space-between"><span class="badge">60g</span><button class="btn btn-gold" onclick="buyColorless()">Buy</button></div></div>
  </div>
  <div style="margin-top:14px" class="grid3">
   ${s.relics.map((r,i)=>`<div class="card" style="padding:12px"><b>${r.name}</b><p style="font-size:11px;color:var(--muted)">${r.desc}</p><div style="margin-top:8px;display:flex;justify-content:space-between"><span class="badge">150g</span><button class="btn btn-gold" onclick="buyRelic(${i})">Buy</button></div></div>`).join('')}
  </div>
  <div style="margin-top:14px;display:flex;gap:8px;flex-wrap:wrap">${s.pots.map((p,i)=>`<div class="card" style="padding:10px;min-width:160px"><div style="font-size:20px">${p.icon}</div><b style="font-size:11px">${p.name}</b><p style="font-size:11px;color:var(--muted)">${p.desc}</p><div style="margin-top:6px;display:flex;justify-content:space-between"><span class="badge">50g</span><button class="btn btn-gold" onclick="buyPotion(${i})">Buy</button></div></div>`).join('')}</div>
  <div style="margin-top:14px" class="card" style="padding:12px"><b>Card Removal Service — 75g</b><p style="font-size:11px;color:var(--muted)">${s.removalUsed?'Used this visit.':'Remove a card from your deck.'}</p><button class="btn" ${s.removalUsed?'disabled':''} onclick="removeCardShop()">Remove Card</button></div>
  <div style="margin-top:16px"><button class="btn" onclick="leaveShop()">Leave Shop →</button></div>
 </div></div>`;
}
window.buyCard=(i)=>{let c=state.shop.clsCards[i]; if(!c) return; let price=c.rarity==='Rare'?150:c.rarity==='Uncommon'?75:50; if(state.gold<price) return alert('Not enough gold'); state.gold-=price; state.deck.push(c); state.shop.clsCards.splice(i,1); render();};
window.buyColorless=()=>{if(state.gold<60) return alert('Need 60g'); state.gold-=60; state.deck.push(state.shop.colCard); state.shop.colCard=null; render();};
window.buyRelic=(i)=>{if(state.gold<150) return alert('Need 150g'); state.gold-=150; state.relics.push(state.shop.relics[i]); state.shop.relics.splice(i,1); render();};
window.buyPotion=(i)=>{if(state.gold<50) return alert('Need 50g'); if(state.potions.length>=3) return alert('Slots full'); state.gold-=50; state.potions.push(state.shop.pots[i]); state.shop.pots.splice(i,1); render();};
window.removeCardShop=()=>{
 let choice=prompt('Enter card name to remove (exact):\n'+state.deck.map(c=>c.name+(c.upgraded?'+':'')).join(', '));
 if(!choice) return; let idx=state.deck.findIndex(c=>c.name===choice || c.name+'+'===choice); if(idx<0) return alert('Not found'); if(state.gold<75) return alert('Need 75g'); if(state.cursedCrown && state.deck[idx].type==='Curse') return alert('Cursed Crown prevents removal!'); state.gold-=75; state.deck.splice(idx,1); state.shop.removalUsed=true; render();
};
window.leaveShop=()=>{state.floor++; state.visited.add(state.floor-1); state.shop=null; render();};

function restHTML(){
 return `<div class="card"><div class="card-hdr"><h2>⛺ Rest Site — Floor ${state.floor}</h2><span class="badge">HP ${state.hp}/${state.maxHp}</span></div>
 <div style="padding:16px" class="grid3">
  <div class="card" style="padding:16px;text-align:center"><h3>Rest</h3><p style="font-size:11px;color:var(--muted)">Heal 25% Max HP (+${Math.floor(state.maxHp*0.25)})</p><button class="btn btn-blood" style="margin-top:10px" onclick="doRest('heal')">Heal</button></div>
  <div class="card" style="padding:16px;text-align:center"><h3>Forge</h3><p style="font-size:11px;color:var(--muted)">Upgrade a card</p><button class="btn btn-gold" style="margin-top:10px" onclick="doRest('forge')">Upgrade</button></div>
  <div class="card" style="padding:16px;text-align:center"><h3>Meditate</h3><p style="font-size:11px;color:var(--muted)">Remove a card</p><button class="btn" style="margin-top:10px" onclick="doRest('meditate')">Remove</button></div>
 </div></div>`;
}
window.doRest=(type)=>{
 if(type==='heal'){ healPlayer(Math.floor(state.maxHp*0.25)); }
 else if(type==='forge'){
   let name=prompt('Card to upgrade:\n'+state.deck.map((c,i)=>i+': '+c.name+(c.upgraded?' + (already)':'')).join('\n'));
   if(name===null) return; let idx=parseInt(name); if(isNaN(idx)||idx<0||idx>=state.deck.length) {let f=state.deck.findIndex(c=>c.name===name); if(f>=0) idx=f; else return alert('Invalid');}
   if(state.deck[idx].upgraded) return alert('Already upgraded'); state.deck[idx].upgraded=true; if(state.deck[idx].def.upCost!==undefined) state.deck[idx].cost=state.deck[idx].def.upCost;
 }
 else if(type==='meditate'){
   let name=prompt('Card to remove:\n'+state.deck.map((c,i)=>i+': '+c.name).join('\n'));
   if(name===null) return; let idx=parseInt(name); if(isNaN(idx)){let f=state.deck.findIndex(c=>c.name===name); idx=f;} if(idx<0||idx>=state.deck.length) return alert('Invalid'); if(state.cursedCrown && state.deck[idx].type==='Curse') return alert('Cursed Crown blocks removal'); state.deck.splice(idx,1);
 }
 state.floor++; state.visited.add(state.floor-1); render();
};

const EVENTS=[
 {title:'The Forgotten Altar',text:'A blood-stained altar hums with power.',choices:[
  {label:'Offer 15 HP → Random Rare relic',fn:()=>{applyDamage({hp:state.hp,maxHp:state.maxHp,block:0,intangible:state.combat?state.combat.intangible:0},15,true); state.hp=Math.max(1,state.hp-15); state.relics.push(RELICS.filter(r=>r.rarity==='Rare')[0]);}},
  {label:'Pray → Heal 10, gain Doubt',fn:()=>{healPlayer(10); state.deck.push(makeCard('Doubt'));}},
  {label:'Leave',fn:()=>{}},
 ]},
 {title:'Shadowy Peddler',text:'A hooded figure offers a trade.',choices:[
  {label:'Trade a card → Remove 1, gain 100 Gold',fn:()=>{let n=prompt('Card to remove:\n'+state.deck.map(c=>c.name).join(', ')); let i=state.deck.findIndex(c=>c.name===n); if(i>=0){state.deck.splice(i,1); state.gold+=100;}}},
  {label:'Buy wares → -50 Gold, random Potion + Common relic',fn:()=>{if(state.gold>=50){state.gold-=50; state.potions.push(POTIONS[Math.floor(Math.random()*POTIONS.length)]); state.relics.push(RELICS.find(r=>r.rarity==='Common'));}}},
  {label:'Attack → fight Shadow Thief (25 HP) for 75 Gold',fn:()=>{state.combat={enemies:[makeEnemy('Skeleton')],drawPile:[],discard:[],hand:[],exhaust:[],energy:3,block:0,hp:state.hp,maxHp:state.maxHp,str:0,dex:0,weak:0,vuln:0,intangible:0,regen:0,bleed:0,burn:0,poison:0,powers:[],turn:1,nextEnergy:0,nextBlock:0,nextDex:0,selected:0}; state.combat.enemies[0].hp=25; state.combat.enemies[0].maxHp=25; state.inCombat=true; state.eventCombatGold=75; drawCards(5);}},
 ]},
 {title:'Cursed Spring',text:'A spring bubbles with dark water.',choices:[
  {label:'Drink → -10 HP, remove all Curses',fn:()=>{state.hp=Math.max(1,state.hp-10); state.deck=state.deck.filter(c=>c.type!=='Curse');}},
  {label:'Bottle it → Obtain Cursed Brew',fn:()=>{if(state.potions.length<3) state.potions.push(POTIONS.find(p=>p.id==='Cursed Brew'));}},
  {label:'Leave',fn:()=>{}},
 ]},
 {title:'Restless Dead',text:'Ghosts offer a bargain.',choices:[
  {label:'Accept → -20 HP, upgrade 2 random cards',fn:()=>{state.hp=Math.max(1,state.hp-20); let cand=state.deck.filter(c=>!c.upgraded); shuffle(cand).slice(0,2).forEach(c=>c.upgraded=true);}},
  {label:'Refuse',fn:()=>{}},
 ]},
 {title:'Impish Gambler',text:'A grinning imp challenges you.',choices:[
  {label:'Bet 30 Gold (50% double)',fn:()=>{if(state.gold>=30){if(Math.random()<0.5) state.gold+=30; else state.gold-=30;}}},
  {label:'Wager a card → remove random; if Curse +100 Gold',fn:()=>{if(state.deck.length){let i=Math.floor(Math.random()*state.deck.length); let c=state.deck.splice(i,1)[0]; if(c.type==='Curse') state.gold+=100;}}},
  {label:'Leave',fn:()=>{}},
 ]},
 {title:'Ancient Library',text:'Dusty tomes contain forgotten knowledge.',choices:[
  {label:'Study → +1 Energy next combat, -5 HP',fn:()=>{state.nextTurnBuffs.energy=1; state.hp=Math.max(1,state.hp-5);}},
  {label:'Read forbidden → Random Power + Doubt',fn:()=>{let pool=CLASSES[state.cls].cards.filter(c=>c.type==='Power'); let p=pool[Math.floor(Math.random()*pool.length)]; state.deck.push(makeCard(p.n)); state.deck.push(makeCard('Doubt'));}},
  {label:'Leave',fn:()=>{}},
 ]},
];
function shuffle(a){for(let i=a.length-1;i>0;i--){let j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]} return a;}
function eventHTML(){
 if(!state.event) state.event=EVENTS[Math.floor(Math.random()*EVENTS.length)];
 let e=state.event;
 return `<div class="card"><div class="card-hdr"><h2>❓ Event — ${e.title}</h2><span class="badge">Floor ${state.floor}</span></div>
 <div style="padding:16px"><p style="color:var(--muted);line-height:1.6">${e.text}</p>
 <div style="margin-top:14px;display:flex;flex-direction:column;gap:8px">${e.choices.map((c,i)=>`<button class="btn" style="text-align:left" onclick="chooseEvent(${i})">${c.label}</button>`).join('')}</div>
 <div style="margin-top:12px"><button class="btn" onclick="leaveEvent()">Leave</button></div></div></div>`;
}
window.chooseEvent=(i)=>{
 let e=state.event; e.choices[i].fn(); if(state.inCombat){ render(); return; }
 state.event=null; state.floor++; state.visited.add(state.floor-1); render();
};
window.leaveEvent=()=>{state.event=null; state.floor++; state.visited.add(state.floor-1); render();};

window.pickClass=(cls)=>{initRun(cls)};
window.advance=()=>{state.floor=2; render();};
window.setBranch=(side)=>{state.mapChoice=side; render();};
window.trySmokeEscape=()=>{
 let idx=state.potions.findIndex(p=>p.id==='Smoke Bomb');
 if(idx<0) return alert('No Smoke Bomb');
 state.potions.splice(idx,1); state.floor++; render();
};
window.usePotion=(i)=>{
 let p=state.potions[i]; if(!p) return;
 if(state.combat && state.inCombat){
   let c=state.combat;
   if(p.id==='Blood Vial'){ heal(c,15); state.hp=c.hp; }
   else if(p.id==='Firebomb'){ c.enemies.forEach(e=>{if(e.alive){let d=calcDamage(10,c,e); applyDamage(e,d);}}); checkVictory(); }
   else if(p.id==='Dex Tonic'){ c.dex+=3; setTimeout(()=>c.dex=Math.max(0,c.dex-3),1); }
   else if(p.id==='Energy Elixir'){ c.energy+=2; }
   else if(p.id==='Cursed Brew'){ c.enemies.forEach(e=>{if(e.alive) e.vuln+=3}); }
   else if(p.id==='Bleeding Flask'){ let t=c.enemies.find(e=>e.alive); if(t) t.bleed+=6; }
   else if(p.id==='Imp-in-a-Bottle'){ c.hand.push(makeCard('Imp Claw+')); }
   else if(p.id==='Smoke Bomb'){ state.inCombat=false; state.combat=null; state.floor++; state.potions.splice(i,1); render(); return; }
   state.potions.splice(i,1); render();
 } else {
   if(p.id==='Blood Vial'){ healPlayer(15); state.potions.splice(i,1); }
   else alert('Can only use in combat (except Blood Vial).');
   renderTop();
 }
};
window.resetRun=()=>{state.cls=null; state.shop=null; state.event=null; state.combat=null; state.inCombat=false; state.pendingReward=null; render();};
window.toggleHelp=()=> alert('Shadows of the Abyss — How to play:\n• 3 Energy/turn, draw 5.\n• Block resets each turn.\n• Intents show enemy next move.\n• Bleed/Burn halve each turn, Poison -1.\n• Intangible = damage capped to 1.\n• Exhaust cards removed for combat.\n• Potions are free actions.\n• Rest: heal/upgrade/remove.\n• Shop: buy cards/relics/potions.\n• Boss at floor 15, full heal after boss.');
// keyboard
addEventListener('keydown',e=>{
 if(e.key.toLowerCase()==='e' && state.inCombat) endTurn();
});
render();
</script>
</body>
</html>
<!-- agent-meta {"model":"meta/muse-spark-1.2","provider":"openrouter","persona":"basic","sessionId":"34bfc9b5-2bdc-4788-a9ea-f39baaa13b53","tokensIn":8015,"tokensOut":26396,"tokensTotal":34411,"cost":0.12220175,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-08-06T15:18:59.874Z"} -->