← Shadows results

Shadows

GLM 5.3 Flash · 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 62,510 bytes · SHA-256 714db8016877
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Shadows of the Abyss</title>
<style>
:root{--bg:#0b0a12;--panel:#161322;--panel2:#1e1930;--gold:#d4a94e;--red:#c0392b;--txt:#d8d2e8;--dim:#8a82a3;--blue:#4a7fb5;--green:#3f9d5a;}
*{box-sizing:border-box;margin:0;padding:0}
body{background:radial-gradient(ellipse at 50% -10%,#1c1533 0%,#0b0a12 60%);color:var(--txt);font-family:'Segoe UI',system-ui,sans-serif;min-height:100vh;overflow-x:hidden}
#app{max-width:1200px;margin:0 auto;padding:10px}
h1{font-family:Georgia,serif;letter-spacing:3px;text-align:center;color:var(--gold);text-shadow:0 0 18px #7a5b1e66}
h2{font-family:Georgia,serif;color:var(--gold)}
button{cursor:pointer;background:linear-gradient(#2c2444,#1c1730);color:var(--txt);border:1px solid #4a3f6e;border-radius:6px;padding:8px 16px;font-size:14px;transition:all .15s}
button:hover:not(:disabled){border-color:var(--gold);box-shadow:0 0 10px #d4a94e33;color:#fff}
button:disabled{opacity:.4;cursor:default}
.screen{display:none}.screen.active{display:block}
/* top bar */
#topbar{display:flex;gap:14px;align-items:center;background:var(--panel);border:1px solid #332a52;border-radius:8px;padding:8px 14px;margin-bottom:10px;flex-wrap:wrap;font-size:14px}
#topbar .stat b{color:var(--gold)}
.hpbar{width:140px;height:14px;background:#2a0d0d;border:1px solid #5a2020;border-radius:7px;overflow:hidden;position:relative}
.hpbar>div{height:100%;background:linear-gradient(#c0392b,#7e1f14);transition:width .3s}
.hpbar span{position:absolute;inset:0;text-align:center;font-size:11px;line-height:13px;color:#fff;text-shadow:0 0 3px #000}
/* map */
#mapwrap{padding:10px 0}
.mapfloor{display:flex;justify-content:center;gap:60px;margin:6px 0}
.node{width:64px;height:64px;border-radius:50%;border:2px solid #443a6b;background:var(--panel2);display:flex;align-items:center;justify-content:center;font-size:26px;cursor:default;position:relative;user-select:none}
.node.reachable{border-color:var(--gold);cursor:pointer;box-shadow:0 0 12px #d4a94e55;animation:pulse 1.5s infinite}
.node.reachable:hover{transform:scale(1.12)}
.node.done{opacity:.35;border-color:#2a2440}
.node.here::after{content:'▼';position:absolute;bottom:-20px;color:var(--gold);font-size:14px}
@keyframes pulse{50%{box-shadow:0 0 22px #d4a94e99}}
.flabel{position:absolute;top:-18px;left:50%;transform:translateX(-50%);font-size:10px;color:var(--dim);white-space:nowrap}
/* combat */
#enemies{display:flex;justify-content:center;gap:40px;min-height:220px;align-items:flex-end;padding:10px}
.enemy{text-align:center;width:150px;position:relative;transition:transform .2s}
.enemy.targetable{cursor:pointer}.enemy.targetable:hover{transform:translateY(-6px)}
.enemy .sprite{font-size:64px;filter:drop-shadow(0 0 10px #000);transition:transform .15s}
.enemy.dead{opacity:.15;filter:grayscale(1)}
.enemy .ehpbar{height:10px;background:#2a0d0d;border:1px solid #5a2020;border-radius:5px;overflow:hidden;margin:3px 0}
.enemy .ehpbar>div{height:100%;background:#a03030;transition:width .3s}
.intent{font-size:13px;background:#00000088;border:1px solid #443a6b;border-radius:5px;padding:2px 8px;display:inline-block;margin-bottom:4px}
.statchips{display:flex;gap:3px;justify-content:center;flex-wrap:wrap;min-height:18px;font-size:11px}
.chip{background:#000000aa;border:1px solid #555;border-radius:4px;padding:0 5px}
.chip.buff{border-color:#3f9d5a;color:#8fe0a5}.chip.debuff{border-color:#c0392b;color:#f0a0a0}
.eblock{color:#8ab4d8;font-weight:bold}
#playerzone{display:flex;justify-content:center;align-items:center;gap:30px;margin:6px 0}
#pblock{font-size:22px;color:#8ab4d8}
#energyorb{width:74px;height:74px;border-radius:50%;background:radial-gradient(#e8c860,#8a6a1e);display:flex;flex-direction:column;align-items:center;justify-content:center;color:#221803;font-weight:bold;font-size:22px;box-shadow:0 0 18px #d4a94e66;border:2px solid #f0d888}
#energyorb small{font-size:10px}
#hand{display:flex;justify-content:center;gap:10px;min-height:190px;padding:10px;flex-wrap:nowrap}
.card{width:130px;min-width:130px;height:180px;background:linear-gradient(#241d3d,#151022);border:2px solid #4a3f6e;border-radius:10px;padding:6px;display:flex;flex-direction:column;cursor:pointer;transition:transform .15s,border-color .15s;position:relative}
.card:hover{transform:translateY(-18px);border-color:var(--gold);z-index:5}
.card.unplayable{opacity:.5}.card.unplayable:hover{transform:none;border-color:#4a3f6e}
.card .cname{font-size:12px;font-weight:bold;color:var(--gold);text-align:center}
.card .ctype{font-size:9px;text-align:center;color:var(--dim);text-transform:uppercase}
.card .cdesc{font-size:11px;margin-top:4px;flex:1;text-align:center;line-height:1.3}
.card .ccost{position:absolute;top:-8px;left:-8px;width:26px;height:26px;border-radius:50%;background:radial-gradient(#e8c860,#8a6a1e);color:#221803;font-weight:bold;display:flex;align-items:center;justify-content:center;font-size:13px;border:1px solid #f0d888}
.card.upgraded{border-color:#3f9d5a}.card.upgraded .cname{color:#8fe0a5}
.card.atk{border-left:4px solid #a03030}.card.skill{border-left:4px solid #4a7fb5}.card.power{border-left:4px solid #9a4ab5}.card.curse{border-left:4px solid #555;filter:grayscale(.4)}
#combatlog{max-height:90px;overflow-y:auto;font-size:12px;color:var(--dim);background:#00000055;border-radius:6px;padding:6px 10px;margin-top:6px}
#combatlog div{margin:1px 0}
/* generic panels */
.panel{background:var(--panel);border:1px solid #332a52;border-radius:10px;padding:18px;margin:10px auto;max-width:900px}
.row{display:flex;gap:12px;flex-wrap:wrap;justify-content:center}
.choicecard{width:170px;min-height:200px}
.rewarditem{display:block;margin:8px auto;min-width:260px;text-align:left}
.small{font-size:12px;color:var(--dim)}
.potion{width:40px;height:40px;font-size:22px;border-radius:8px;border:1px solid #4a3f6e;background:var(--panel2);cursor:pointer;display:flex;align-items:center;justify-content:center;position:relative}
.potion:hover .tip,.relic:hover .tip,.card .tip2{display:block}
.tip{display:none;position:absolute;top:110%;left:50%;transform:translateX(-50%);background:#000;border:1px solid #4a3f6e;padding:6px 10px;border-radius:6px;width:200px;font-size:11px;z-index:50;text-align:center;color:var(--txt)}
.relic{width:36px;height:36px;font-size:20px;border-radius:8px;border:1px solid #6a5a2e;background:#2a2410;cursor:default;display:flex;align-items:center;justify-content:center;position:relative}
#deckview .card{cursor:default}#deckview .card:hover{transform:none}
.overlay{position:fixed;inset:0;background:#000000cc;display:flex;align-items:center;justify-content:center;z-index:100}
.overlay .panel{max-height:90vh;overflow-y:auto;width:900px}
.eventtext{font-style:italic;line-height:1.5;margin:10px 0;color:#c8c0dd}
.eventbtn{display:block;width:100%;text-align:left;margin:6px 0}
.shopitem{display:flex;align-items:center;gap:10px;width:100%;margin:5px 0;text-align:left}
.shopitem .price{margin-left:auto;color:var(--gold);font-weight:bold;white-space:nowrap}
.classcard{width:220px;padding:20px;text-align:center;cursor:pointer}
.classcard .big{font-size:50px}
.classcard:hover{border-color:var(--gold)}
.flash{animation:flash .4s}
@keyframes flash{0%{background:#c0392b66}}
#victory,#defeat{text-align:center;padding:60px 20px}
#victory h1,#defeat h1{font-size:44px;margin-bottom:20px}
</style>
</head>
<body>
<div id="app">
<div id="topbar" style="display:none">
  <div class="stat"><b id="tclass"></b></div>
  <div class="stat">HP <div class="hpbar" style="display:inline-block;vertical-align:middle"><div id="thpfill"></div><span id="thptext"></span></div></div>
  <div class="stat">🪙 <b id="tgold"></b></div>
  <div class="stat">Act <b id="tact"></b> · Floor <b id="tfloor"></b></div>
  <div class="stat" id="trelics" style="display:flex;gap:4px"></div>
  <div class="stat" id="tpotions" style="display:flex;gap:4px"></div>
  <div class="stat" style="margin-left:auto"><button onclick="UI.viewDeck()">Deck (<span id="tdeck"></span>)</button></div>
</div>

<div id="scr-title" class="screen active">
  <h1 style="font-size:52px;margin-top:60px">Shadows of the Abyss</h1>
  <p style="text-align:center;color:var(--dim);margin:10px 0 40px">A dark fantasy roguelike deckbuilder</p>
  <div class="row" id="classpick"></div>
</div>

<div id="scr-map" class="screen"><div id="mapwrap"></div></div>

<div id="scr-combat" class="screen">
  <div id="enemies"></div>
  <div id="playerzone">
    <div id="pstats"></div>
    <div id="energyorb"><span id="etext">3/3</span><small>ENERGY</small></div>
    <button id="endturn" onclick="Combat.endTurn()" style="font-size:16px;padding:12px 24px">End Turn</button>
  </div>
  <div id="hand"></div>
  <div id="combatlog"></div>
</div>

<div id="scr-reward" class="screen"><div class="panel" id="rewardpanel"></div></div>
<div id="scr-rest" class="screen"><div class="panel" id="restpanel"></div></div>
<div id="scr-shop" class="screen"><div class="panel" id="shoppanel"></div></div>
<div id="scr-event" class="screen"><div class="panel" id="eventpanel"></div></div>
<div id="scr-end" class="screen"><div id="endpanel"></div></div>
<div id="overlayhost"></div>
</div>
<script>
/* ============ DATA ============ */
const CLASSES={
 reaper:{name:'Blood Reaper',icon:'🩸',desc:'Sacrifice HP for power. Bleed and life steal.',sig:'sanguine'},
 shadow:{name:'Shadowmancer',icon:'🌑',desc:'Weak, Vulnerable, Intangible, card manipulation.',sig:'sbolt'},
 hell:{name:'Hellforged',icon:'🔥',desc:'Fire, Burn, imps, energy manipulation.',sig:'firebolt'}
};
const CARDS={};
function def(id,o){CARDS[id]=Object.assign({id,up:false},o);CARDS[id+'_+']=Object.assign({},o,{id:id+'_+',up:true});}
// basics
def('strike',{name:'Strike',cls:'all',type:'Attack',cost:1,rarity:'Basic',d:'Deal 6 damage.',fx:{dmg:6},up:{dmg:8},ud:'Deal 8 damage.'});
def('defend',{name:'Defend',cls:'all',type:'Skill',cost:1,rarity:'Basic',d:'Gain 5 Block.',fx:{block:5},up:{block:8},ud:'Gain 8 Block.'});
// Blood Reaper
def('sanguine',{name:'Sanguine Strike',cls:'reaper',type:'Attack',cost:1,rarity:'Common',d:'Deal 8 damage. Heal 2 HP.',fx:{dmg:8,heal:2},up:{dmg:10,heal:3},ud:'Deal 10 damage. Heal 3 HP.'});
def('hemorrhage',{name:'Hemorrhage',cls:'reaper',type:'Attack',cost:1,rarity:'Common',d:'Deal 5 damage. Apply 3 Bleed.',fx:{dmg:5,bleed:3},up:{dmg:6,bleed:4},ud:'Deal 6 damage. Apply 4 Bleed.'});
def('bloodletting',{name:'Bloodletting',cls:'reaper',type:'Attack',cost:1,rarity:'Uncommon',d:'Lose 3 HP. Deal 12 damage.',fx:{selfhp:3,dmg:12},up:{selfhp:2,dmg:15},ud:'Lose 2 HP. Deal 15 damage.'});
def('crimsoneruption',{name:'Crimson Eruption',cls:'reaper',type:'Attack',cost:2,rarity:'Rare',d:'Deal 8 damage to ALL enemies. Lose 2 HP.',fx:{dmg:8,all:true,selfhp:2},up:{dmg:10},ud:'Deal 10 damage to ALL enemies. Lose 2 HP.'});
def('crimsonshield',{name:'Crimson Shield',cls:'reaper',type:'Skill',cost:1,rarity:'Common',d:'Gain 6 Block. Lose 2 HP.',fx:{block:6,selfhp:2},up:{block:8},ud:'Gain 8 Block. Lose 2 HP.'});
def('bloodward',{name:'Blood Ward',cls:'reaper',type:'Skill',cost:1,rarity:'Common',d:'Gain 3 Block. Gain 1 Regeneration.',fx:{block:3,regen:1},up:{block:4,regen:2},ud:'Gain 4 Block. Gain 2 Regeneration.'});
def('sacbarrier',{name:'Sacrificial Barrier',cls:'reaper',type:'Skill',cost:2,rarity:'Uncommon',d:'Gain 10 Block. Lose 5 HP.',fx:{block:10,selfhp:5},up:{block:13},ud:'Gain 13 Block. Lose 5 HP.'});
def('vitalsurge',{name:'Vital Surge',cls:'reaper',type:'Skill',cost:-1,rarity:'Rare',d:'Gain Block equal to your missing HP. Exhaust.',fx:{blockMissing:1,exhaust:true},up:{blockMissing:3},ud:'Gain Block equal to missing HP +3. Exhaust.'});
def('lifetap',{name:'Life Tap',cls:'reaper',type:'Skill',cost:0,rarity:'Common',d:'Draw 2 cards. Lose 3 HP.',fx:{draw:2,selfhp:3},up:{draw:3},ud:'Draw 3 cards. Lose 3 HP.'});
def('rendflesh',{name:'Rend Flesh',cls:'reaper',type:'Skill',cost:1,rarity:'Uncommon',d:'Apply 5 Bleed to ALL enemies. Lose 2 HP.',fx:{bleed:5,all:true,selfhp:2},up:{bleed:7},ud:'Apply 7 Bleed to ALL enemies. Lose 2 HP.'});
def('soulsiphon',{name:'Soul Siphon',cls:'reaper',type:'Power',cost:2,rarity:'Uncommon',d:'At the start of your turn, gain 1 Energy and lose 1 HP.',fx:{power:'soulSiphon'},up:{cost:1},ud:'Cost 1.'});
def('bloodpact',{name:'Blood Pact',cls:'reaper',type:'Power',cost:1,rarity:'Rare',d:'Whenever you lose HP from a card, gain 1 Strength.',fx:{power:'bloodPact'},up:{powerAmt:2},ud:'Gain 2 Strength instead.'});
// Shadowmancer
def('sbolt',{name:'Shadow Bolt',cls:'shadow',type:'Attack',cost:1,rarity:'Common',d:'Deal 6 damage. Apply 1 Weak.',fx:{dmg:6,weak:1},up:{dmg:8,weak:2},ud:'Deal 8 damage. Apply 2 Weak.'});
def('grasp',{name:'Grasp of Despair',cls:'shadow',type:'Attack',cost:1,rarity:'Common',d:'Deal 4 damage. Apply 1 Vulnerable.',fx:{dmg:4,vuln:1},up:{dmg:5,vuln:2},ud:'Deal 5 damage. Apply 2 Vulnerable.'});
def('nlash',{name:'Nightmare Lash',cls:'shadow',type:'Attack',cost:2,rarity:'Uncommon',d:'Deal 7 damage. If enemy has Weak, deal 7 more.',fx:{dmg:7,weakBonus:7},up:{dmg:9,weakBonus:9},ud:'Deal 9 damage. If Weak, deal 9 more.'});
def('eternaldark',{name:'Eternal Darkness',cls:'shadow',type:'Attack',cost:2,rarity:'Rare',d:'Deal 3 damage to a random enemy 3 times.',fx:{dmg:3,hits:3,random:true},up:{dmg:4},ud:'Deal 4 damage to a random enemy 3 times.'});
def('cloak',{name:'Cloak of Shadows',cls:'shadow',type:'Skill',cost:1,rarity:'Common',d:'Gain 5 Block. Draw 1 card.',fx:{block:5,draw:1},up:{block:7},ud:'Gain 7 Block. Draw 1 card.'});
def('hmist',{name:'Haunting Mist',cls:'shadow',type:'Skill',cost:1,rarity:'Common',d:'Gain 7 Block. Apply 1 Weak to ALL enemies.',fx:{block:7,weak:1,all:true},up:{block:9},ud:'Gain 9 Block. Apply 1 Weak to ALL.'});
def('pharmor',{name:'Phantom Armor',cls:'shadow',type:'Skill',cost:1,rarity:'Uncommon',d:'Gain 8 Block. Next turn, gain 2 Dexterity.',fx:{block:8,nextDex:2},up:{block:10,nextDex:3},ud:'Gain 10 Block. Next turn, gain 3 Dexterity.'});
def('shroud',{name:'Whispering Shroud',cls:'shadow',type:'Skill',cost:1,rarity:'Rare',d:'Gain 4 Block. Next turn, gain 4 Block.',fx:{block:4,nextBlock:4},up:{block:5,nextBlock:5},ud:'Gain 5 Block. Next turn, gain 5 Block.'});
def('shadowform',{name:'Shadowform',cls:'shadow',type:'Skill',cost:2,rarity:'Uncommon',d:'Gain 2 Intangible. Exhaust.',fx:{intangible:2,exhaust:true},up:{intangible:3},ud:'Gain 3 Intangible. Exhaust.'});
def('cursemis',{name:'Curse of Misfortune',cls:'shadow',type:'Skill',cost:1,rarity:'Common',d:'Enemy loses 3 Strength this turn. Apply 1 Vulnerable.',fx:{strDown:3,vuln:1},up:{strDown:4,vuln:2},ud:'Lose 4 Strength. Apply 2 Vulnerable.'});
def('darkrit',{name:'Dark Ritual',cls:'shadow',type:'Skill',cost:0,rarity:'Uncommon',d:'Gain 2 Energy. Shuffle a Dazed into your draw pile.',fx:{energy:2,dazed:'draw'},up:{dazed:'discard'},ud:'Gain 2 Energy. Shuffle a Dazed into your discard pile.'});
def('umbral',{name:'Umbral Mastery',cls:'shadow',type:'Power',cost:2,rarity:'Rare',d:'At the start of your turn, apply 1 Weak to ALL enemies.',fx:{power:'umbral'},up:{powerAmt:2},ud:'Apply 2 Weak instead.'});
// Hellforged
def('firebolt',{name:'Firebolt',cls:'hell',type:'Attack',cost:1,rarity:'Common',d:'Deal 7 damage. Apply 2 Burn.',fx:{dmg:7,burn:2},up:{dmg:9,burn:3},ud:'Deal 9 damage. Apply 3 Burn.'});
def('hellfireblast',{name:'Hellfire Blast',cls:'hell',type:'Attack',cost:2,rarity:'Uncommon',d:'Deal 9 damage to ALL enemies.',fx:{dmg:9,all:true},up:{dmg:12},ud:'Deal 12 damage to ALL enemies.'});
def('searing',{name:'Searing Strike',cls:'hell',type:'Attack',cost:1,rarity:'Common',d:'Deal 6 damage. Gain 1 Energy next turn.',fx:{dmg:6,nextEnergy:1},up:{dmg:8},ud:'Deal 8 damage. Gain 1 Energy next turn.'});
def('immolate',{name:'Immolate',cls:'hell',type:'Attack',cost:2,rarity:'Rare',d:'Deal 12 damage. Exhaust.',fx:{dmg:12,exhaust:true},up:{dmg:16},ud:'Deal 16 damage. Exhaust.'});
def('obsidian',{name:'Obsidian Skin',cls:'hell',type:'Skill',cost:1,rarity:'Common',d:'Gain 7 Block.',fx:{block:7},up:{block:10},ud:'Gain 10 Block.'});
def('flameward',{name:'Flame Ward',cls:'hell',type:'Skill',cost:1,rarity:'Common',d:'Gain 5 Block. Apply 2 Burn to a random enemy.',fx:{block:5,burn:2,random:true},up:{block:6,burn:3},ud:'Gain 6 Block. Apply 3 Burn to a random enemy.'});
def('demonres',{name:'Demonic Resilience',cls:'hell',type:'Skill',cost:1,rarity:'Uncommon',d:'Gain 4 Block. Heal 2 HP.',fx:{block:4,heal:2},up:{block:5,heal:3},ud:'Gain 5 Block. Heal 3 HP.'});
def('brimstone',{name:'Brimstone Shield',cls:'hell',type:'Skill',cost:1,rarity:'Rare',d:'Gain 3 Block. Gain 2 Strength this turn.',fx:{block:3,tmpStr:2},up:{block:4,tmpStr:3},ud:'Gain 4 Block. Gain 3 Strength this turn.'});
def('summonimp',{name:'Summon Imp',cls:'hell',type:'Skill',cost:1,rarity:'Common',d:'Add an Imp Claw to your hand. Exhaust.',fx:{impClaw:0,exhaust:true},up:{impClaw:1},ud:'Add an Imp Claw+ instead. Exhaust.'});
def('inpact',{name:'Infernal Pact',cls:'hell',type:'Skill',cost:0,rarity:'Uncommon',d:'Lose 2 HP. Gain 2 Energy. Add an Imp Claw to your hand.',fx:{selfhp:2,energy:2,impClaw:0},up:{selfhp:1,impClaw:1},ud:'Lose 1 HP. Gain 2 Energy. Add an Imp Claw+.'});
def('hellaura',{name:'Hellfire Aura',cls:'hell',type:'Power',cost:2,rarity:'Rare',d:'At the start of your turn, deal 3 damage to ALL enemies.',fx:{power:'hellAura'},up:{powerAmt:5},ud:'Deal 5 damage instead.'});
def('soulburn',{name:'Soul Burn',cls:'hell',type:'Skill',cost:1,rarity:'Uncommon',d:'Apply 8 Burn to an enemy. Draw 1 card.',fx:{burn:8,draw:1},up:{burn:10},ud:'Apply 10 Burn. Draw 1 card.'});
// colorless
def('flash',{name:'Flash of Insight',cls:'none',type:'Skill',cost:1,rarity:'Common',d:'Draw 2 cards.',fx:{draw:2},up:{draw:3},ud:'Draw 3 cards.'});
def('ironwill',{name:'Iron Will',cls:'none',type:'Skill',cost:1,rarity:'Common',d:'Gain 8 Block.',fx:{block:8},up:{block:11},ud:'Gain 11 Block.'});
def('quickslash',{name:'Quick Slash',cls:'none',type:'Attack',cost:1,rarity:'Common',d:'Deal 7 damage.',fx:{dmg:7},up:{dmg:9},ud:'Deal 9 damage.'});
def('brew',{name:'Potent Brew',cls:'none',type:'Skill',cost:0,rarity:'Uncommon',d:'Gain 1 Energy. Exhaust.',fx:{energy:1,exhaust:true},up:{energy:2},ud:'Gain 2 Energy. Exhaust.'});
// curses/status/tokens
def('wound',{name:'Wound',cls:'none',type:'Status',cost:-2,rarity:'Special',d:'Unplayable. Ethereal. Exhaust.',unplayable:true,ethereal:true,exhaust:true});
def('dazed',{name:'Dazed',cls:'none',type:'Status',cost:-2,rarity:'Special',d:'Unplayable. Ethereal. Exhaust.',unplayable:true,ethereal:true,exhaust:true});
def('doubt',{name:'Doubt',cls:'none',type:'Curse',cost:-2,rarity:'Special',d:'Unplayable. At end of your turn, lose 1 HP.',unplayable:true,curse:true});
def('impclaw',{name:'Imp Claw',cls:'none',type:'Attack',cost:0,rarity:'Special',d:'Deal 4 damage. Exhaust.',fx:{dmg:4,exhaust:true},up:{dmg:6},ud:'Deal 6 damage. Exhaust.',token:true});
def('impclaw_+',{name:'Imp Claw+',cls:'none',type:'Attack',cost:0,rarity:'Special',d:'Deal 6 damage. Exhaust.',fx:{dmg:6,exhaust:true},token:true});
function mkCard(id){const b=CARDS[id];return {id:b.id,up:false};}
function cardDef(c){return CARDS[c.id];}
function cardName(c){const d=cardDef(c);return d.name+(c.up&&d.ud?'+':'');}
function cardCost(c){const d=cardDef(c);return c.up&&d.up&&d.up.cost!==undefined?d.up.cost:d.cost;}
function cardFx(c){const d=cardDef(c);return (c.up&&d.up)?Object.assign({},d.fx,d.up):d.fx;}
function cardDesc(c){const d=cardDef(c);return c.up&&d.ud?d.ud:d.d;}
/* ============ ENEMIES ============ */
function A(dmg,hits){return {t:'attack',dmg:dmg,hits:hits||1};}
function D(b){return {t:'defend',block:b};}
function B(fx){return {t:'buff',fx};}
function DE(fx){return {t:'debuff',fx};}
function SUM(en){return {t:'summon',en};}
const ENEMIES={
 // act1
 skeleton:{name:'Skeleton Warrior',icon:'💀',hp:22,pat:[A(6),A(6),D(8)]},
 zombie:{name:'Zombie Brute',icon:'🧟',hp:28,pat:[A(8),DE({weak:2}),A(8)]},
 cultist:{name:'Dark Cultist',icon:'🕯️',hp:20,pat:[B({str:3}),A(5),A(5)]},
 rats:{name:'Rat Swarm',icon:'🐀',hp:15,pat:[A(3,2),A(3,2)]},
 tombguard:{name:'Tomb Guardian',icon:'🗿',hp:42,elite:true,pat:[{t:'attack',dmg:6,block:8},A(4,3),A(12)]},
 bloodwitch:{name:'Blood Witch',icon:'🧝‍♀️',hp:36,elite:true,pat:[{t:'attack',dmg:5,heal:4},DE({vuln:1,weak:1}),{t:'attack',dmg:7,heal:4}]},
 lich:{name:'Lich King',icon:'👑',hp:65,boss:true,pat:[SUM('skelmin'),A(8),D(12)],
   phase2:{hp:30,gainStr:2,pat:[A(10),A(10),D(15)]}},
 skelmin:{name:'Skeleton Minion',icon:'☠️',hp:15,pat:[A(5),A(5),A(5)],minion:true},
 shadowthief:{name:'Shadow Thief',icon:'🥷',hp:30,pat:[A(7),DE({weak:1}),A(9)]},
 // act2
 wraith:{name:'Wraith',icon:'👻',hp:30,pat:[{t:'attack',dmg:7,debuff:{weak:1}},A(7),D(10)]},
 beast:{name:'Corrupted Beast',icon:'🐺',hp:38,pat:[A(12),{t:'defend',block:5,debuff:{selfVuln:1}},A(12)]},
 thorn:{name:'Thorned Horror',icon:'🌵',hp:25,pat:[A(4,3),DE({bleed:3}),A(8)]},
 witchdoc:{name:'Witch Doctor',icon:'🪘',hp:22,pat:[B({block:4,regen:2}),{t:'attack',dmg:6,debuff:{poison:2}},A(6)]},
 treant:{name:'Treant Protector',icon:'🌳',hp:50,elite:true,pat:[{t:'attack',dmg:9,block:6},DE({vuln:2}),A(10,2)]},
 plague:{name:'Plague Shaman',icon:'🦠',hp:42,elite:true,pat:[DE({poison:4}),SUM('plaguerat'),A(8)]},
 plaguerat:{name:'Plague Rat',icon:'🐀',hp:18,pat:[A(5),A(5),A(5)],minion:true},
 eldtreant:{name:'Eldritch Treant',icon:'🌲',hp:75,boss:true,pat:[{t:'attack',dmg:10,debuff:{weak:2}},{t:'defend',block:15,regen:2},A(8,2)],
   phase2:{hp:40,gainStr:1,pat:[{t:'attack',dmg:14,debuff:{vuln:1}},{t:'defend',block:20,heal:5},{t:'attack',dmg:14,debuff:{vuln:1}}]}},
 // act3
 hellhound:{name:'Hellhound',icon:'🐕‍🦺',hp:26,pat:[{t:'attack',dmg:8,debuff:{burn:2}},A(8),D(9)]},
 shadowdemon:{name:'Shadow Demon',icon:'😈',hp:30,pat:[{t:'attack',dmg:7,debuff:{weak:1}},A(7),B({intangible:1})],
   phase2:{hp:40,gainStr:1,pat:[{t:'attack',dmg:14,debuff:{vuln:1}},{t:'defend',block:20,heal:5},{t:'attack',dmg:14,debuff:{vuln:1}}]}},
 imp:{name:'Infernal Imp',icon:'👺',hp:18,pat:[A(5),A(5),SUM('imp')]},
 abysscult:{name:'Abyssal Cultist',icon:'🔮',hp:24,pat:[B({strAll:2}),A(7),A(7)]},
 flamespawn:{name:'Flamespawn',icon:'🔥',hp:48,elite:true,pat:[{t:'attack',dmg:9,debuff:{burn:3}},{t:'defend',block:10,str:2},A(8,2)]},
 dreadknight:{name:'Dread Knight',icon:'⚔️',hp:55,elite:true,pat:[A(12),DE({bleed:3}),{t:'defend',block:12,heal:4}]},
 archdemon:{name:'Archdemon',icon:'👹',hp:85,boss:true,pat:[SUM('hellhound'),{t:'attack',dmg:12,debuff:{burn:2}},{t:'defend',block:15,str:3}],
   phase2:{hp:45,gainStr:2,pat:[A(16),A(7,3),A(16)]}}
};
const ACTS={
 1:{name:'The Crypts',boss:'lich',floors:{
   2:[['skeleton'],['zombie']],3:[['cultist']],4:'event',5:'shop',6:[['rats']],
   7:'rest',8:[['skeleton','skeleton'],['zombie']],9:[['tombguard'],['skeleton','rats']],
   10:'event',11:[['cultist','rats']],12:[['bloodwitch'],['zombie','skeleton']],
   13:[['cultist','cultist']],14:'rest',15:'boss'}},
 2:{name:'Blighted Woods',boss:'eldtreant',floors:{
   2:[['wraith'],['beast']],3:[['thorn']],4:'event',5:'shop',6:[['witchdoc']],
   7:'rest',8:[['wraith','thorn'],['beast']],9:[['treant'],['wraith','witchdoc']],
   10:'event',11:[['witchdoc','thorn']],12:[['plague'],['beast','wraith']],
   13:[['witchdoc','witchdoc']],14:'rest',15:'boss'}},
 3:{name:'Abyssal Spire',boss:'archdemon',floors:{
   2:[['hellhound'],['shadowdemon']],3:[['abysscult']],4:'event',5:'shop',6:[['imp']],
   7:'rest',8:[['hellhound','imp'],['shadowdemon']],9:[['flamespawn'],['hellhound','abysscult']],
   10:'event',11:[['abysscult','imp']],12:[['dreadknight'],['shadowdemon','hellhound']],
   13:[['abysscult','abysscult']],14:'rest',15:'boss'}}
};
/* ============ RELICS / POTIONS ============ */
const RELICS={
 bloodstone:{name:'Bloodstone',icon:'🩸',rarity:'Common',d:'Whenever you lose HP from a card, heal 1 HP.'},
 demonhorns:{name:'Demon Horns',icon:'😈',rarity:'Special',cls:'hell',d:'At the start of each combat, add a random Imp Claw to your hand (once per combat).'},
 shadowcloak:{name:'Shadow Cloak',icon:'🌑',rarity:'Rare',d:'At the start of each combat, gain 1 Intangible.'},
 cursedamulet:{name:'Cursed Amulet',icon:'📿',rarity:'Common',d:'At the start of each combat, apply 1 Weak to ALL enemies.'},
 tome:{name:'Tome of Dark Rituals',icon:'📖',rarity:'Uncommon',d:'Whenever you play a Power card, draw 1 card.'},
 bleedingedge:{name:'Bleeding Edge',icon:'🗡️',rarity:'Common',d:'At the start of your turn, apply 1 Bleed to a random enemy.'},
 soulember:{name:'Soul Ember',icon:'🔥',rarity:'Rare',d:'Whenever you apply Burn to an enemy, deal 2 additional damage.'},
 hourglass:{name:'Eternal Hourglass',icon:'⏳',rarity:'Uncommon',d:'At the start of your turn, if you have no cards in hand, gain 1 Energy.'},
 sacrificialdagger:{name:'Sacrificial Dagger',icon:'🔪',rarity:'Special',cls:'reaper',d:'Your first Attack that costs HP each combat costs 1 less HP.'},
 vialofshadows:{name:'Vial of Shadows',icon:'🧪',rarity:'Rare',d:'When obtained, remove a card from your deck. Then this relic does nothing.'},
 ashenarmor:{name:'Ashen Armor',icon:'🛡️',rarity:'Common',d:'At the end of combat, heal 6 HP.'},
 greedyimp:{name:'Greedy Imp',icon:'💰',rarity:'Common',d:'Enemies drop 10 extra Gold.'}
};
const BOSSRELICS={
 cursedcrown:{name:'Cursed Crown',icon:'👑',d:'Gain 1 Energy at start of each turn. Curses can no longer be removed.'},
 ectoplasm:{name:'Ectoplasm',icon:'🫧',d:'Gain 1 Energy. Lose all Gold and gain no more Gold this run.'},
 philostone:{name:"Philosopher's Stone",icon:'💎',d:'Gain 1 Energy. All enemies start combat with 1 Strength.'},
 blackblood:{name:'Black Blood',icon:'🖤',d:'At start of combat, lose 10% max HP. Whenever you play an Attack, heal 1 HP.'},
 shadowamulet:{name:'Shadow Amulet',icon:'🧿',d:'Start each turn with 1 Intangible. Reduce max HP by 15.'}
};
const POTIONS={
 bloodvial:{name:'Blood Vial',icon:'🧣',d:'Heal 15 HP.',use:s=>{healPlayer(15);}},
 firebomb:{name:'Firebomb',icon:'💣',d:'Deal 10 damage to ALL enemies.',use:s=>{Combat.enemies.forEach(e=>{if(e.hp>0)Combat.dealToEnemy(e,10,{noStr:true});});}},
 dextonic:{name:'Dex Tonic',icon:'🧉',d:'Gain 3 Dexterity for 1 turn.',use:s=>{s.player.st.dex+=3;s.player.st.dexTurns=2;}},
 energyelixir:{name:'Energy Elixir',icon:'⚡',d:'Gain 2 Energy this turn.',use:s=>{s.player.energy+=2;}},
 cursedbrew:{name:'Cursed Brew',icon:'🍶',d:'Apply 3 Vulnerable to all enemies.',use:s=>{Combat.enemies.forEach(e=>{if(e.hp>0)applyStatus(e,'vuln',3);});}},
 bleedflask:{name:'Bleeding Flask',icon:'🩹',d:'Apply 6 Bleed to an enemy.',use:s=>{const t=Combat.pickTarget();if(t)applyStatus(t,'bleed',6);}},
 impbottle:{name:'Imp-in-a-Bottle',icon:'🍾',d:'Add an Imp Claw+ to your hand.',use:s=>{Combat.hand.push(mkCard('impclaw_+'));Combat.renderHand();}},
 smokebomb:{name:'Smoke Bomb',icon:'💨',d:'Escape from a non-boss combat, no rewards.',use:s=>{if(!Combat.isBoss){log('You vanish in smoke...');Combat.endCombat(false,true);}}}
};
const POTIONKEYS=Object.keys(POTIONS);
/* ============ EVENTS ============ */
const EVENTS=[
 {name:'The Forgotten Altar',icon:'🩸',text:'A blood-stained altar hums with power.',
  choices:[
   {label:'[Offer 15 HP] — Obtain a random Rare relic',can:s=>s.hp>15,do:s=>{damagePlayerRaw(15);giveRelic(randomRareRelic());}},
   {label:'[Pray] — Heal 10 HP, gain a Doubt curse',do:s=>{healPlayer(10);s.deck.push(mkCard('doubt'));msg('Doubt creeps into your deck.');}},
   {label:'[Leave]',do:s=>{}}]},
 {name:'Shadowy Peddler',icon:'🥷',text:'A hooded figure offers a trade.',
  choices:[
   {label:'[Trade a card] — Remove a card, gain 100 Gold',can:s=>s.deck.length>0,do:s=>{pickRemoveCard(()=>{s.gold+=100;msg('+100 Gold');leaveEvent();});}},
   {label:'[Buy his wares] — Lose 50 Gold, gain a random Potion and Common relic',can:s=>s.gold>=50,do:s=>{s.gold-=50;givePotion();giveRelic(randomRelic('Common'));leaveEvent();}},
   {label:'[Attack] — Fight a Shadow Thief for 75 Gold',do:s=>{Combat.special={en:'shadowthief',gold:75};startCombat(['shadowthief']);}}]},
 {name:'Cursed Spring',icon:'⛲',text:'A spring bubbles with dark water.',
  choices:[
   {label:'[Drink] — Lose 10 HP, remove all Curses from deck',can:s=>s.hp>10,do:s=>{damagePlayerRaw(10);s.deck=s.deck.filter(c=>cardDef(c).type!=='Curse');msg('Curses washed away.');}},
   {label:'[Bottle it] — Obtain a Cursed Brew potion',do:s=>{givePotion('cursedbrew');}},
   {label:'[Leave]',do:s=>{}}]},
 {name:'Restless Dead',icon:'👻',text:'Ghosts offer a bargain.',
  choices:[
   {label:'[Accept] — Lose 20 HP, upgrade 2 random cards',can:s=>s.hp>20,do:s=>{damagePlayerRaw(20);upgradeRandom(2);}},
   {label:'[Refuse]',do:s=>{}}]},
 {name:'Impish Gambler',icon:'🎲',text:'A grinning imp challenges you.',
  choices:[
   {label:'[Bet 30 Gold] — 50% chance to double it',can:s=>s.gold>=30,do:s=>{s.gold-=30;if(Math.random()<0.5){s.gold+=60;msg('You win! +60 Gold');}else msg('The imp laughs as he takes your gold.');}},
   {label:'[Wager a card] — Remove a random card; if Curse, gain 100 Gold',can:s=>s.deck.length>0,do:s=>{const i=Math.floor(Math.random()*s.deck.length);const c=s.deck.splice(i,1)[0];if(cardDef(c).type==='Curse'){s.gold+=100;msg('A curse removed! +100 Gold');}else msg(cardName(c)+' is lost to the void.');}},
   {label:'[Leave]',do:s=>{}}]},
 {name:'Ancient Library',icon:'📚',text:'Dusty tomes contain forgotten knowledge.',
  choices:[
   {label:'[Study] — Gain 1 Energy next combat, lose 5 HP',can:s=>s.hp>5,do:s=>{damagePlayerRaw(5);s.energyNextCombat=(s.energyNextCombat||0)+1;msg('Knowledge fills you...');}},
   {label:'[Read forbidden page] — Add a random Power card, gain Doubt',do:s=>{const p=classPool().filter(id=>CARDS[id].type==='Power');s.deck.push(mkCard(p[Math.floor(Math.random()*p.length)]));s.deck.push(mkCard('doubt'));msg('Forbidden knowledge... and Doubt.');}},
   {label:'[Leave]',do:s=>{}}]}
];
/* ============ STATE & HELPERS ============ */
let S=null;
const rnd=n=>Math.floor(Math.random()*n);
const pick=a=>a[rnd(a.length)];
const shuffle=a=>{for(let i=a.length-1;i>0;i--){const j=rnd(i+1);[a[i],a[j]]=[a[j],a[i]];}return a;};
const $=id=>document.getElementById(id);
function classPool(){const c=CLASSES[S.cls].sig;return Object.keys(CARDS).filter(id=>CARDS[id].cls===S.cls&&id.slice(-1)!=='+'&&CARDS[id].type!=='Power'||CARDS[id].cls===S.cls&&CARDS[id].type==='Power'&&id.slice(-1)!=='+');}
function classPoolIds(){return Object.keys(CARDS).filter(id=>CARDS[id].cls===S.cls&&!CARDS[id].token);}
function randomRelic(rar){const avail=Object.keys(RELICS).filter(k=>!S.relics.includes(k)&&(!RELICS[k].cls||RELICS[k].cls===S.cls)&&(!rar||RELICS[k].rarity===rar));if(!avail.length)return null;return pick(avail);}
function randomRareRelic(){return randomRelic('Rare')||randomRelic('Uncommon')||randomRelic('Common');}
function msg(t){const d=document.createElement('div');d.textContent=t;return d;}
function log(t){const c=$('combatlog');c.prepend(msg(t));}
function healPlayer(n){S.hp=Math.min(S.maxhp,S.hp+n);renderTop();}
function damagePlayerRaw(n){S.hp-=n;renderTop();if(S.hp<=0)gameOver();}
function givePotion(key){if(S.potions.length>=3){log('Potion slots full!');return false;}S.potions.push(key||pick(POTIONKEYS));renderTop();return true;}
function giveRelic(key){if(!key)return;S.relics.push(key);renderTop();if(key==='vialofshadows')pickRemoveCard(null,'Vial of Shadows: remove a card from your deck.');}
function upgradeRandom(n){const cands=S.deck.filter(c=>!c.up);for(let i=0;i<n&&cands.length;i++){const c=cands.splice(rnd(cands.length),1)[0];c.up=true;}msg('Cards upgraded!');}
function hasRelic(k){return S.relics.includes(k);}
function startRun(cls){
 S={cls,hp:70,maxhp:70,gold:99,deck:[],relics:[],potions:[],act:1,floor:1,node:0,energyNextCombat:0};
 for(let i=0;i<5;i++)S.deck.push(mkCard('strike'));
 for(let i=0;i<4;i++)S.deck.push(mkCard('defend'));
 S.deck.push(mkCard(CLASSES[cls].sig));
 showScreen('map');renderMap();
}
function gameOver(){showScreen('end');$('endpanel').innerHTML='<div id="defeat"><h1 style="color:#c0392b">YOU DIED</h1><p>The abyss claims another soul...</p><br><button onclick="location.reload()">New Run</button></div>';}
function victory(){showScreen('end');$('endpanel').innerHTML='<div id="victory"><h1>VICTORY</h1><p>The Archdemon falls. The abyss falls silent... for now.</p><br><button onclick="location.reload()">New Run</button></div>';}
/* ============ SCREENS / TOPBAR ============ */
function showScreen(name){document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));$('scr-'+name).classList.add('active');$('topbar').style.display=name==='title'?'none':'flex';}
function renderTop(){
 if(!S)return;
 $('tclass').textContent=CLASSES[S.cls].icon+' '+CLASSES[S.cls].name;
 $('thpfill').style.width=Math.max(0,S.hp/S.maxhp*100)+'%';
 $('thptext').textContent=Math.max(0,S.hp)+'/'+S.maxhp;
 $('tgold').textContent=S.gold;
 $('tact').textContent=S.act;$('tfloor').textContent=S.floor;
 $('tdeck').textContent=S.deck.length;
 $('trelics').innerHTML=S.relics.map(k=>{const r=RELICS[k]||BOSSRELICS[k];return `<span class="relic">${r.icon}<span class="tip"><b>${r.name}</b><br>${r.d}</span></span>`;}).join('');
 $('tpotions').innerHTML=S.potions.map((k,i)=>`<span class="potion" onclick="usePotion(${i})">${POTIONS[k].icon}<span class="tip"><b>${POTIONS[k].name}</b><br>${POTIONS[k].d}</span></span>`).join('');
}
function usePotion(i){
 if(!S||!S.potions[i])return;
 if(!$('scr-combat').classList.contains('active')&&!['bloodvial'].includes(S.potions[i])){alert('Potions can only be used during your turn in combat (or Blood Vial anytime).');return;}
 const k=S.potions[i];
 if(k==='bloodvial'&&!$('scr-combat').classList.contains('active')){POTIONS[k].use();S.potions.splice(i,1);renderTop();return;}
 if(!$('scr-combat').classList.contains('active'))return;
 if(Combat.busy)return;
 POTIONS[k].use();S.potions.splice(i,1);renderTop();Combat.renderAll();
}
function cardHTML(c,extra){
 const d=cardDef(c),cost=cardCost(c);
 const costTxt=cost<0?'—':cost;
 return `<div class="card ${d.type.toLowerCase()} ${c.up?'upgraded':''} ${extra||''}" data-cid="${c.id}">
  <div class="ccost">${costTxt}</div><div class="cname">${cardName(c)}</div><div class="ctype">${d.type}</div>
  <div class="cdesc">${cardDesc(c)}</div></div>`;
}
function viewDeckHTML(){return S.deck.map(c=>cardHTML(c)).join('');}
UI={viewDeck(){
 const h=document.createElement('div');h.className='overlay';
 h.innerHTML=`<div class="panel"><h2>Your Deck (${S.deck.length})</h2><div class="row" id="deckview">${viewDeckHTML()}</div><br><button onclick="this.closest('.overlay').remove()">Close</button></div>`;
 $('overlayhost').appendChild(h);
}};
function pickRemoveCard(cb,title){
 const h=document.createElement('div');h.className='overlay';
 h.innerHTML=`<div class="panel"><h2>${title||'Choose a card to remove'}</h2><div class="row">${S.deck.map((c,i)=>cardHTML(c).replace('class="card','data-i="'+i+'" class="card pickrm')).join('')}</div></div>`;
 $('overlayhost').appendChild(h);
 h.querySelectorAll('.pickrm').forEach(el=>el.onclick=()=>{
  const i=+el.dataset.i;const c=S.deck.splice(i,1)[0];
  h.remove();if(cb)cb(c);renderTop();
 });
}
function pickUpgradeCard(cb,title){
 const cands=S.deck.filter(c=>!c.up);
 if(!cands.length){msg('No cards to upgrade.');if(cb)cb();return;}
 const h=document.createElement('div');h.className='overlay';
 h.innerHTML=`<div class="panel"><h2>${title||'Choose a card to upgrade'}</h2><div class="row">${cands.map(c=>cardHTML(c).replace('class="card','data-cid="'+c.id+'" class="card pickup')).join('')}</div></div>`;
 $('overlayhost').appendChild(h);
 h.querySelectorAll('.pickup').forEach(el=>el.onclick=()=>{
  const cid=el.dataset.cid;const c=S.deck.find(x=>x.id===cid&&!x.up);if(c)c.up=true;
  h.remove();if(cb)cb();renderTop();
 });
}
/* ============ MAP ============ */
const NODEICON={enemy:'⚔️',elite:'💀',rest:'🔥',shop:'🛒',event:'❓',boss:'👑'};
function nodeType(f){const fl=ACTS[S.act].floors[f];if(typeof fl==='string')return fl;return 'enemy';}
function renderMap(){
 const act=ACTS[S.act];
 let html=`<h2 style="text-align:center">Act ${S.act}: ${act.name}</h2>`;
 for(let f=15;f>=1;f--){
  const fl=act.floors[f];
  let nodes;
  if(f===1)nodes=[{t:'start'}];
  else if(typeof fl==='string')nodes=[{t:fl}];
  else nodes=fl.map(g=>({t:'enemy',g}));
  const reachable=f===S.floor+1;
  html+=`<div class="mapfloor">`;
  nodes.forEach((n,i)=>{
   const done=f<=S.floor,here=f===S.floor;
   let cls='node'+(done?' done':'')+(here?' here':'')+(reachable?' reachable':'');
   let onclick=reachable?`onclick="enterNode(${f},${i})"`:'';
   let label=NODEICON[n.t]||'⚔️';
   if(n.t==='enemy'&&n.g)label=n.g.length>1?'⚔️⚔️':'⚔️';
   html+=`<div class="${cls}" ${onclick}><span class="flabel">${f===1?'START':f}</span>${label}</div>`;
  });
  html+=`</div>`;
 }
 $('mapwrap').innerHTML=html;
}
function enterNode(f,i){
 S.floor=f;
 const fl=ACTS[S.act].floors[f];
 if(typeof fl==='string'){
  if(fl==='event')startEvent();
  else if(fl==='shop')startShop();
  else if(fl==='rest')startRest();
  else if(fl==='boss')Combat.startCombat([ACTS[S.act].boss],true);
 }else Combat.startCombat(fl[i],false);
}
function nextFloor(){
 if(S.floor>=15){ // boss done
  if(S.act>=3){victory();return;}
  S.act++;S.floor=1;S.hp=S.maxhp;renderTop();showScreen('map');renderMap();return;
 }
 showScreen('map');renderMap();
}
/* ============ REST / SHOP / EVENT ============ */
function startRest(){
 showScreen('rest');
 $('restpanel').innerHTML=`<h2>🔥 Rest Site</h2><p class="small">Choose one action.</p><br>
  <button class="rewarditem" onclick="doRest('rest')">🛌 Rest — Heal ${Math.floor(S.maxhp*0.25)} HP (25% max)</button>
  <button class="rewarditem" onclick="doRest('forge')">⚒️ Forge — Upgrade a card</button>
  <button class="rewarditem" onclick="doRest('meditate')">🧘 Meditate — Remove a card from deck</button>`;
}
function doRest(mode){
 if(mode==='rest'){healPlayer(Math.floor(S.maxhp*0.25));afterNode();}
 else if(mode==='forge')pickUpgradeCard(()=>afterNode(),'Forge: upgrade a card');
 else pickRemoveCard(()=>afterNode(),'Meditate: remove a card');
}
function startShop(){
 showScreen('shop');
 const pool=shuffle(classPoolIds()).slice(0,3);
 const colorless=pick(['flash','ironwill','quickslash','brew']);
 const price=c=>({Common:50,Uncommon:75,Rare:150}[cardDef(c).rarity]||50);
 const relics=[randomRelic(),randomRelic()].filter(Boolean);
 const potions=[pick(POTIONKEYS),pick(POTIONKEYS),pick(POTIONKEYS)];
 let html=`<h2>🛒 Shop</h2><p>Gold: <b style="color:var(--gold)">${S.gold}</b></p><br><div class="row" style="align-items:flex-start">`;
 pool.forEach((id,i)=>{const c=mkCard(id);html+=`<div style="text-align:center">${cardHTML(c)}<br><button class="shopitem" onclick="buyCard(${i},'${id}',${price(c)})" id="sc${i}">Buy — ${price(c)}g</button></div>`;});
 const cc=mkCard(colorless);
 html+=`<div style="text-align:center">${cardHTML(cc)}<br><button class="shopitem" onclick="buyCard(9,'${colorless}',60)" id="sc9">Buy — 60g</button></div>`;
 html+=`</div><br><h2>Relics</h2><div class="row">`;
 relics.forEach((k,i)=>{const r=RELICS[k];html+=`<button class="shopitem" style="width:280px" onclick="buyRelic(${i},'${k}',150)" id="sr${i}">${r.icon} ${r.name}<span class="price">150g</span></button>`;});
 html+=`</div><br><h2>Potions</h2><div class="row">`;
 potions.forEach((k,i)=>{const p=POTIONS[k];html+=`<button class="shopitem" style="width:240px" onclick="buyPotion(${i},'${k}',50)" id="sp${i}">${p.icon} ${p.name}<span class="price">50g</span></button>`;});
 html+=`</div><br><button class="shopitem" style="width:400px" onclick="buyRemove()" id="srm">🗑️ Remove a card from your deck<span class="price">75g</span></button>
 <br><button onclick="afterNode()">Leave Shop</button>`;
 $('shoppanel').innerHTML=html;
 S._shop={pool,cc,relics,potions,removed:false};
}
function buyCard(i,id,price){
 if(S.gold<price)return;S.gold-=price;S.deck.push(mkCard(id));
 const b=$('sc'+i);if(b){b.disabled=true;b.textContent='Sold';}
 renderTop();
}
function buyRelic(i,k,price){
 if(S.gold<price||S.relics.includes(k))return;S.gold-=price;giveRelic(k);
 const b=$('sr'+i);if(b){b.disabled=true;b.textContent='Sold';}
}
function buyPotion(i,k,price){
 if(S.gold<price)return;if(!givePotion(k))return;S.gold-=price;
 const b=$('sp'+i);if(b){b.disabled=true;b.textContent='Sold';}
}
function buyRemove(){
 if(S.gold<75||S._shop.removed)return;S.gold-=75;S._shop.removed=true;
 $('srm').disabled=true;$('srm').innerHTML='Card removed';
 pickRemoveCard(null,'Remove a card');
}
function startEvent(){
 showScreen('event');
 const ev=pick(EVENTS);
 $('eventpanel').innerHTML=`<h2>${ev.icon} ${ev.name}</h2><p class="eventtext">${ev.text}</p><br>`+
  ev.choices.map((c,i)=>`<button class="eventbtn" id="ev${i}">${c.label}</button>`).join('');
 S._event=ev;
 ev.choices.forEach((c,i)=>{
  const b=$('ev'+i);
  if(c.can&&!c.can(S)){b.disabled=true;}
  b.onclick=()=>{if(c.can&&!c.can(S))return;c.do(S);};
 });
}
function leaveEvent(){afterNode();}
function afterNode(){renderTop();showScreen('map');renderMap();}
/* ============ STATUS / DAMAGE ============ */
function applyStatus(ent,key,amt){
 if(!ent||ent.hp<=0)return;
 if(key==='burn'&&ent.isPlayer===undefined){/*enemy*/}
 ent.st[key]=(ent.st[key]||0)+amt;
 if(key==='burn'&&ent.isEnemy&&hasRelic('soulember')&&!ent.isPlayer)dealToEnemy(ent,2,{noStr:true,pure:true});
}
function effStr(e){return (e.st.str||0)-(e.st.strDown||0);}
function calcDmg(base,att,target){
 let d=base+effStr(att);
 if(att.st.weak)d=Math.floor(d*0.75);
 if(target&&target.st.vuln)d=Math.floor(d*1.5);
 return Math.max(0,d);
}
function dealToEnemy(e,dmg,opts){
 opts=opts||{};
 if(e.hp<=0)return;
 if(e.st.intangible)dmg=Math.min(dmg,1);
 let blk=Math.min(e.block,dmg);e.block-=blk;dmg-=blk;
 if(dmg>0){e.hp-=dmg;e.flash=true;}
 Combat.renderEnemies();
 if(e.hp<=0)log(e.name+' dies!');
}
function damagePlayer(dmg,opts){
 opts=opts||{};
 if(S.hp<=0)return;
 if(S.player.st.intangible)dmg=Math.min(dmg,1);
 if(!opts.pierce){const blk=Math.min(S.player.block,dmg);S.player.block-=blk;dmg-=blk;}
 if(dmg>0)S.hp-=dmg;
 renderTop();Combat.renderPlayer();
 if(S.hp<=0){gameOver();throw 'dead';}
}
function loseHpFromCard(n){
 if(hasRelic('sacrificialdagger')&&Combat.firstHpAttack){n=Math.max(0,n-1);Combat.firstHpAttack=false;}
 if(hasRelic('bloodstone'))healPlayer(1);
 if(Combat.powers.bloodPact)applyStatus(S.player,'str',Combat.powers.bloodPact);
 if(S.player.st.intangible)n=Math.min(n,1);
 S.hp-=n;renderTop();Combat.renderPlayer();
 if(S.hp<=0){gameOver();throw 'dead';}
}
/* ============ COMBAT ============ */
const Combat={
 enemies:[],draw:[],hand:[],discard:[],exhaust:[],powers:{},pending:null,busy:false,isBoss:false,
 startCombat(group,boss){
  this.enemies=[];this.draw=[];this.hand=[];this.discard=[];this.exhaust=[];
  this.powers={};this.pending=null;this.busy=false;this.isBoss=!!boss;
  this.turn=0;this.firstHpAttack=true;this.impUsed=false;
  S.player={block:0,st:{}};
  this.nextBlock=0;this.nextDex=0;this.nextEnergy=0;this.tmpStr=0;
  group.forEach((key,i)=>{
   const d=ENEMIES[key];
   this.enemies.push({key,name:d.name,icon:d.icon,hp:d.hp,maxhp:d.hp,block:0,st:{},pat:d.pat,pi:0,def:d,boss:!!d.boss,elite:!!d.elite,phase:1});
  });
  if(hasRelic('philostone')||S.relics.some(r=>r==='philostone'))this.enemies.forEach(e=>e.st.str=(e.st.str||0)+1);
  // boss relics handled at pickup
  if(hasRelic('blackblood')){S.maxhp;S.hp=Math.max(1,S.hp-Math.floor(S.maxhp*0.1));}
  this.draw=shuffle(S.deck.map(c=>({...c})));
  if(hasRelic('cursedamulet'))this.enemies.forEach(e=>e.st.weak=(e.st.weak||0)+1);
  if(hasRelic('shadowcloak'))S.player.st.intangible=1;
  if(hasRelic('demonhorns')&&CLASSES[S.cls].sig==='firebolt'){this.hand.push(mkCard(Math.random()<0.5?'impclaw':'impclaw_+'));this.impUsed=true;}
  showScreen('combat');$('combatlog').innerHTML='';
  log('Combat begins!');
  this.startPlayerTurn();
 },
 alive(){return this.enemies.filter(e=>e.hp>0);},
 startPlayerTurn(){
  this.turn++;this.busy=false;
  S.player.block=0;
  if(this.tmpStr){S.player.st.str=(S.player.st.str||0)-this.tmpStr;this.tmpStr=0;}
  if(this.nextBlock){S.player.block+=this.nextBlock;this.nextBlock=0;}
  if(this.nextDex){S.player.st.dex=(S.player.st.dex||0)+this.nextDex;S.player.st.dexTurns=2;this.nextDex=0;}
  // duration decrements
  ['weak','vuln','intangible'].forEach(k=>{if(S.player.st[k])S.player.st[k]--;});
  if(S.player.st.dexTurns){S.player.st.dexTurns--;if(S.player.st.dexTurns<=0)S.player.st.dex=0;}
  // regen
  if(S.player.st.regen){healPlayer(S.player.st.regen);S.player.st.regen--;}
  // powers
  if(this.powers.soulSiphon){S.player.energy+=1;loseHpFromCard(1);}
  if(this.powers.umbral)this.alive().forEach(e=>applyStatus(e,'weak',this.powers.umbral));
  if(this.powers.hellAura)this.alive().forEach(e=>dealToEnemy(e,this.powers.hellAura,{noStr:true}));
  if(hasRelic('bleedingedge')){const a=this.alive();if(a.length)applyStatus(pick(a),'bleed',1);}
  if(hasRelic('shadowamulet'))S.player.st.intangible=(S.player.st.intangible||0)+1;
  let en=3+(hasRelic('cursedcrown')?1:0)+(hasRelic('ectoplasm')?1:0)+(hasRelic('philostone')?1:0)+(hasRelic('shadowamulet')?0:0);
  en+=this.nextEnergy;this.nextEnergy=0;
  en+=S.energyNextCombat||0;S.energyNextCombat=0;
  S.player.energy=en;
  if(hasRelic('hourglass')&&this.hand.length===0)S.player.energy+=1;
  // bleed on player
  if(S.player.st.bleed){damagePlayer(S.player.st.bleed,{pierce:true});S.player.st.bleed=Math.floor(S.player.st.bleed/2);}
  if(S.player.st.poison){damagePlayer(S.player.st.poison,{pierce:true});S.player.st.poison--;}
  this.drawCards(5);
  this.renderAll();
 },
 drawCards(n){
  for(let i=0;i<n;i++){
   if(this.hand.length>=10)break;
   if(!this.draw.length){
    if(!this.discard.length)break;
    this.draw=shuffle(this.discard);this.discard=[];
   }
   const c=this.draw.pop();this.hand.push(c);
  }
  this.renderHand();
 },
 pickTarget(){const a=this.alive();return a.length?a[0]:null;},
 needTarget(fx){return this.alive().length>1&&((fx.dmg&&!fx.all&&!fx.random)||(fx.bleed&&!fx.all)||(fx.burn&&!fx.all)||(fx.vuln&&!fx.all)||(fx.strDown));},
 clickCard(idx){
  if(this.busy)return;
  const c=this.hand[idx],d=cardDef(c);
  if(d.unplayable)return;
  const cost=cardCost(c);
  if(cost>S.player.energy){log('Not enough energy!');return;}
  const fx=cardFx(c);
  if(this.needTarget(fx)){this.pending=idx;this.renderAll();log('Choose a target for '+cardName(c));return;}
  this.playCard(idx,null);
 },
 playCard(idx,target){
  const c=this.hand[idx],d=cardDef(c),fx=cardFx(c);
  const cost=cardCost(c);
  if(cost>S.player.energy)return;
  S.player.energy-=cost;
  this.hand.splice(idx,1);
  log('You play '+cardName(c)+'.');
  const targets=fx.all?this.alive():(fx.random?null:[target||this.pickTarget()]);
  // self HP loss first
  if(fx.selfhp)loseHpFromCard(fx.selfhp);
  if(fx.impClaw!==undefined)this.hand.push(mkCard(fx.impClaw?'impclaw_+':'impclaw'));
  if(fx.energy)S.player.energy+=fx.energy;
  if(fx.nextEnergy)this.nextEnergy+=fx.nextEnergy;
  if(fx.nextBlock)this.nextBlock+=fx.nextBlock;
  if(fx.nextDex)this.nextDex+=fx.nextDex;
  if(fx.power){this.powers[fx.power]=fx.powerAmt||1;log(cardName(c)+' is active for the rest of combat.');}
  if(fx.str){S.player.st.str=(S.player.st.str||0)+fx.str;}
  if(fx.tmpStr){S.player.st.str=(S.player.st.str||0)+fx.tmpStr;this.tmpStr+=fx.tmpStr;}
  if(fx.dex)S.player.st.dex=(S.player.st.dex||0)+fx.dex;
  if(fx.regen)S.player.st.regen=(S.player.st.regen||0)+fx.regen;
  if(fx.intangible)S.player.st.intangible=(S.player.st.intangible||0)+fx.intangible;
  if(fx.block){let b=fx.block+(fx.blockMissing?Math.max(0,S.maxhp-S.hp):0);if(fx.blockMissing)b=fx.blockMissing===1?Math.max(0,S.maxhp-S.hp):Math.max(0,S.maxhp-S.hp)+3;b+=S.player.st.dex||0;S.player.block+=b;log('You gain '+b+' Block.');}
  if(fx.blockMissing){let b=(fx.blockMissing===1?Math.max(0,S.maxhp-S.hp):Math.max(0,S.maxhp-S.hp)+3)+(S.player.st.dex||0);S.player.block+=b;log('You gain '+b+' Block.');}
  if(fx.heal)healPlayer(fx.heal);
  if(fx.draw)this.drawCards(fx.draw);
  if(fx.dazed){const dz=mkCard('dazed');if(fx.dazed==='draw')this.draw.splice(rnd(this.draw.length+1),0,dz);else this.discard.push(dz);}
  if(fx.strDown&&target)target.st.strDown=(target.st.strDown||0)+fx.strDown;
  // attacks
  if(fx.dmg){
   const list=fx.random?null:targets;
   const hits=fx.hits||1;
   for(let h=0;h<hits;h++){
    let tg;
    if(fx.random){const a=this.alive();if(!a.length)break;tg=pick(a);}
    else tg=list&&list.filter(e=>e.hp>0)[0];
    if(!tg)break;
    let dmg=calcDmg(fx.dmg,S.player,tg);
    if(fx.weakBonus&&tg.st.weak)dmg=calcDmg(fx.dmg+fx.weakBonus,S.player,tg);
    dealToEnemy(tg,dmg);
   }
  }
  // debuffs
  const debuffTargets=fx.all?this.alive():[target||this.pickTarget()].filter(Boolean);
  ['bleed','weak','vuln','burn','poison'].forEach(k=>{
   if(fx[k])debuffTargets.forEach(e=>{if(e.hp>0)applyStatus(e,k,fx[k]);});
  });
  if(hasRelic('tome')&&d.type==='Power')this.drawCards(1);
  if(d.type==='Attack'&&hasRelic('blackblood'))healPlayer(1);
  // discard/exhaust
  if(fx.exhaust||d.exhaust)this.exhaust.push(c);else this.discard.push(c);
  this.pending=null;
  this.renderAll();
  this.checkWin();
 },
 pickEnemy(idx){
  if(this.pending===null)return;
  const i=this.pending;this.pending=null;
  this.playCard(i,this.enemies[idx]);
 },
 checkWin(){
  if(!this.alive().length&&!this.busy){this.busy=true;setTimeout(()=>this.endCombat(true),400);return true;}
  return false;
 },
 endTurn(){
  if(this.busy||this.pending!==null&&this.pending!==null)return;
  this.busy=true;
  // doubt
  if(this.hand.some(c=>c.id==='doubt')){damagePlayer(1,{pierce:true});}
  // ethereal exhaust
  this.hand.forEach(c=>{const d=cardDef(c);if(d.ethereal||d.type==='Status')this.exhaust.push(c);else this.discard.push(c);});
  this.hand=[];
  // burn on player at end of turn
  if(S.player.st.burn){damagePlayer(S.player.st.burn,{pierce:true});S.player.st.burn=Math.floor(S.player.st.burn/2);}
  this.renderAll();
  setTimeout(()=>this.enemyTurn(),350);
 },
 enemyTurn(){
  try{
  for(const e of this.enemies){
   if(e.hp<=0)continue;
   // start of enemy turn: durations, bleed, poison
   ['weak','vuln','intangible'].forEach(k=>{if(e.st[k])e.st[k]--;});
   if(e.st.strDown)e.st.strDown=0;
   if(e.st.bleed){e.hp-=e.st.bleed;e.st.bleed=Math.floor(e.st.bleed/2);if(e.hp<=0){log(e.name+' bleeds out!');this.renderEnemies();continue;}}
   if(e.st.poison){e.hp-=e.st.poison;e.st.poison--;if(e.hp<=0){log(e.name+' succumbs to poison!');this.renderEnemies();continue;}}
   // boss phase check
   const d=e.def;
   if(d.phase2&&e.phase===1&&e.hp<=d.phase2.hp){e.phase=2;e.pat=d.phase2.pat;e.pi=0;log(e.name+' ENRAGES!');}
   if(d.phase2&&e.phase===2&&d.phase2.gainStr)e.st.str=(e.st.str||0)+d.phase2.gainStr;
   const step=e.pat[e.pi%e.pat.length];e.pi++;
   this.execStep(e,step);
   // burn at end of enemy turn
   if(e.st.burn&&e.hp>0){e.hp-=e.st.burn;e.st.burn=Math.floor(e.st.burn/2);if(e.hp<=0)log(e.name+' burns to ash!');}
   if(S.hp<=0)return;
  }
  }catch(err){if(err!=='dead')throw err;return;}
  this.enemies=this.enemies.filter(e=>e.hp>0||e.def.boss);
  this.renderAll();
  if(this.checkWin())return;
  this.busy=false;
  this.startPlayerTurn();
 },
 execStep(e,step){
  step=step||e.pat[e.pi%e.pat.length];
  e.pi++;
  if(step.t==='attack'){
   if(step.block){e.block+=step.block;log(e.name+' gains '+step.block+' Block.');}
   if(step.heal){e.hp=Math.min(e.maxhp,e.hp+step.heal);log(e.name+' heals '+step.heal+' HP.');}
   const hits=step.hits||1;
   for(let h=0;h<hits;h++){
    if(e.hp<=0||S.hp<=0)break;
    const dmg=calcDmg(step.dmg,e,S.player);
    log(e.name+' attacks for '+dmg+'.');
    damagePlayer(dmg);
   }
   if(step.debuff)this.applyDebuffs(step.debuff);
  }else if(step.t==='defend'){
   e.block+=step.block;log(e.name+' gains '+step.block+' Block.');
   if(step.str)e.st.str=(e.st.str||0)+step.str;
   if(step.regen)e.st.regen=(e.st.regen||0)+step.regen;
   if(step.heal){e.hp=Math.min(e.maxhp,e.hp+step.heal);log(e.name+' heals '+step.heal+' HP.');}
   if(step.debuff){if(step.debuff.selfVuln){e.st.vuln=(e.st.vuln||0)+step.debuff.selfVuln;log(e.name+' exposes itself.');}else this.applyDebuffs(step.debuff);}
  }else if(step.t==='buff'){
   const f=step.fx;
   if(f.str)e.st.str=(e.st.str||0)+f.str;
   if(f.strAll)this.enemies.forEach(x=>{if(x.hp>0)x.st.str=(x.st.str||0)+f.strAll;});
   if(f.block)e.block+=f.block;
   if(f.regen)e.st.regen=(e.st.regen||0)+f.regen;
   if(f.intangible)e.st.intangible=(e.st.intangible||0)+f.intangible;
   log(e.name+' buffs itself.');
  }else if(step.t==='debuff'){
   if(step.fx.selfVuln){e.st.vuln=(e.st.vuln||0)+step.fx.selfVuln;log(e.name+' exposes itself.');}
   else this.applyDebuffs(step.fx);
  }else if(step.t==='summon'){
   const d=e.key==='lich'?{max:2,types:['skelmin']}:e.key==='plague'?{max:1,types:['plaguerat']}:e.key==='archdemon'?{max:1,types:['hellhound']}:{max:9,types:[step.en]};
   const minions=this.enemies.filter(x=>x.hp>0&&x.def.minion);
   const canSummon=d.max?minions.length<d.max:true;
   if(canSummon){
    const key=step.en||pick(d.types);
    const nd=ENEMIES[key];
    this.enemies.push({key,name:nd.name,icon:nd.icon,hp:nd.hp,maxhp:nd.hp,block:0,st:{},pat:nd.pat,pi:0,def:nd,minion:true,phase:1});
    log(e.name+' summons a '+nd.name+'!');
   }else{
    log(e.name+' cannot summon — attacks instead!');
    const dmg=calcDmg(8,e,S.player);damagePlayer(dmg);
   }
  }
 },
 applyDebuffs(fx){
  if(fx.weak){S.player.st.weak=(S.player.st.weak||0)+fx.weak;log('You are weakened!');}
  if(fx.vuln){S.player.st.vuln=(S.player.st.vuln||0)+fx.vuln;log('You are vulnerable!');}
  if(fx.bleed){S.player.st.bleed=(S.player.st.bleed||0)+fx.bleed;log('You bleed!');}
  if(fx.burn){S.player.st.burn=(S.player.st.burn||0)+fx.burn;log('You are set on fire!');}
  if(fx.poison){S.player.st.poison=(S.player.st.poison||0)+fx.poison;log('You are poisoned!');}
 },
 intentText(e){
  const step=e.pat[e.pi%e.pat.length];
  const effStr2=effStr(e);
  if(step.t==='attack'){
   let d=step.dmg+effStr2;if(e.st.weak)d=Math.floor(d*0.75);if(S.player.st.vuln)d=Math.floor(d*1.5);
   let s=(step.hits>1?step.hits+'×':'')+d+' ⚔️';
   if(step.block)s+=' +🛡️';if(step.heal)s+=' +❤️';
   if(step.debuff){if(step.debuff.weak)s+=' 🌑';if(step.debuff.vuln)s+=' 🎯';if(step.debuff.bleed)s+=' 🩸';if(step.debuff.burn)s+=' 🔥';if(step.debuff.poison)s+=' ☠️';}
   return s;
  }
  if(step.t==='defend')return step.block+' 🛡️'+(step.str?' +💪':'')+(step.heal?' +❤️':'');
  if(step.t==='buff')return '⬆️ Buff';
  if(step.t==='debuff')return '🔻 Debuff';
  if(step.t==='summon')return '✨ Summon';
  return '?';
 },
 renderAll(){this.renderEnemies();this.renderPlayer();this.renderHand();renderTop();},
 renderEnemies(){
  $('enemies').innerHTML=this.enemies.map((e,i)=>{
   const dead=e.hp<=0;
   const chips=Object.entries(e.st).filter(([k,v])=>v>0).map(([k,v])=>`<span class="chip ${['str','regen','intangible','dex'].includes(k)?'buff':'debuff'}">${{str:'💪',dex:'🏃',regen:'❤️‍🩹',intangible:'👻',weak:'🌑',vuln:'🎯',bleed:'🩸',burn:'🔥',poison:'☠️',strDown:'⬇️'}[k]||k} ${v}</span>`).join('');
   return `<div class="enemy ${dead?'dead':''} ${this.pending!==null&&!dead?'targetable':''}" ${this.pending!==null&&!dead?`onclick="Combat.pickEnemy(${i})"`:''}>
    <div class="intent">${dead?'':this.intent(e)}</div>
    <div class="sprite">${e.icon}</div>
    <div>${e.name}</div>
    <div class="ehpbar"><div style="width:${Math.max(0,e.hp/e.maxhp*100)}%"></div></div>
    <div>${Math.max(0,e.hp)}/${e.maxhp} ${e.block>0?`<span class="eblock">🛡️${e.block}</span>`:''}</div>
    <div class="statchips">${chips}</div></div>`;
  }).join('');
 },
 intent(e){
  return `<span class="intent">${this.intentText(e)}</span>`;
 },
 intentText(e){
  const step=e.pat[e.pi%e.pat.length];
  const eff=effStr(e);
  if(step.t==='attack'){
   let d=step.dmg+eff;if(e.st.weak)d=Math.floor(d*0.75);if(S.player.st.vuln)d=Math.floor(d*1.5);
   return (step.hits>1?step.hits+'× ':'')+d+'⚔️'+(step.block?' 🛡️':'')+(step.heal?' ❤️':'')+(step.debuff?' 🔻':'');
  }
  if(step.t==='defend')return step.block+'🛡️'+(step.str?' 💪':'')+(step.regen?' ❤️‍🩹':'')+(step.heal?' ❤️':'')+(step.debuff?' 🔻':'');
  if(step.t==='buff')return '⬆️';
  if(step.t==='debuff')return '🔻';
  if(step.t==='summon')return '✴️';
  return '?';
 },
 renderPlayer(){
  const p=S.player;
  const chips=Object.entries(p.st).filter(([k,v])=>v>0).map(([k,v])=>`<span class="chip ${['str','dex','regen','intangible'].includes(k)?'buff':'debuff'}">${{str:'💪',dex:'🛡️+',regen:'❤️‍🩹',intangible:'👻',weak:'🌑',vuln:'🎯',bleed:'🩸',burn:'🔥',poison:'☠️'}[k]||k} ${v}</span>`).join('');
  const powers=Object.keys(this.powers).map(k=>`<span class="chip buff">${{soulSiphon:'🩸Siphon',bloodPact:'🩸Pact',umbral:'🌑Mastery',hellAura:'🔥Aura'}[k]}</span>`).join('');
  $('pstats').innerHTML=`<div style="text-align:center">${CLASSES[S.cls].icon} <b>You</b><div class="hpbar" style="width:160px;margin:2px auto"><div style="width:${Math.max(0,S.hp/S.maxhp*100)}%"></div><span>${Math.max(0,S.hp)}/${S.maxhp}</span></div><span class="eblock">${p.block>0?'🛡️ '+p.block:''}</span><div class="statchips">${chips}${powers}</div></div>`;
  $('etext').textContent=S.player.energy+'/3';
 },
 renderHand(){
  const h=$('hand');
  h.innerHTML=this.hand.map((c,i)=>{
   const d=cardDef(c);
   const unplay=(!d.unplayable&&cardCost(c)>S.player.energy)?'unplayable':'';
   return cardHTML(c,unplayable2(c,this)).replace('data-cid','data-idx="'+i+'" data-cid');
  }).join('');
  h.querySelectorAll('.card').forEach((el,i)=>{
   el.onclick=()=>Combat.clickCard(i);
  });
  function unplayable2(c){const d=cardDef(c);return d.unplayable?'unplayable':(cardCost(c)>S.player.energy?'unplayable':'');}
 },
 endCombat(won,fled){
  // Ashen Armor
  if(hasRelic('ashenarmor'))healPlayer(6);
  if(!won){afterNode();return;}
  if(this.special){const g=S.noGold?0:this.special.gold;S.gold+=g;log('You loot '+g+' Gold!');this.special=null;renderTop();afterNode();return;}
  if(this.isBoss){showBossRewards();return;}
  // rewards
  const elite=this.enemies.some(e=>e.def.elite);
  let gold=(elite?25+rnd(11):10+rnd(11))+(hasRelic('greedyimp')?10:0);
  if(S.noGold)gold=0;
  S.gold+=gold;
  S._relicDrop=elite?randomRelic():null;
  if(S._relicDrop)S.relics.push(S._relicDrop);
  S._potionDrop=Math.random()<(elite?0.7:0.4)?pick(POTIONKEYS):null;
  if(S._potionDrop)givePotion(S._potionDrop);
  showRewards({gold,elite});
 }
};
/* ============ REWARDS ============ */
function rollRarity(elite){const r=Math.random();if(r<(elite?0.2:0.1))return 'Rare';if(r<(elite?0.5:0.4))return 'Uncommon';return 'Common';}
function cardReward(elite,forceRare){
 const pool=classPoolIds();
 const out=[];
 for(let i=0;i<3;i++){
  const rar=(forceRare&&i===0)?'Rare':rollRarity(elite);
  let cands=pool.filter(id=>CARDS[id].rarity===rar);
  if(!cands.length)cands=pool.filter(id=>CARDS[id].rarity==='Common');
  out.push(pick(cands));
 }
 return out;
}
function showRewards(rw){
 showScreen('reward');
 const choices=cardReward(rw.elite,false);
 const potionDrop=!!S._potionDrop;
 let html=`<h2>🏆 Victory!</h2><p>You loot <b style="color:var(--gold)">${rw.gold} Gold</b>.</p>`;
 if(rw.elite)html+=`<p>The elite drops a relic: <b>${RELICS[S._relicDrop].name}</b></p>`;
 if(potionDrop)html+=`<p>You find a potion: <b>${POTIONS[S._potionDrop].icon} ${POTIONS[S._potionDrop].name}</b></p>`;
 html+=`<br><h3>Choose a card</h3><div class="row" id="rwcards">${choices.map(id=>cardHTML(mkCard(id))).join('')}</div>
 <br><button onclick="finishRewards()">Skip</button>`;
 $('rewardpanel').innerHTML=html;
 S._rwChoices=choices;
 $('rwcards').querySelectorAll('.card').forEach((el,i)=>{
  el.classList.add('choicecard');
  el.onclick=()=>{S.deck.push(mkCard(choices[i]));finishRewards();};
 });
}
function finishRewards(){renderTop();nextFloor();}
function showBossRewards(){
 S.hp=S.maxhp;renderTop();
 const boss=ENEMIES[ACTS[S.act].boss];
 S.gold+=50;
 const p1=pick(POTIONKEYS),p2=pick(POTIONKEYS);
 givePotion(p1);givePotion(p2);
 const keys=shuffle(Object.keys(BOSSRELICS)).slice(0,3);
 const choices=cardReward(false,true);
 showScreen('reward');
 $('rewardpanel').innerHTML=`<h2>👑 ${boss.name} defeated!</h2>
 <p>You are fully healed. +50 Gold, +2 potions.</p>
 <br><h3>Choose a Boss Relic</h3><div class="row">${keys.map((k,i)=>`<button class="choicecard rewarditem" onclick="takeBossRelic(${i})" style="height:auto;min-height:110px"><b>${BOSSRELICS[k].icon} ${BOSSRELICS[k].name}</b><br><span class="small">${BOSSRELICS[k].d}</span></button>`).join('')}</div>
 <br><h3>Choose a card (one is Rare)</h3><div class="row" id="rwcards">${choices.map(id=>cardHTML(mkCard(id))).join('')}</div>
 <br><button onclick="finishBoss()">Skip &amp; Continue</button>`;
 S._bossKeys=keys;S._rwChoices=choices;S._bossCardDone=false;S._bossRelicDone=false;
 $('rwcards').querySelectorAll('.card').forEach((el,i)=>{
  el.classList.add('choicecard');
  el.onclick=()=>{if(S._bossCardDone)return;S._bossCardDone=true;S.deck.push(mkCard(choices[i]));el.style.opacity=.3;checkBossDone();};
 });
}
function takeBossRelic(i){
 if(S._bossRelicDone)return;S._bossRelicDone=true;
 const k=S._bossKeys[i];
 S.relics.push(k);
 if(k==='ectoplasm'){S.gold=0;S.noGold=true;}
 if(k==='shadowamulet')S.maxhp-=15;
 if(k==='cursedcrown')S.noCurseRemoval=true;
 renderTop();checkBossDone();
}
function checkBossDone(){if(S._bossCardDone&&S._bossRelicDone)finishBoss();}
function finishBoss(){renderTop();nextFloor();}
/* ============ INIT ============ */
(function init(){
 $('classpick').innerHTML=Object.entries(CLASSES).map(([k,c])=>
  `<div class="classcard panel" onclick="startRun('${k}')"><div class="big">${c.icon}</div><h2>${c.name}</h2><p class="small">${c.desc}</p></div>`).join('');
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"z-ai/glm-5.3-flash","provider":"openrouter","persona":"full","sessionId":"6bc782ab-bbf8-4e87-92bb-3b5e158a2475","tokensIn":4213595,"tokensOut":32126,"tokensTotal":4245721,"cost":0.15056666500000002,"turns":53,"toolCalls":56,"failedToolCalls":4,"timestamp":"2026-08-28T03:42:47.892Z"} -->