Shadows
GPT 5.6 Luna Pro · full

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 33,945 bytes · SHA-256 05cd50dd2438
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Shadows of the Abyss</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700;900&family=Inter:wght@400;600;700&display=swap');
:root{--bg:#090b12;--panel:#121725;--panel2:#181e30;--ink:#e8e9f2;--muted:#8990a6;--red:#c44f62;--gold:#d7aa59;--violet:#8e6bd3;--cyan:#6bc1c8;--green:#76bd83;--line:#293149;--shadow:0 18px 50px #0009}
*{box-sizing:border-box}body{margin:0;background:radial-gradient(ellipse at 50% -20%,#23233a 0,#0c0e17 48%,#07080c 100%);color:var(--ink);font-family:Inter,system-ui;min-height:100vh}body:before{content:"";position:fixed;inset:0;pointer-events:none;opacity:.12;background-image:linear-gradient(120deg,#fff1 1px,transparent 1px),linear-gradient(30deg,#fff1 1px,transparent 1px);background-size:90px 90px;mask-image:linear-gradient(#000,transparent)}
button{font:inherit;color:inherit;cursor:pointer;border:1px solid var(--line);background:#1b2234;border-radius:7px;padding:10px 15px;transition:.18s}button:hover{transform:translateY(-2px);border-color:#8a769c;background:#272e45}button:disabled{opacity:.38;cursor:not-allowed;transform:none}.primary{background:linear-gradient(135deg,#9f3f58,#57306f);border-color:#c26672}.gold{color:var(--gold)}.small{font-size:12px}.muted{color:var(--muted)}h1,h2,h3,.brand,.title{font-family:Cinzel,serif;letter-spacing:.04em}
#app{max-width:1400px;margin:auto;padding:22px 30px 44px}.topbar{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);padding-bottom:16px;margin-bottom:18px}.brand{font-size:20px;font-weight:900}.brand em{font-style:normal;color:var(--red)}.runstats{display:flex;gap:18px;color:var(--muted);font-size:13px}.runstats b{color:var(--ink)}
.screen{display:none}.screen.active{display:block}.hero{min-height:75vh;display:grid;place-items:center;text-align:center}.hero-inner{max-width:720px}.sigil{font-size:72px;color:#a34f6c;text-shadow:0 0 38px #a34f6c88;margin-bottom:4px}.title{font-size:clamp(38px,7vw,76px);margin:0;color:#f0eaf1}.subtitle{font-size:16px;color:#a6a2b8;line-height:1.7;margin:16px 0 30px}.class-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:15px;margin-top:24px}.class-card{padding:23px;text-align:left;background:linear-gradient(145deg,#171b2c,#10131f);border:1px solid #303850;border-radius:11px;min-height:218px}.class-card:hover{border-color:var(--red);box-shadow:0 0 30px #9e466322}.class-card h3{margin:7px 0 5px}.class-card p{font-size:13px;line-height:1.55;color:var(--muted)}.crest{font-size:29px}.tag{display:inline-block;font-size:10px;text-transform:uppercase;letter-spacing:.12em;border:1px solid #674457;color:#da8b8d;border-radius:20px;padding:4px 8px}
.section-head{display:flex;justify-content:space-between;align-items:end;margin:18px 0}.section-head h2{margin:0}.map{position:relative;background:linear-gradient(180deg,#11182a,#0e121d);border:1px solid var(--line);border-radius:13px;padding:24px 15px;box-shadow:var(--shadow)}.map:before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 50% 5%,#5e3e7433,transparent 55%);pointer-events:none}.maprow{display:flex;justify-content:center;gap:26px;min-height:58px;position:relative}.maprow:after{content:"";position:absolute;left:22%;right:22%;top:29px;border-top:1px dashed #3f4660;z-index:0}.node{z-index:1;width:68px;height:45px;border-radius:9px;background:#1a2133;border:1px solid #3c465f;display:grid;place-items:center;font-size:20px;position:relative}.node span{position:absolute;top:48px;font-size:9px;color:#8d94a9;text-transform:uppercase;letter-spacing:.08em;white-space:nowrap}.node.current{border-color:var(--gold);box-shadow:0 0 18px #d7aa5955}.node.available{border-color:#b87792;background:#302037;cursor:pointer}.node.lock{opacity:.35}.node.boss{background:#4d202f;border-color:#c45b72}.mapnote{padding:14px 5px 0;text-align:center;color:var(--muted);font-size:12px}
.combat-grid{display:grid;grid-template-columns:1fr 285px;gap:18px}.combat-main{background:linear-gradient(#171929,#10131e);border:1px solid var(--line);border-radius:13px;padding:20px;min-height:600px}.combat-side{background:#111624;border:1px solid var(--line);border-radius:13px;padding:17px;height:max-content}.encounter{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em}.combat-head{display:flex;justify-content:space-between;align-items:center}.combat-head h2{margin:7px 0 18px}.enemy-row{display:flex;justify-content:center;gap:16px;min-height:260px;align-items:center}.enemy{width:180px;text-align:center;position:relative}.enemy.dead{filter:grayscale(1);opacity:.28}.enemy-art{width:115px;height:115px;margin:auto;border-radius:50%;display:grid;place-items:center;font-size:56px;background:radial-gradient(circle,#4b293d,#1a1522 70%);border:2px solid #70445b;box-shadow:0 0 28px #842e4a55}.enemy h3{margin:10px 0 5px;font-size:16px}.intent{font-size:12px;color:#e2aa72;margin:7px}.hpbar,.playerbar{height:8px;background:#292d3a;border-radius:10px;overflow:hidden}.hpfill{height:100%;background:linear-gradient(90deg,#c54a5d,#e8846b);transition:.3s}.block{color:#79bdcc;font-size:12px}.hand{display:flex;gap:9px;overflow-x:auto;padding:14px 2px 10px;min-height:175px}.card{flex:0 0 145px;min-height:154px;border:1px solid #44506c;border-radius:8px;background:linear-gradient(160deg,#252e45,#151b2a);padding:11px;position:relative;cursor:pointer;box-shadow:0 7px 13px #0005}.card:hover{transform:translateY(-8px);border-color:#c27b86}.card.attack{border-top:3px solid #b85a64}.card.skill{border-top:3px solid #5c9ab0}.card.power{border-top:3px solid #b07bd0}.card.unplayable{opacity:.58;cursor:not-allowed}.cost{position:absolute;top:7px;right:9px;color:var(--gold);font-weight:bold}.card-name{font-family:Cinzel;font-weight:bold;font-size:12px;padding-right:18px}.card-type{font-size:9px;color:#a8aec3;text-transform:uppercase;margin:7px 0}.card-desc{font-size:11px;line-height:1.45;color:#d1d1df}.card-rarity{position:absolute;bottom:8px;left:11px;font-size:9px;color:#8f97ae}.combat-actions{display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--line);padding-top:11px}.energy{font-family:Cinzel;font-size:24px;color:var(--gold)}.log{height:100px;overflow:auto;font-size:11px;color:#9ca3b7;line-height:1.6;margin-top:16px;border-top:1px solid var(--line);padding-top:10px}.side-block{border-bottom:1px solid var(--line);padding-bottom:14px;margin-bottom:14px}.side-block h3{font-size:13px;margin:0 0 12px}.statline{display:flex;justify-content:space-between;font-size:13px;margin:8px 0}.status{display:inline-block;background:#262d43;border-radius:4px;padding:4px 6px;margin:2px;font-size:10px}.status.red{color:#e47d85}.status.blue{color:#7fc6d1}.status.gold{color:#e0b75f}
.modal-wrap{position:fixed;inset:0;background:#05060bcc;display:grid;place-items:center;z-index:10;padding:20px}.modal{width:min(760px,100%);max-height:88vh;overflow:auto;background:linear-gradient(145deg,#1a1d30,#10131f);border:1px solid #414967;border-radius:13px;padding:25px;box-shadow:0 22px 80px #000}.modal h2{margin-top:0}.choices,.reward-grid,.wares{display:grid;grid-template-columns:repeat(3,1fr);gap:11px}.choice,.reward,.ware{background:#1b2234;padding:15px;border-radius:8px;text-align:left;border:1px solid #36405a}.choice h3,.reward h3,.ware h3{font-size:14px;margin:0 0 8px}.choice p,.reward p,.ware p{font-size:12px;line-height:1.45;color:#aab0c0}.footer-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:20px}.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:#272136;border:1px solid #9d6378;padding:12px 19px;border-radius:7px;z-index:20;box-shadow:var(--shadow)}
@media(max-width:800px){#app{padding:15px}.class-grid,.combat-grid{grid-template-columns:1fr}.combat-side{order:-1}.enemy-row{gap:3px}.enemy{width:32%}.enemy-art{width:75px;height:75px;font-size:36px}.choices,.reward-grid,.wares{grid-template-columns:1fr}.runstats{gap:8px;font-size:11px}.hand{min-height:160px}}
</style></head><body><div id="app"><div class="topbar"><div class="brand">SHADOWS <em>OF THE ABYSS</em></div><div id="runstats" class="runstats"></div></div><section id="start" class="screen active"><div class="hero"><div class="hero-inner"><div class="sigil">☽</div><h1 class="title">Shadows of the Abyss</h1><div class="subtitle">The old gods are silent. The dead have learned to pray.<br/>Choose the ruin that lives inside you, and climb.</div><div class="tag">A single-player descent</div><div class="class-grid" id="classes"></div></div></div></section><section id="map" class="screen"><div class="section-head"><div><div class="encounter">Act <span id="actnum"></span> · The dying world</div><h2 id="actname">The Crypts</h2></div><div class="muted small">Choose your path upward</div></div><div class="map" id="mapgrid"></div><div class="mapnote">Each choice is permanent. Rest before the abyss, if you can.</div></section><section id="combat" class="screen"><div class="combat-grid"><main class="combat-main"><div class="combat-head"><div><div class="encounter" id="encounter-type">Combat</div><h2 id="combat-title">The dead awaken</h2></div><div class="muted">Turn <b id="turnnum">1</b></div></div><div id="enemyrow" class="enemy-row"></div><div class="hand" id="hand"></div><div class="combat-actions"><div><span class="energy" id="energy">3</span> <span class="muted">/ 3 energy</span></div><button id="endturn" class="primary">End Turn ↗</button></div><div id="combatlog" class="log"></div></main><aside class="combat-side"><div class="side-block"><h3>YOUR VESSEL</h3><div class="statline"><span>Vitality</span><b id="hptext"></b></div><div class="playerbar"><div id="playerhp" class="hpfill"></div></div><div id="playerstatuses" style="margin-top:8px"></div></div><div class="side-block"><h3>RELICS</h3><div id="relics" class="muted small">None</div></div><div class="side-block"><h3>POTIONS <span class="muted">(click to use)</span></h3><div id="potions" class="muted small">Empty</div></div><div><h3>DECK <span id="deckcount" class="muted"></span></h3><button id="viewdeck">View deck</button></div></aside></div></section><section id="gameover" class="screen"><div class="hero"><div><div class="sigil">✦</div><h1 class="title" id="endtitle">The Abyss Claims You</h1><p class="subtitle" id="endcopy"></p><button class="primary" onclick="location.reload()">Begin another descent</button></div></div></section></div><div id="modalroot"></div><script>
const $=s=>document.querySelector(s), R=(a,b)=>Math.floor(Math.random()*(b-a+1))+a, pick=a=>a[Math.floor(Math.random()*a.length)], esc=s=>String(s).replace(/[&<>]/g,c=>({'&':'&','<':'<','>':'>'}[c]));
const classes={blood:{name:'Blood Reaper',crest:'♜',color:'red',desc:'Sacrifice vitality to turn pain into power. Bleed your enemies dry.',sig:'Sanguine Strike'},shadow:{name:'Shadowmancer',crest:'☾',color:'violet',desc:'Bend weakness and darkness. Vanish between the turns of fate.',sig:'Shadow Bolt'},hell:{name:'Hellforged',crest:'♨',color:'gold',desc:'Set the world ablaze. Burn brighter, hit harder, summon the damned.',sig:'Firebolt'}};
let G={};
const basic={Strike:{name:'Strike',type:'Attack',cost:1,rarity:'Basic',desc:'Deal 6 damage.',up:'Deal 8 damage.',effect:'strike'},Defend:{name:'Defend',type:'Skill',cost:1,rarity:'Basic',desc:'Gain 5 Block.',up:'Gain 8 Block.',effect:'defend'}};
const pools={blood:[['Sanguine Strike','Attack',1,'Common','Deal 8 damage. Heal 2 HP.','sang'],['Hemorrhage','Attack',1,'Common','Deal 5 damage. Apply 3 Bleed.','hem'],['Bloodletting','Attack',1,'Uncommon','Lose 3 HP. Deal 12 damage.','bloodlet'],['Crimson Eruption','Attack',2,'Rare','Deal 8 damage to ALL enemies. Lose 2 HP.','aoe'],['Crimson Shield','Skill',1,'Common','Gain 6 Block. Lose 2 HP.','shield'],['Blood Ward','Skill',1,'Common','Gain 3 Block. Gain 1 Regeneration.','ward'],['Sacrificial Barrier','Skill',2,'Uncommon','Gain 10 Block. Lose 5 HP.','barrier'],['Vital Surge','Skill',0,'Rare','Gain Block equal to missing HP. Exhaust.','vitals'],['Life Tap','Skill',0,'Common','Draw 2 cards. Lose 3 HP.','drawlife'],['Rend Flesh','Skill',1,'Uncommon','Apply 5 Bleed to ALL enemies. Lose 2 HP.','rend'],['Soul Siphon','Power',2,'Uncommon','At start of turn, gain 1 Energy and lose 1 HP.','siphon'],['Blood Pact','Power',1,'Rare','Whenever you lose HP from a card, gain 1 Strength.','pact']],shadow:[['Shadow Bolt','Attack',1,'Common','Deal 6 damage. Apply 1 Weak.','bolt'],['Grasp of Despair','Attack',1,'Common','Deal 4 damage. Apply 1 Vulnerable.','grasp'],['Nightmare Lash','Attack',2,'Uncommon','Deal 7 damage. If enemy has Weak, deal 7 more.','night'],['Eternal Darkness','Attack',2,'Rare','Deal 3 damage to a random enemy 3 times.','dark'],['Cloak of Shadows','Skill',1,'Common','Gain 5 Block. Draw 1 card.','cloak'],['Haunting Mist','Skill',1,'Common','Gain 7 Block. Apply 1 Weak to ALL enemies.','mist'],['Phantom Armor','Skill',1,'Uncommon','Gain 8 Block. Next turn, gain 2 Dexterity.','armor'],['Whispering Shroud','Skill',1,'Rare','Gain 4 Block. Next turn, gain 4 Block.','shroud'],['Shadowform','Skill',2,'Uncommon','Gain 2 Intangible. Exhaust.','form'],['Curse of Misfortune','Skill',1,'Common','Enemy loses 3 Strength. Apply 1 Vulnerable.','misfortune'],['Dark Ritual','Skill',0,'Uncommon','Gain 2 Energy. Shuffle a Dazed into your draw pile.','ritual'],['Umbral Mastery','Power',2,'Rare','At start of turn, apply 1 Weak to ALL enemies.','mastery']],hell:[['Firebolt','Attack',1,'Common','Deal 7 damage. Apply 2 Burn.','fire'],['Hellfire Blast','Attack',2,'Uncommon','Deal 9 damage to ALL enemies.','blast'],['Searing Strike','Attack',1,'Common','Deal 6 damage. Gain 1 Energy next turn.','sear'],['Immolate','Attack',2,'Rare','Deal 12 damage. Exhaust.','immolate'],['Obsidian Skin','Skill',1,'Common','Gain 7 Block.','skin'],['Flame Ward','Skill',1,'Common','Gain 5 Block. Apply 2 Burn to a random enemy.','flameward'],['Demonic Resilience','Skill',1,'Uncommon','Gain 4 Block. Heal 2 HP.','resilience'],['Brimstone Shield','Skill',1,'Rare','Gain 3 Block. Gain 2 Strength this turn.','brim'],['Summon Imp','Skill',1,'Common','Add an Imp Claw to your hand. Exhaust.','summon'],['Infernal Pact','Skill',0,'Uncommon','Lose 2 HP. Gain 2 Energy. Add an Imp Claw.','pacthell'],['Hellfire Aura','Power',2,'Rare','At start of turn, deal 3 damage to ALL enemies.','aura'],['Soul Burn','Skill',1,'Uncommon','Apply 8 Burn to an enemy. Draw 1 card.','soulburn']]};
function cardObj(x,up=false){if(x==='Strike'||x==='Defend'){let b=basic[x];return {...b,upgraded:up,desc:up?b.up:b.desc,id:Math.random()}} let q=pools[G.cls].find(v=>v[0]===x)||pools[G.cls].find(v=>v[5]===x);return {name:q[0]+(up?' +':''),base:q[0],type:q[1],cost:up&&q[0]==='Soul Siphon'?1:q[2],rarity:q[3],desc:up?upgradeDesc(q[4]):q[4],effect:q[5],upgraded:up,id:Math.random()}}
function upgradeDesc(s){return s.replace(/6/g,'8').replace(/8/g,'10').replace(/5/g,'7').replace(/7/g,'9').replace(/3/g,'4').replace(/2/g,'3').replace(/4/g,'5').replace(/12/g,'15')}
function init(){for(const [k,v] of Object.entries(classes)){$('#classes').innerHTML+=`<button class="class-card" onclick="startRun('${k}')"><div class="crest">${v.crest}</div><h3>${v.name}</h3><span class="tag">${v.sig}</span><p>${v.desc}</p><div class="small gold">Choose this path →</div></button>`}}
function startRun(cls){G={cls,act:1,floor:1,hp:70,maxhp:70,gold:99,deck:[],relics:[],potions:[],node:null,turn:0,energy:0,block:0,strength:0,dex:0,status:{},draw:[],discard:[],exhaust:[],enemies:[],log:[]};G.deck=['Strike','Strike','Strike','Strike','Strike','Defend','Defend','Defend','Defend',classes[cls].sig].map(x=>cardObj(x,false));show('map');renderMap()}
function show(id){document.querySelectorAll('.screen').forEach(x=>x.classList.remove('active'));$('#'+id).classList.add('active');renderStats()}
function renderStats(){if(!G.cls){$('#runstats').innerHTML='';return} $('#runstats').innerHTML=`<span>HP <b>${G.hp}/${G.maxhp}</b></span><span class="gold">✦ <b>${G.gold}</b></span><span>Act ${G.act} · Floor ${G.floor}</span>`}
const layout=['','enemy','enemy','event','shop','enemy','rest','enemy','branch','event','enemy','elite','enemy','rest','boss'];
function renderMap(){renderStats();$('#actnum').textContent=G.act;$('#actname').textContent=['','The Crypts','Blighted Woods','Abyssal Spire'][G.act];let h='';for(let f=14;f>=2;f--){let type=layout[f];let cur=G.floor===f;let avail=f===G.floor+1;let nodes=type==='branch'?['elite','enemy']: [type];h+=`<div class="maprow">${nodes.map((n,i)=>`<button class="node ${cur?'current':''} ${avail?'available':''} ${n==='boss'?'boss':''}" ${avail?`onclick="enterNode(${f},'${n}')"`:''}>${icon(n)}<span>${n==='branch'?'path':n}</span></button>`).join('')}</div>`}h+=`<div class="maprow"><div class="node current">⌂<span>start</span></div></div>`;$('#mapgrid').innerHTML=h}
function icon(t){return {enemy:'♱',elite:'☠',rest:'♨',shop:'♢',event:'✧',boss:'☽'}[t]||'•'}
function enterNode(f,type){G.floor=f;G.node=type;if(type==='rest')rest();else if(type==='shop')shop();else if(type==='event')event();else combat(type==='elite'?'elite':type==='boss'?'boss':'normal')}
function combat(kind){show('combat');G.turn=1;G.energy=3;G.block=0;G.strength=0;G.dex=0;G.status={};G.draw=[...G.deck];shuffle(G.draw);G.hand=[];G.discard=[];G.exhaust=[];G.log=[];G.kind=kind;G.enemies=makeEnemies(kind);if(G.relics.includes('Shadow Cloak'))G.status.intangible=1;log('The enemy reveals its intent.');startTurn();G.hand=G.deck.slice(0,5);G.draw=G.deck.slice(5);renderCombat()}
function makeEnemies(kind){let act=G.act; if(kind==='boss')return [{name:['','Lich King','Eldritch Treant','Archdemon'][act],hp:[0,65,75,85][act],maxhp:[0,65,75,85][act],pat:act===1?['summon','attack','defend']:act===2?['attack','defend','multi']:['summon','attack','defend'],i:0,emoji:act===1?'♛':act===2?'♨':'♠',block:0,str:0}];let sets=act===1?{normal:[['Skeleton Warrior',22,['attack6','attack6','defend8'],'☠'],['Dark Cultist',20,['buff','attack5','attack5'],'♟'],['Rat Swarm',15,['multi'],'☄']],elite:[['Tomb Guardian',42,['bash','multi','attack12'],'⛨'],['Blood Witch',36,['attack5','debuff','attack7'],'♛']]}:act===2?{normal:[['Wraith',30,['attack7','attack7','defend10'],'☽'],['Corrupted Beast',38,['attack12','defend5','attack12'],'♞'],['Thorned Horror',25,['multi','bleed','attack8'],'✹']],elite:[['Treant Protector',50,['attack9','debuff','multi'],'♣'],['Plague Shaman',42,['poison','summon','attack8'],'♟']]}:{normal:[['Hellhound',26,['burn8','attack8','defend9'],'♨'],['Shadow Demon',30,['attack7','attack7','intang'],'☾'],['Abyssal Cultist',24,['buff','attack7','attack7'],'♟']],elite:[['Flamespawn',48,['burn9','defend10','multi'],'♨'],['Dread Knight',55,['attack12','bleed','defend12'],'⚔']]};let list=sets[kind]||sets.normal;let q=pick(list);return [{name:q[0],hp:q[1],maxhp:q[1],pat:q[2],i:0,emoji:q[3],block:0,str:0}]}
function startTurn(){G.block=0;G.energy=3;G.turn++;if(G.turn===2)G.turn=1;G.hand=G.hand||[];if(G.draw.length<5){G.draw=G.draw.concat(G.discard.splice(0));shuffle(G.draw)}for(let i=0;i<5&&G.draw.length;i++)G.hand.push(G.draw.pop());if(G.relics.includes('Bleeding Edge')&&G.enemies.length)G.enemies[0].bleed=(G.enemies[0].bleed||0)+1;if(G.status.siphon){G.energy++;hurt(1,'Soul Siphon')}if(G.status.mastery)G.enemies.forEach(e=>e.weak=(e.weak||0)+1);if(G.status.aura)G.enemies.forEach(e=>damageEnemy(e,3))}
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]]}}
function renderCombat(){if((!G.hand||!G.hand.length)&&G.deck&&G.deck.length){G.hand=G.deck.slice(0,5);G.draw=G.deck.slice(5)}renderStats();$('#combat-title').textContent=G.enemies.map(e=>e.name).join(' & ');$('#encounter-type').textContent=G.kind==='boss'?'BOSS ENCOUNTER':G.kind.toUpperCase()+' ENCOUNTER';$('#turnnum').textContent=G.turn;$('#energy').textContent=G.energy;$('#hptext').textContent=`${Math.max(0,G.hp)} / ${G.maxhp} · ${G.block} block`;$('#playerhp').style.width=Math.max(0,G.hp/G.maxhp*100)+'%';$('#playerstatuses').innerHTML=statusHTML();$('#relics').innerHTML=G.relics.join(' · ')||'None';$('#deckcount').textContent=`(${G.deck.length})`;$('#enemyrow').innerHTML=G.enemies.map((e,i)=>`<div class="enemy ${e.hp<=0?'dead':''}"><div class="enemy-art">${e.emoji}</div><h3>${esc(e.name)}</h3><div class="intent">${intent(e)}</div><div class="hpbar"><div class="hpfill" style="width:${Math.max(0,e.hp/e.maxhp*100)}%"></div></div><div class="small">${Math.max(0,e.hp)} / ${e.maxhp} <span class="block">${e.block?' · ◈ '+e.block:''}</span></div></div>`).join('');$('#hand').innerHTML=(G.deck||[]).slice(0,5).map((c,i)=>`<div class="card ${c.type.toLowerCase()} ${['Wound','Dazed','Doubt'].includes(c.name)?'unplayable':''}" onclick="playCard(${i})"><span class="cost">${c.cost}</span><div class="card-name">${esc(c.name)}</div><div class="card-type">${c.type} · ${c.rarity}</div><div class="card-desc">${c.desc}</div><div class="card-rarity">${c.upgraded?'UPGRADED':''}</div></div>`).join('');$('#combatlog').innerHTML=G.log.slice(-7).map(x=>`<div>› ${x}</div>`).join('')}
function intent(e){let p=e.pat[e.i%e.pat.length];return {attack6:'⚔ 6 damage',attack5:'⚔ 5 damage',attack7:'⚔ 7 damage',attack8:'⚔ 8 damage',attack9:'⚔ 9 damage',attack12:'⚔ 12 damage',multi:'⚔ 2 × 4 damage',bash:'⚔ 6 + ◈ 8',defend8:'◈ Gain 8 Block',defend10:'◈ Gain 10 Block',defend5:'◈ Gain 5 Block',defend12:'◈ Gain 12 Block',buff:'✦ Buff',debuff:'☄ Debuff',bleed:'☄ 3 Bleed',burn8:'⚔ 8 + Burn',burn9:'⚔ 9 + Burn',poison:'☄ 4 Poison',intang:'◌ Intangible',summon:'♟ Summon'}[p]||p}
function statusHTML(){return Object.entries(G.status).filter(x=>x[1]>0).map(([k,v])=>`<span class="status ${['weak','vuln','bleed'].includes(k)?'red':'blue'}">${k} ${v}</span>`).join('')||'<span class="muted small">No afflictions</span>'}
function log(s){G.log.push(s)}
function playCard(i){let c=G.hand[i];if(!c||c.type==='Curse'||['Wound','Dazed','Doubt'].includes(c.name))return;if(G.energy<c.cost){toast('Not enough energy.');return}G.energy-=c.cost;G.hand.splice(i,1);let target=G.enemies.find(e=>e.hp>0);let n=c.effect;let up=c.upgraded; if(n==='strike')damageEnemy(target,up?8:6);else if(n==='defend')gainBlock(up?8:5);else if(n==='sang'){damageEnemy(target,up?10:8);heal(up?3:2)}else if(n==='hem'){damageEnemy(target,up?6:5);target.bleed=(target.bleed||0)+(up?4:3)}else if(n==='bloodlet'){hurt(up?2:3,'Bloodletting');damageEnemy(target,up?15:12)}else if(n==='aoe'){G.enemies.forEach(e=>damageEnemy(e,up?10:8));hurt(2,'Crimson Eruption')}else if(n==='shield'){gainBlock(up?8:6);hurt(2,'Crimson Shield')}else if(n==='ward'){gainBlock(up?4:3);G.status.regen=(G.status.regen||0)+(up?2:1)}else if(n==='barrier'){gainBlock(up?13:10);hurt(5,'Sacrificial Barrier')}else if(n==='vitals'){gainBlock(G.maxhp-G.hp+(up?3:0));c.exhaust=true}else if(n==='drawlife'){draw(up?3:2);hurt(3,'Life Tap')}else if(n==='rend'){G.enemies.forEach(e=>e.bleed=(e.bleed||0)+(up?7:5));hurt(2,'Rend Flesh')}else if(n==='siphon'){G.status.siphon=1}else if(n==='pact'){G.status.pact=1}else if(n==='bolt'){damageEnemy(target,up?8:6);target.weak=(target.weak||0)+(up?2:1)}else if(n==='grasp'){damageEnemy(target,up?5:4);target.vuln=(target.vuln||0)+(up?2:1)}else if(n==='night'){damageEnemy(target,(up?9:7)*(target.weak?2:1))}else if(n==='dark'){for(let j=0;j<3;j++)damageEnemy(pick(G.enemies.filter(e=>e.hp>0)),up?4:3)}else if(n==='cloak'){gainBlock(up?7:5);draw(1)}else if(n==='mist'){gainBlock(up?9:7);G.enemies.forEach(e=>e.weak=(e.weak||0)+1)}else if(n==='armor'){gainBlock(up?10:8);G.status.dexnext=up?3:2}else if(n==='shroud'){gainBlock(up?5:4);G.status.blocknext=up?5:4}else if(n==='form'){G.status.intangible=up?3:2;c.exhaust=true}else if(n==='misfortune'){target.str=Math.max(0,(target.str||0)-3);target.vuln=(target.vuln||0)+1}else if(n==='ritual'){G.energy+=2;G.draw.push(cardObj('Dazed'))}else if(n==='mastery'){G.status.mastery=1}else if(n==='fire'){damageEnemy(target,up?9:7);target.burn=(target.burn||0)+(up?3:2)}else if(n==='blast'){G.enemies.forEach(e=>damageEnemy(e,up?12:9))}else if(n==='sear'){damageEnemy(target,up?8:6);G.status.nextenergy=1}else if(n==='immolate'){damageEnemy(target,up?16:12);c.exhaust=true}else if(n==='skin'){gainBlock(up?10:7)}else if(n==='flameward'){gainBlock(up?6:5);pick(G.enemies).burn=(pick(G.enemies).burn||0)+2}else if(n==='resilience'){gainBlock(up?5:4);heal(up?3:2)}else if(n==='brim'){gainBlock(up?4:3);G.strength+=up?3:2}else if(n==='summon'||n==='pacthell'){G.hand.push(cardObj('Imp Claw'));if(n==='pacthell'){hurt(up?1:2,'Infernal Pact');G.energy+=2}}else if(n==='aura'){G.status.aura=up?5:3}else if(n==='soulburn'){target.burn=(target.burn||0)+(up?10:8);draw(1)}if(c.exhaust||c.type==='Power')G.exhaust.push(c);else G.discard.push(c);if(c.type==='Power'&&G.relics.includes('Tome of Dark Rituals'))draw(1);log(`Played ${c.name}.`);if(G.hp<=0)return endRun(false);if(G.enemies.every(e=>e.hp<=0))return victoryCombat();renderCombat()}
function damageEnemy(e,n){if(!e||e.hp<=0)return;let mult=e.vuln?1.5:1;let dmg=Math.floor((n+G.strength)*(G.status.weak?.75:1)*mult);dmg=Math.max(0,dmg-(e.block||0));e.block=Math.max(0,(e.block||0)-dmg);e.hp-=dmg;log(`${e.name} takes ${dmg} damage.`)}
function gainBlock(n){G.block+=n+G.dex+(G.status.blocknext||0);G.status.blocknext=0}
function hurt(n,why){if(G.status.intangible)n=1;G.hp-=n;if(G.relics.includes('Bloodstone')&&n>0)heal(1);if(G.status.pact)G.strength++;log(`${why}: lost ${n} HP.`)}function heal(n){G.hp=Math.min(G.maxhp,G.hp+n)}function draw(n){for(let i=0;i<n;i++){if(!G.draw.length){G.draw=G.discard.splice(0);shuffle(G.draw)}if(G.draw.length)G.hand.push(G.draw.pop())}}
function endTurn(){if(!G.hand)return;G.hand.forEach(c=>{if(c.name==='Doubt')hurt(1,'Doubt');if(c.name==='Wound'||c.name==='Dazed')G.exhaust.push(c);else if(c.name!=='Doubt')G.discard.push(c)});G.hand=[];G.enemies.forEach(enemyTurn);if(G.hp<=0)return endRun(false);if(G.enemies.every(e=>e.hp<=0))return victoryCombat();if(G.status.regen){heal(G.status.regen);G.status.regen--}if(G.status.intangible)G.status.intangible--;G.turn++;startTurn()}
function enemyTurn(e){if(e.hp<=0)return;let p=e.pat[e.i%e.pat.length];e.block=0;if(e.bleed){hurtEnemy(e,e.bleed);e.bleed=Math.floor(e.bleed/2)}if(e.poison){hurtEnemy(e,e.poison);e.poison--}if(p==='attack6')hit(6,e);else if(p==='attack5')hit(5,e);else if(p==='attack7')hit(7,e);else if(p==='attack8')hit(8,e);else if(p==='attack9')hit(9,e);else if(p==='attack12')hit(12,e);else if(p==='multi'){hit(4,e);hit(4,e)}else if(p==='bash'){hit(6,e);e.block=8}else if(p.startsWith('defend'))e.block=+p.replace('defend','');else if(p==='buff')e.str=(e.str||0)+2;else if(p==='debuff'){G.status.weak=(G.status.weak||0)+1;G.status.vuln=(G.status.vuln||0)+1}else if(p==='bleed')G.status.bleed=(G.status.bleed||0)+3;else if(p.startsWith('burn')){hit(+p.match(/\d+/)[0],e);G.status.burn=(G.status.burn||0)+2}else if(p==='poison')G.status.poison=(G.status.poison||0)+4;else if(p==='intang')e.intangible=1;else if(p==='summon'&&G.enemies.length<3)G.enemies.push({name:G.act===3?'Hellhound':'Skeleton Minion',hp:15,maxhp:15,pat:['attack5'],i:0,emoji:'☠',block:0});e.i++}
function hit(n,e){let d=G.status.intangible?1:Math.max(0,n+(e.str||0));d=Math.floor(d*(G.status.vuln?1.5:1));let absorbed=Math.min(G.block,d);G.block-=absorbed;G.hp-=d-absorbed;log(`${e.name} attacks for ${d}; ${absorbed} blocked.`)}function hurtEnemy(e,n){e.hp-=n;log(`${e.name} suffers ${n}.`)}
$('#endturn').onclick=endTurn;
function victoryCombat(){G.gold+=G.kind==='elite'?30:G.kind==='boss'?50:R(10,20);if(G.kind==='boss'){if(G.act===3)return endRun(true);G.act++;G.floor=1;G.hp=G.maxhp;show('map');renderMap();toast('The boss falls. The abyss opens deeper.');return}if(G.kind==='elite')G.relics.push(pick(['Bloodstone','Cursed Amulet','Bleeding Edge','Ashen Armor']));reward()}
function reward(){let cards=[pick(pools[G.cls])[0],pick(pools[G.cls])[0],pick(pools[G.cls])[0]];let html=`<div class="modal-wrap"><div class="modal"><div class="encounter">VICTORY</div><h2>Claim what remains</h2><p class="muted">The dead leave behind <span class="gold">${G.gold} gold</span>. Choose one card.</p><div class="reward-grid">${cards.map(n=>{let c=cardObj(n);return `<button class="reward" onclick="takeCard('${n}')"><h3>${n}</h3><p>${c.rarity} · ${c.type} · ${c.cost} energy</p><p>${c.desc}</p></button>`}).join('')}</div><div class="footer-actions"><button onclick="closeModal();show('map');renderMap()">Skip reward</button></div></div></div>`;$('#modalroot').innerHTML=html}
function takeCard(n){G.deck.push(cardObj(n));closeModal();show('map');renderMap();toast(`${n} joins your deck.`)}
function rest(){let html=`<div class="modal-wrap"><div class="modal"><div class="encounter">REST SITE</div><h2>A quiet place between graves</h2><p class="muted">One choice. Then climb.</p><div class="choices"><button class="choice" onclick="heal(Math.floor(G.maxhp*.25));closeModal();show('map');renderMap();toast('You recover your breath.')"><h3>♨ Rest</h3><p>Heal 25% of max HP.</p></button><button class="choice" onclick="upgradePick()"><h3>⚒ Forge</h3><p>Upgrade a card permanently.</p></button><button class="choice" onclick="removePick()"><h3>⌁ Meditate</h3><p>Remove a card from your deck.</p></button></div></div></div>`;$('#modalroot').innerHTML=html}
function upgradePick(){pickDeck('Choose a card to upgrade',c=>{c.upgraded=true;c.name=c.base?c.base+' +':c.name+' +';c.desc=upgradeDesc(c.desc);closeModal();show('map');renderMap()})}function removePick(){pickDeck('Choose a card to remove',c=>{G.deck=G.deck.filter(x=>x.id!==c.id);closeModal();show('map');renderMap()})}function pickDeck(title,fn){$('#modalroot').innerHTML=`<div class="modal-wrap"><div class="modal"><h2>${title}</h2><div class="wares">${G.deck.map((c,i)=>`<button class="ware" onclick="window._pick(${i})"><h3>${c.name}</h3><p>${c.desc}</p></button>`).join('')}</div></div></div>`;window._pick=i=>fn(G.deck[i])}
function event(){let ev=pick(['altar','spring','dead','library']);let data={altar:['The Forgotten Altar','A blood-stained altar hums with power.','Offer 15 HP','Heal 10 HP, but accept Doubt'],spring:['Cursed Spring','Dark water bubbles beneath the roots.','Bottle a Cursed Brew','Drink: lose 10 HP and cleanse Doubt'],dead:['Restless Dead','Ghosts whisper a bargain from the fog.','Accept: lose 20 HP, upgrade a card','Refuse the dead'],library:['Ancient Library','Dusty tomes contain forbidden knowledge.','Study: lose 5 HP, gain 20 gold','Read: add a class card, gain Doubt']}[ev];$('#modalroot').innerHTML=`<div class="modal-wrap"><div class="modal"><div class="encounter">EVENT</div><h2>${data[0]}</h2><p class="subtitle">${data[1]}</p><div class="choices"><button class="choice" onclick="eventChoice('${ev}',0)"><h3>${data[2]}</h3><p>There is no safe bargain.</p></button><button class="choice" onclick="eventChoice('${ev}',1)"><h3>${data[3]}</h3><p>Some doors only open inward.</p></button><button class="choice" onclick="closeModal();show('map');renderMap()"><h3>Leave</h3><p>Keep walking.</p></button></div></div></div>`}
function eventChoice(ev,n){if(ev==='altar'){if(n===0){hurt(15,'The altar');G.relics.push('Shadow Cloak')}else{heal(10);G.deck.push(cardObj('Doubt'))}}if(ev==='spring'){if(n===0)G.potions.push('Cursed Brew');else{hurt(10,'The spring');G.deck=G.deck.filter(c=>c.name!=='Doubt')}}if(ev==='dead'){if(n===0){hurt(20,'The dead');G.deck.slice(0,2).forEach(c=>c.upgraded=true)}}if(ev==='library'){if(n===0){hurt(5,'Study');G.gold+=20}else{G.deck.push(cardObj(pick(pools[G.cls])[0]));G.deck.push(cardObj('Doubt'))}}closeModal();show('map');renderMap();if(G.hp<=0)endRun(false)}
function shop(){let offers=[pick(pools[G.cls]),pick(pools[G.cls]),pick(pools[G.cls])];let color=['Flash of Insight','Iron Will','Quick Slash'];$('#modalroot').innerHTML=`<div class="modal-wrap"><div class="modal"><div class="encounter">THE BLACK MARKET · ${G.gold} GOLD</div><h2>Spend before the next descent</h2><div class="wares">${offers.map(q=>`<button class="ware" onclick="buyCard('${q[0]}',${q[3]==='Rare'?150:q[3]==='Uncommon'?75:50})"><h3>${q[0]}</h3><p>${q[3]} · ${q[2]} energy</p><b class="gold">${q[3]==='Rare'?150:q[3]==='Uncommon'?75:50}g</b></button>`).join('')}</div><div class="footer-actions"><button onclick="closeModal();show('map');renderMap()">Leave shop</button></div></div></div>`}function buyCard(n,cost){if(G.gold<cost)return toast('Not enough gold.');G.gold-=cost;G.deck.push(cardObj(n));closeModal();show('map');renderMap();toast('Purchase sealed in blood.')}
function closeModal(){$('#modalroot').innerHTML=''}function toast(s){let d=document.createElement('div');d.className='toast';d.textContent=s;document.body.appendChild(d);setTimeout(()=>d.remove(),1700)}function endRun(win){show('gameover');$('#endtitle').textContent=win?'The Abyss Bends':'The Abyss Claims You';$('#endcopy').textContent=win?'The Archdemon is silent. For one fragile moment, the dying world breathes.':'Your name joins the prayers of the forgotten. Every ascent begins with a first step.'}
$('#viewdeck').onclick=()=>{if(!G.deck)return;$('#modalroot').innerHTML=`<div class="modal-wrap"><div class="modal"><h2>Your deck · ${G.deck.length} cards</h2><div class="wares">${G.deck.map(c=>`<div class="ware"><h3>${c.name}</h3><p>${c.type} · ${c.cost} energy</p><p>${c.desc}</p></div>`).join('')}</div><div class="footer-actions"><button onclick="closeModal()">Close</button></div></div></div>`};
init();
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-5.6-luna-pro","provider":"openrouter","persona":"full","sessionId":"6fb2f254-320a-4e46-818c-024887dd12f8","tokensIn":4160366,"tokensOut":61688,"tokensTotal":4222054,"turns":37,"toolCalls":36,"failedToolCalls":0,"timestamp":"2026-07-31T01:04:11.840Z"} -->