← Warden results

Warden

space bunny alpha · full

Initial view of Warden

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 41,184 bytes · SHA-256 6cd0e00d3b13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>The Warden's Corrosion</title>
<style>
:root{--gold:#e7b75f;--ember:#e77b38;--slate:#101a25;--ice:#8bdce5;--parch:#e9dfc5}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#070c12;color:var(--parch);font-family:Georgia,serif}canvas{display:block;width:100%;height:100%;image-rendering:auto}button{font:inherit}#hud{position:fixed;inset:0;pointer-events:none;z-index:3}.top{position:absolute;left:22px;top:18px;display:flex;gap:20px;align-items:flex-start}.hearts{display:flex;gap:5px;filter:drop-shadow(0 2px 3px #000)}heart{width:25px;height:23px;background:#efb04d;clip-path:polygon(50% 100%,8% 53%,0 30%,12% 9%,31% 8%,50% 23%,69% 8%,88% 9%,100% 30%,92% 53%);position:relative}heart.empty{background:#342b2a}heart.half{background:linear-gradient(90deg,#efb04d 50%,#342a2a 50%)}.stat{font:12px/1.5 system-ui;letter-spacing:1.5px;color:#c4bfae;text-shadow:0 2px 2px #000}.stat b{color:#f3c96f}.zone{position:absolute;top:21px;left:50%;transform:translateX(-50%);text-align:center;letter-spacing:4px;font-size:12px;color:#c9bfa6;text-shadow:0 2px 3px #000}.zone small{display:block;color:#788896;letter-spacing:2px;margin-top:7px}.objective{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);padding:9px 18px;background:linear-gradient(90deg,transparent,#09121cdd,transparent);font:12px/1.4 system-ui;letter-spacing:1px;text-align:center;width:min(90vw,600px);text-shadow:0 2px 2px #000}.objective em{color:#69c9d1;font-style:normal}.keyhint{position:absolute;left:20px;bottom:18px;font:10px/1.7 system-ui;color:#81909b;letter-spacing:1px}.keyhint b{color:#d2c7ae}.boss{position:absolute;top:57px;left:50%;transform:translateX(-50%);width:min(480px,80vw);text-align:center}.boss span{font:11px system-ui;letter-spacing:3px}.bar{height:7px;margin-top:7px;background:#1b1c22;border:1px solid #675b4a;clip-path:polygon(5% 0,95% 0,100% 50%,95% 100%,5% 100%,0 50%)}.bar i{display:block;height:100%;background:linear-gradient(90deg,#a33c31,#ec7850);transition:.2s}#prompt{position:absolute;left:50%;top:58%;transform:translate(-50%,-50%);display:none;padding:10px 16px;background:#0b141ce8;border:1px solid #a8874e;color:#f1d28b;font:13px system-ui;letter-spacing:1px;box-shadow:0 0 25px #e4a54b22;white-space:nowrap}#toast{position:absolute;left:50%;top:34%;transform:translateX(-50%);text-align:center;opacity:0;transition:opacity .25s;width:min(90vw,700px);font-size:21px;text-shadow:0 3px 5px #000}#toast small{display:block;margin-top:8px;font:12px system-ui;color:#a8b2b5;letter-spacing:2px}#modal{position:fixed;inset:0;z-index:8;display:none;align-items:center;justify-content:center;background:radial-gradient(circle,#14212cd9,#03070de8);padding:18px}panel{width:min(640px,96vw);max-height:88vh;overflow:auto;background:linear-gradient(145deg,#17222bea,#0a1118f2);border:1px solid #876f47;box-shadow:0 20px 80px #000,inset 0 0 40px #e0a23a09;padding:30px;position:relative}panel:before,panel:after{content:"";position:absolute;width:25px;height:25px;border-color:#c3974e;border-style:solid}panel:before{left:8px;top:8px;border-width:1px 0 0 1px}panel:after{right:8px;bottom:8px;border-width:0 1px 1px 0}h1{font-weight:400;font-size:32px;line-height:1.1;margin:4px 0 10px;color:#f0cf87;text-align:center}h2{font-weight:400;color:#e7bf72;letter-spacing:1px}h3{font-size:14px;color:#a9b7bd;letter-spacing:2px;font-weight:400}p{line-height:1.6;color:#b9b6aa;font-size:14px}.rule{height:1px;background:linear-gradient(90deg,transparent,#a78348,transparent);margin:20px 0}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.card{border:1px solid #40505a;background:#101a22;padding:14px;cursor:pointer;transition:.15s}.card:hover{border-color:#d0a354;transform:translateY(-2px)}.card.locked{opacity:.42;cursor:not-allowed}.card.sel{border-color:#76d2da;box-shadow:inset 0 0 18px #59b9c51c}.card .icon{font-size:30px;height:42px;color:#e9c475}.card b{display:block;font-size:14px;color:#e5dbc2}.card small{display:block;margin-top:6px;font:11px/1.4 system-ui;color:#8999a2}.qty{color:#e6b85c!important}.actions{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:22px}.btn{padding:10px 18px;color:#f0d18a;background:#17232c;border:1px solid #8f7445;cursor:pointer;transition:.15s}.btn:hover{background:#27313a;border-color:#efbe62;box-shadow:0 0 15px #e8a83a22}.btn.primary{background:#49361e}.btn:disabled{opacity:.4;cursor:not-allowed}.close{position:absolute;right:15px;top:12px;background:none;border:0;color:#c2ae83;font-size:25px;cursor:pointer}.tabs{display:flex;justify-content:center;gap:8px;margin:15px 0}.tab{padding:7px 14px;background:#101820;border:1px solid #40505a;color:#8999a2;cursor:pointer}.tab.on{border-color:#d0a354;color:#e9c475}#intro .sigil{font-size:66px;text-align:center;color:#e4a943;text-shadow:0 0 40px #e8802580;animation:float 3s ease-in-out infinite}@keyframes float{50%{transform:translateY(-8px);filter:brightness(1.25)}}#vignette{position:fixed;inset:0;pointer-events:none;z-index:2;box-shadow:inset 0 0 150px 25px #02060baa;transition:.2s}#hurt{position:fixed;inset:0;pointer-events:none;z-index:4;box-shadow:inset 0 0 100px #ef3329;opacity:0}#shimmer{position:fixed;inset:0;pointer-events:none;z-index:5;box-shadow:inset 0 0 80px #ffd45b;opacity:0;transition:.4s}#touch{position:fixed;inset:0;z-index:6;display:none;pointer-events:none}#stick{position:absolute;left:25px;bottom:28px;width:120px;height:120px;border-radius:50%;border:1px solid #afc6ca55;background:#10212a44;pointer-events:auto}#nub{position:absolute;left:42px;top:42px;width:36px;height:36px;border-radius:50%;background:#d8b46988;border:1px solid #ead398} .touchbtn{position:absolute;right:25px;width:65px;height:65px;border-radius:50%;background:#8d683e55;border:1px solid #e7c67999;color:#f2db9b;pointer-events:auto;font:12px system-ui}.touchbtn.attack{right:105px;bottom:92px}.touchbtn.use{right:28px;bottom:30px}@media(pointer:coarse){#touch{display:block}.keyhint{display:none}.objective{bottom:165px}.hearts{transform:scale(.8);transform-origin:left top}}@media(max-width:600px){.top{left:10px}.zone{top:66px}.cards{grid-template-columns:1fr}.panel{padding:24px 18px}#intro h1{font-size:28px}}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="vignette"></div><div id="hurt"></div><div id="shimmer"></div>
<div id="hud">
 <div class="top"><div class="hearts" id="hearts"></div><div class="stat">SIGILS <b id="gold">0</b><br>EMBER BLADE <b id="weapon">BASE</b></div></div>
 <div class="zone" id="zoneName"></div><div class="boss" id="boss" style="display:none"><span>CORRUPTED CONSTRUCT</span><div class="bar"><i id="bossBar"></i></div></div>
 <div class="objective" id="objective"></div><div class="keyhint"><b>WASD / ARROWS</b> MOVE &nbsp; <b>SPACE</b> ATTACK &nbsp; <b>E</b> INTERACT &nbsp; <b>Q</b> AETHER &nbsp; <b>I</b> INVENTORY &nbsp; <b>M</b> SOUND</div>
 <div id="prompt"></div><div id="toast"></div>
</div>
<div id="touch"><div id="stick"><div id="nub"></div></div><button class="touchbtn attack">STRIKE</button><button class="touchbtn use">USE</button></div>
<div id="modal"></div>
<script>
const cv=document.getElementById('game'),ctx=cv.getContext('2d');let W,H,DPR;
const $=s=>document.querySelector(s), modal=$('#modal');
const ZONES=['THE ASHEN THRESHOLD','THE RUSTED ARCHIVES','THE COLLAPSED GALLERY','THE SUNKEN VAULT','THE TWIN SPIRE','THE CORE CHAMBER'];
const SUB=['The storm remembers your footsteps.','Dust, commerce, and the merchant’s prayer.','A seal remembers the shape of flame.','Water conducts the language of light.','Two towers. One final radiance.','Rekindle what the heavens lost.'];
const state={zone:0,gold:0,hp:6,max:6,weapon:0,aether:false,ammo:5,band:false,lantern:false,cloak:false,stems:1,seals:0,up3:false,up4:false,up5:false,soldStem:0,soldSeal:0,soldHeart:false,soldAmmo:false,shop:false,stone:0,won:false,sealedEnemies:{}};
let p,enemies=[],items=[],props=[],shots=[],particles=[],labels=[],decor=[],doors=[],plates=[],switches=[],breakables=[],npc=null,shopAltar=null,activeAltar=null,boss=null,gameTime=0,last=0,cam={x:0,y:0},foot=0,shake=0,attackCd=0,hurtFx=0,shimmerFx=0,modalType='',shopTab='items',toastT=0,pad={x:0,y:0},keys={},audioOn=true,visitedStones=[],dialogCooldown=0,ended=false;
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)),dist=(a,b)=>Math.hypot(a.x-b.x,a.y-b.y),rand=(a,b)=>a+Math.random()*(b-a);
function resize(){DPR=Math.min(devicePixelRatio||1,2);W=innerWidth;H=innerHeight;cv.width=W*DPR;cv.height=H*DPR;ctx.setTransform(DPR,0,0,DPR,0,0)}addEventListener('resize',resize);resize();
function initAudio(){try{audioOn=localStorage.getItem('wardenAudio')!=='0'}catch(e){}}
function tone(f=220,d=.1,type='sine',vol=.04,slide=0,delay=0){if(!audioOn)return;let t=audio.currentTime+delay,o=audio.createOscillator(),g=audio.createGain();o.type=type;o.frequency.setValueAtTime(f,t);o.frequency.exponentialRampToValueAtTime(Math.max(25,f+slide),t+d);g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(.0001,t+d);o.connect(g);g.connect(audio.destination);o.start(t);o.stop(t+d+.02)}
let audio;try{audio=new(window.AudioContext||window.webkitAudioContext)()}catch(e){}initAudio();
function noise(d=.1,vol=.03,cut=900){if(!audioOn||!audio)return;let n=audio.sampleRate*d,b=audio.createBuffer(1,n,audio.sampleRate),a=b.getChannelData(0);for(let i=0;i<n;i++)a[i]=(Math.random()*2-1)*(1-i/n);let s=audio.createBufferSource(),f=audio.createBiquadFilter(),g=audio.createGain();s.buffer=b;f.type='lowpass';f.frequency.value=cut;g.gain.value=vol;s.connect(f);f.connect(g);g.connect(audio.destination);s.start()}
function sfx(type){if(type==='step'){noise(.07,.018,state.zone===3?500:950);tone(85,.06,'triangle',.02,-25)}if(type==='base'){noise(.15,.045,1900);tone(130,.13,'triangle',.06,-70)}if(type==='ember'){noise(.18,.06,2400);tone(100,.2,'sawtooth',.035,-55);tone(420,.09,'sine',.025,-180)}if(type==='solar'){tone(70,.42,'sine',.08,-20);noise(.24,.07,3000);tone(260,.45,'sine',.035,-170, .1)}if(type==='aether'){tone(880,.22,'sine',.045,-520);tone(1320,.12,'triangle',.025,200,.04);noise(.06,.02,5000)}if(type==='hit'){noise(.09,.05,700);tone(72,.11,'sine',.06,-30)}if(type==='hurt'){noise(.14,.07,450);tone(210,.17,'triangle',.035,110);tone(750,.2,'sine',.02,430)}if(type==='coin'){tone(1100,.07,'square',.018,400);tone(1600,.1,'sine',.025,-200,.05)}if(type==='item'){[440,660,880,1320].forEach((f,i)=>tone(f,.3,'sine',.035,120,i*.07))}if(type==='rare'){[220,330,440,660].forEach((f,i)=>tone(f,.7,'sine',.03,0,i*.1))}if(type==='stone'){tone(196,1,'sine',.05,98);tone(294,.9,'sine',.035,40,.12);tone(392,1.1,'sine',.03,80,.2)}if(type==='seal'){tone(520,.6,'sine',.04,-260);tone(780,.7,'sine',.03,40)}if(type==='shop'){tone(110,.25,'sine',.015,10)}}
function toast(t,s=''){const el=$('#toast');el.innerHTML=t+(s?`<small>${s}</small>`:'');el.style.opacity=1;toastT=3.4}function showPrompt(t){$('#prompt').textContent=t;$('#prompt').style.display='block'}function hidePrompt(){$('#prompt').style.display='none'}
function zoneObj(){return{enemies:[],items:[],props:[],shots:[],particles:[],labels:[],decor:[],doors:[],plates:[],switches:[],breakables:[],boss:null}}
function newRun(){state.zone=0;state.gold=0;state.hp=6;state.max=6;state.weapon=0;state.aether=false;state.ammo=5;state.band=false;state.lantern=false;state.cloak=false;state.stems=1;state.seals=0;state.up3=false;state.up4=false;state.up5=false;state.soldStem=0;state.soldSeal=0;state.soldHeart=false;state.soldAmmo=false;state.shop=false;state.stone=0;state.won=false;state.sealedEnemies={};visitedStones=[];ended=false;loadZone(0);closeModal();toast('THE LAST WARDEN','Break the constructs. Find the first Sanctuary Stone.')}
function spawnEnemy(x,y,bossEl=false){enemies.push({x,y,hp:bossEl?72:(state.zone>=4?38:state.zone>=2?30:23),max:bossEl?72:(state.zone>=4?38:state.zone>=2?30:23),r:bossEl?29:17,spd:bossEl?48:rand(38,60),phase:rand(0,9),boss:bossEl,atk:0,flash:0,dead:0})}
function loadZone(z){
 state.zone=z;enemies=[];items=[];props=[];doors=[];plates=[];switches=[];breakables=[];particles=[];labels=[];shots=[];decor=[];boss=null;npc=null;shopAltar=null;activeAltar=null;ended=false;attackCd=0;
 p={x:W/2,y:H/2+70,r:16,hp:state.hp,cd:0,face:0,moving:0};
 for(let i=0;i<36;i++)decor.push({x:rand(0,W),y:rand(0,H),s:rand(1,3),p:rand(0,7)});
 $('[id="zoneName"]');
 $('#zoneName').innerHTML=`${ZONES[z]}<small>${SUB[z]}</small>`;
 if(z===0){spawnEnemy(W*.74,H*.28);spawnEnemy(W*.82,H*.66);props.push({x:W*.3,y:H*.42,type:'stone',used:false});props.push({x:W*.68,y:H*.77,type:'chest',gold:15,used:false});}
 if(z===1){for(let i=0;i<4;i++)spawnEnemy(W*(.3+i*.16),H*(.3+(i%2)*.35));props.push({x:W*.25,y:H*.27,type:'chest',item:'band',used:false});props.push({x:W*.72,y:H*.66,type:'stone',used:false});props.push({x:W*.55,y:H*.32,type:'chest',item:'stem',used:false});shopAltar={x:W*.5,y:H*.75};}
 if(z===2){props.push({x:W*.5,y:H*.24,type:'altar',upgrade:1});breakables.push({x:W*.22,y:H*.38,hp:4,type:'health'});props.push({x:W*.75,y:H*.7,type:'platePair'});spawnEnemy(W*.7,H*.38);props.push({x:W*.78,y:H*.32,type:'chest',gold:35,used:false});}
 if(z===3){breakables.push({x:W*.72,y:H*.33,hp:state.weapon===2?2:4,type:'aether'});props.push({x:W*.25,y:H*.66,type:'lightPuzzle',used:false});props.push({x:W*.26,y:H*.28,type:'chest',gold:30,used:false});spawnEnemy(W*.38,H*.34);spawnEnemy(W*.6,H*.62);props.push({x:W*.8,y:H*.75,type:'platePair'});}
 if(z===4){props.push({x:W*.5,y:H*.24,type:'altar',upgrade:2});props.push({x:W*.22,y:H*.34,type:'chest',item:'seal',used:false});props.push({x:W*.75,y:H*.68,type:'chest',item:'cloak',used:false});props.push({x:W*.25,y:H*.7,type:'chest',gold:80,used:false});switches.push({x:W*.5,y:H*.75,on:false});breakables.push({x:W*.78,y:H*.3,hp:3,type:'health'});spawnEnemy(W*.58,H*.4,true);spawnEnemy(W*.22,H*.55);spawnEnemy(W*.75,H*.48);}
 if(z===5){spawnEnemy(W*.23,H*.3);spawnEnemy(W*.5,H*.25);spawnEnemy(W*.77,H*.3);spawnEnemy(W*.3,H*.65);spawnEnemy(W*.7,H*.65);switches.push({x:W*.5,y:H*.75,on:false});props.push({x:W*.5,y:H*.5,type:'final'});props.push({x:W*.23,y:H*.75,type:'chest',gold:60,used:false});}
 updateHUD();sfx('shop');
}
function updateHUD(){let h=$('#hearts');h.innerHTML='';for(let i=0;i<state.max/2;i++){let e=document.createElement('heart');let v=clamp(state.hp-i*2,0,2);e.className=v===0?'empty':v===1?'half':'';h.appendChild(e)}$('#gold').textContent=state.gold;$('#weapon').textContent=['EMBER BLADE','IGNITED EDGE','SOLAR FALCHION'][state.weapon];let objs=['Reach and awaken the Sanctuary Stone.','Defeat the Archives constructs, then rest at the stone.','Find the Ignited Edge altar; investigate cracked masonry.','Shatter the sealed wall. Solve the light-memory.','Claim the Solar Falchion. Strike the distant mechanism.','Strike the Beacon lock. Rekindle Aurelion.'];$('#objective').innerHTML=`<span style="color:#dfb862">${zeros(state.zone)} / VI</span> &nbsp; · &nbsp; ${objs[state.zone]}`;$('#boss').style.display=boss?'block':'none'}
const zeros=n=>['I','I','II','III','IV','V','VI'][n];
function burst(x,y,color,n=12,sp=100){for(let i=0;i<n;i++){let a=rand(0,7),s=rand(20,sp);particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:rand(.25,.7),max:.7,c:color,r:rand(1,3)})}}
function label(x,y,t,c='#f4d37c'){labels.push({x,y,t,c,life:1.1})}
function damageEnemy(e,d){if(e.dead)return;e.hp-=d;e.flash=.18;burst(e.x,e.y,state.weapon===0?'#f2a24a':'#8ae5ee',12,140);label(e.x,e.y-20,Math.round(d),state.weapon===1?'#ff9a43':'#ffe18a');sfx('hit');if(e.hp<=0){e.dead=1;const gold=e.boss?80:rand(5,15);state.gold+=gold;burst(e.x,e.y,'#e4b450',14,100);if(!e.boss)label(e.x,e.y-42,`+${gold} SIGILS`,'#edc666');sfx('coin');if(e.boss){boss=null;toast('THE SENTINEL FALLS','Darkness loosens its grip.')}}updateHUD()}
function hurt(n){if(dialogCooldown>0||hurtFx>0)return;let dmg=state.cloak?Math.ceil(n*.65):n;state.hp=Math.max(0,state.hp-dmg);hurtFx=.5;shake=5;sfx('hurt');burst(p.x,p.y,'#e14d43',18,150);label(p.x,p.y-28,`−${dmg}`,'#ff6b62');updateHUD();if(state.hp<=0){closeModal();modal.innerHTML=`<panel><div class="sigil">✦</div><h1>THE WARDEN FALLS</h1><p>Corrosion may bend you. It cannot erase the light you carry.</p><div class="actions"><button class="btn primary" onclick="respawn()">Return to last Sanctuary Stone</button></div></panel>`;modal.style.display='flex'}}
function respawn(){state.hp=state.max;loadZone(state.zone);toast('THE STONE REMEMBERS','Your strength returns. The corruption remains.')}
function attack(){if(attackCd>0||dialogCooldown>0)return;const wide=state.weapon===2;attackCd=state.weapon===0?.38:state.weapon===1?.25:.62;shake=wide?5:2;sfx(['base','ember','solar'][state.weapon]);let range=wide?112:78,arc=wide?2.15:1.65;for(const e of enemies){if(e.dead)continue;let a=Math.atan2(e.y-p.y,e.x-p.x),d=dist(e,p);if(d<range+e.r&&Math.abs(Math.atan2(Math.sin(a-p.face),Math.cos(a-p.face)))<arc/2)damageEnemy(e,state.weapon===2?34:state.weapon===1?25:18)}if(state.weapon)for(let i=0;i<12;i++)particles.push({x:p.x+Math.cos(p.face+rand(-arc/2,arc/2))*rand(15,range),y:p.y+Math.sin(p.face+rand(-arc/2,arc/2))*rand(15,range),vx:rand(-20,20),vy:rand(-20,20),life:.4,max:.4,c:state.weapon===1?'#ff762e':'#ffd35b',r:rand(1,4)})}
function shoot(){if(!state.aether){toast('NO AETHER RESONANCE','Find the crystalline launcher in the Sunken Vault.');return}if(state.ammo<=0){toast('THE SHARD IS DIM','Merchant charges refill each Aether charge.');return}if(attackCd>0)return;state.ammo--;attackCd=.28;sfx('aether');shots.push({x:p.x,y:p.y,vx:Math.cos(p.face)*520,vy:Math.sin(p.face)*520,life:1.4});burst(p.x,p.y,'#83e7ff',10,80);updateHUD()}
function fireSwitch(x,y,fromShot=false){for(const s of switches){if(!s.on&&dist(s,{x,y})<55){s.on=true;sfx('seal');toast('AETHER LOCK ANSWERS','A mechanism stirs beyond the walls.');burst(s.x,s.y,'#84e7ff',30,180);if(state.zone===4&&!state.up5){addHealth(1);toast('A FIFTH VESSEL ANSWERS','The Spire conceals one final heart.')}return true}}return false}
function addHealth(n){state.max+=n*2;state.hp=state.max;shimmerFx=1;burst(p.x,p.y,'#ffe07a',45,180);toast('A NEW VESSEL AWAKENS',n===2?'Four hearts now beat within you.':'Five hearts. The Warden endures.');sfx('rare')}
function pickup(it){if(it.used)return;it.used=true;if(it.type==='gold'){state.gold+=it.value;sfx('coin');burst(it.x,it.y,'#e8bd57',16,100);label(it.x,it.y-20,`+${it.value} SIGILS`,'#f2ca70')}else{sfx(it.accessory?'rare':'item');burst(it.x,it.y,'#dff8ff',28,120);if(it.type==='stem')state.stems++;if(it.type==='seal')state.seals++;if(it.type==='band'){state.band=true;toast('WARDEN’S BAND','Silver remembers your wrist. <em>+12% attack speed</em>')}if(it.type==='lantern'){state.lantern=true;toast('PHANTOM LANTERN','Hidden cracks breathe in blue light.')}if(it.type==='cloak'){state.cloak=true;toast('SHADOW CLOAK','<em>Trap damage reduced by 35%</em>')}if(it.type==='heart')addHealth(1);if(it.type==='aether'){state.aether=true;toast('THE AETHER SHARD','Aim with <em>Q</em> to cast a crystal bolt.')}if(it.type==='upgrade1'){state.weapon=1;toast('IGNITED EDGE','Faster strikes. Ember fire consumes the Corrosion.')}if(it.type==='upgrade2'){state.weapon=2;toast('SOLAR FALCHION','A wider, heavier arc bathes the ruin in gold.')}if(it.type==='exit'){if(state.zone===5)endGame();else loadZone(state.zone+1)} }updateHUD()}
function nearestInteraction(){let arr=[];if(npc)arr.push(npc);if(shopAltar)arr.push(shopAltar);props.filter(x=>!x.used).forEach(x=>arr.push(x));breakables.filter(x=>x.hp>0).forEach(x=>arr.push(x));let n=arr.filter(x=>dist(p,x)<70).sort((a,b)=>dist(p,a)-dist(p,b))[0];return n}
function interact(){let o=nearestInteraction();if(!o){toast('NOTHING ANSWERS','Look for fractured stone, bronze, or a warm glint.');return}if(o.type==='stone'){o.used=true;if(!visitedStones.includes(state.zone)){visitedStones.push(state.zone);state.stone++;if(state.zone===1)state.shop=true;toast('SANCTUARY STONE AWAKENED','Progress secured. Rest here to restore every heart.');sfx('stone')}else toast('SANCTUARY STONE','Rest restores all hearts. Your progress is secured.')}
if(o.type==='chest'){pickup({...o,type:o.item||'gold',value:o.gold||20,accessory:['band','cloak'].includes(o.item)});o.used=true}
if(o.type==='altar'){o.used=true;if(o.upgrade===1)pickup({type:'upgrade1'});else pickup({type:'upgrade2'});shimmerFx=1}
if(o.type==='break'){o.hp-=state.weapon===2?3:1;burst(o.x,o.y,'#89918d',15,130);if(o.hp<=0){o.used=true;if(o.type==='health')pickup({type:'heart'});else pickup({type:'aether'});}}
if(o.type==='platePair')solvePlates(o)
if(o.type==='lightPuzzle'){o.used=true;items.push({x:o.x+20,y:o.y,type:'lantern',accessory:true,used:false});pickup({type:'gold',value:25});o.used=true}
if(o.type==='vendor')openShop()
if(o.type==='final'){if(state.aether)fireSwitch(o.x,o.y);else toast('THE BEACON ANSWERS THE AETHER SHARD','Only your crystal voice can wake this lock.')}
if(o.type==='exit')loadZone(state.zone+1)
}
function solvePlates(o){if(o.used)return;o.used=true;items.push({x:o.x,y:o.y,type:'gold',value:30});burst(o.x,o.y,'#e8b45b',24,120);toast('PRESSURE SEQUENCE ACCEPTED','Two bronze echoes answer as one.');sfx('stone')}
function openShop(){if(!state.shop){toast('THE MERCHANT STANDS SILENT','Awaken the Archives Sanctuary Stone first.');return}sfx('shop');shopTab='items';renderShop()}
function shopCard(icon,name,desc,cost,fn,locked=false){return`<div class="card ${locked?'locked':''}" ${locked?'':`onclick="${fn}"`}><div class="icon">${icon}</div><b>${name}</b><small>${desc}</small><small class="qty">${locked?'LOCKED':cost+' ◈'}</small></div>`}
function renderShop(){let t=shopTab;let cards=t==='items'?shopCard('❧','Vital Stem','Restores four HP. Carry several.',50,"buy('stem')",state.soldStem>=3||state.gold<50)+shopCard('◈','Binding Seal','Freezes one construct for three seconds.',75,"buy('seal')",state.soldSeal>=2||state.gold<75)+shopCard('✦','Aether Restock','Refills five crystal charges.',state.soldAmmo?'':'10 ◈',"buy('ammo')",!state.aether||state.soldAmmo):shopCard('♥','Fifth Vessel','Permanent upgrade. Two additional HP.',300,"buy('heart')",state.soldHeart||state.gold<300)+shopCard('◎','Warden’s Band','Equipped. Attacks 12% faster.',state.band?'WORN':'50 ◈',"equip('band')",!state.band)+shopCard('♢','Phantom Lantern','Equipped. Reveals hidden fractures.',state.lantern?'WORN':'75 ◈',"equip('lantern')",!state.lantern)+shopCard('☽','Shadow Cloak','Equipped. Traps deal 35% less.',state.cloak?'WORN':'75 ◈',"equip('cloak')",!state.cloak);modal.innerHTML=`<panel><button class="close" onclick="closeModal()">×</button><h1>THE SIGIL MERCHANT</h1><p style="text-align:center">Gold freely given returns as grace.</p><div class="rule"></div><div class="tabs"><button class="tab ${t==='items'?'on':''}" onclick="shopTab='items';renderShop()">WARES</button><button class="tab ${t==='gear'?'on':''}" onclick="shopTab='gear';renderShop()">GEAR</button></div><div class="cards">${cards}</div><p style="text-align:center;color:#e2bd6d">YOUR SIGILS · <b>${state.gold}</b></p></panel>`;modal.style.display='flex'}
function buy(t){if(t==='stem'&&state.soldStem<3&&state.gold>=50){state.gold-=50;state.stems++;state.soldStem++;pickup({type:'gold',value:0})}if(t==='seal'&&state.soldSeal<2&&state.gold>=75){state.gold-=75;state.seals++;state.soldSeal++;pickup({type:'gold',value:0})}if(t==='heart'&&!state.soldHeart&&state.gold>=300){state.gold-=300;state.soldHeart=true;state.up5=true;addHealth(1)}if(t==='ammo'&&state.aether&&!state.soldAmmo&&state.gold>=10){state.gold-=10;state.ammo+=5;state.soldAmmo=true;pickup({type:'gold',value:0})}sfx('coin');tone(1046,.3,'sine',.035,500,.08);renderShop();updateHUD()}
function equip(t){state[t]=!state[t];sfx('seal');renderShop();updateHUD()}
function useItem(){if(state.stems>0&&state.hp<state.max){state.stems--;let heal=Math.min(4,state.max-state.hp);state.hp+=heal;shimmerFx=.5;burst(p.x,p.y,'#83e897',25,100);label(p.x,p.y-30,`+${heal} HP`,'#8cf0a4');sfx('item')}else if(state.seals>0&&enemies.some(e=>!e.dead&&dist(p,e)<130)){let e=enemies.filter(e=>!e.dead).sort((a,b)=>dist(p,a)-dist(p,b))[0];state.seals--;state.sealedEnemies[e]=gameTime+3;burst(e.x,e.y,'#77d9f0',25,120);label(e.x,e.y-30,'BOUND','#8ce9ff');sfx('seal');toast('BINDING SEAL','The construct hangs silent in the air.')}else toast('NOTHING TO RESTORE','No damaged heart. No construct within reach.')}
function openInventory(){modal.innerHTML=`<panel><button class="close" onclick="closeModal()">×</button><h1>WARDEN’S SATCHEL</h1><p style="text-align:center">${Object.keys(state.sealedEnemies).length||''}${state.aether?'<b style="color:#8edce8"> · AETHER RESONANCE</b>':''}</p><div class="rule"></div><h2>WEAPON</h2><p>${['Ember Blade — patient iron.','Ignited Edge — swift, burning arc.','Solar Falchion — wide and radiant.'][state.weapon]}</p><h2>CONSUMABLES</h2><p>Vital Stems <b style="color:#8de19c">× ${state.stems}</b> &nbsp; Binding Seals <b style="color:#8ddbed">× ${state.seals}</b><br><small>Press E outside menus to use a consumable.</small></p><div class="rule"></div><h2>ACCESSORIES</h2><p>Warden’s Band · ${state.band?'<b style="color:#d8e6ef">Equipped</b>':'Not found'}<br>Phantom Lantern · ${state.lantern?'<b style="color:#8ddbed">Equipped</b>':'Not found'}<br>Shadow Cloak · ${state.cloak?'<b style="color:#b8a5dc">Equipped</b>':'Not found'}</p><div class="rule"></div><p>Aether charges: <b style="color:#8edce8">${state.ammo}</b> &nbsp; · &nbsp; Sanctuary Stones: <b>${visitedStones.length} / 3</b></p><div class="actions"><button class="btn" onclick="closeModal()">Return to the ruin</button></div></panel>`;modal.style.display='flex'}
function closeModal(){modal.style.display='none';modal.innerHTML='';modalType='';dialogCooldown=.25}
function endGame(){ended=true;state.won=true;modal.innerHTML=`<panel><div class="sigil">☀</div><h1>THE BEACON KINDLES</h1><p>Golden fire climbs the ancient lenses. Across the valley, storm clouds part for the first time in a hundred years.</p><div class="rule"></div><p>The Last Warden lowers the falchion. The Corrosion remains—but it no longer speaks alone.</p><h3>SANCTUARY SECURED · ${visitedStones.length}/3 &nbsp; · &nbsp; SIGILS ${state.gold}</h3><div class="actions"><button class="btn primary" onclick="newRun()">Begin another vigil</button></div></panel>`;modal.style.display='flex';sfx('rare')}
window.newRun=newRun;window.respawn=respawn;window.closeModal=closeModal;window.shopTab='';window.renderShop=renderShop;window.buy=buy;window.equip=equip;
function intro(){modal.innerHTML=`<panel id="intro"><div class="sigil">✦</div><h1>THE WARDEN’S<br>CORROSION</h1><p style="text-align:center;letter-spacing:2px">AURELION’S SUNKEN OBSERVATORY</p><div class="rule"></div><p>You are the Last Warden. Above a storm-locked valley, the Core Beacon is failing and black-iron Corrosion creeps through the archive.</p><p>Cross six connected halls. Restore two weapon altars. Wake the Aether Shard. Claim five hearts. Rekindle the sky.</p><div class="actions"><button class="btn primary" onclick="startGame()">ENTER THE THRESHOLD</button></div><p style="text-align:center;font:10px system-ui;letter-spacing:2px;color:#687b86">WASD MOVE · SPACE ATTACK · E INTERACT / USE</p></panel>`;modal.style.display='flex';modalType='intro';state.zone=0;Object.assign(state,{gold:0,hp:6,max:6,weapon:0,aether:false,ammo:5,band:false,lantern:false,cloak:false,stems:1,seals:0,up3:false,up4:false,up5:false,shop:false,stone:0,won:false});p={x:W/2,y:H/2+70,r:16,hp:6};$('#zoneName').innerHTML=`THE ASHEN THRESHOLD<small>${SUB[0]}</small>`;enemies=[];props=[];items=[];decor=[];updateHUD()}
window.startGame=()=>{closeModal();loadZone(0);toast('THE LAST WARDEN','Break the constructs. Find the first Sanctuary Stone.')};
function update(dt){
 gameTime+=dt;attackCd=Math.max(0,attackCd-dt);hurtFx=Math.max(0,hurtFx-dt);shimmerFx=Math.max(0,shimmerFx-dt);dialogCooldown=Math.max(0,dialogCooldown-dt);toastT-=dt;if(toastT<0)$('#toast').style.opacity=0;$('#hurt').style.opacity=hurtFx*1.3;$('#shimmer').style.opacity=shimmerFx*.7;shake*=.9;
 if(modalType||ended){particles.forEach(q=>{q.life-=dt;q.x+=q.vx*dt;q.y+=q.vy*dt;q.vy+=30*dt});particles=particles.filter(q=>q.life>0);return}
 let dx=(keys.d||keys.ArrowRight?1:0)-(keys.a||keys.ArrowLeft?1:0)+pad.x,dy=(keys.s||keys.ArrowDown?1:0)-(keys.w||keys.ArrowUp?1:0)+pad.y;let l=Math.hypot(dx,dy);if(l>1){dx/=l;dy/=l}p.moving=l>.1;p.x=clamp(p.x+dx*(state.cloak?175:185)*dt,22,W-22);p.y=clamp(p.y+dy*(state.cloak?175:185)*dt,70,H-65);if(l>.1){p.face=Math.atan2(dy,dx);foot+=dt*8;if(foot>1){foot=0;sfx('step')}}
 // plates are activated by standing; the paired sequence is a physical bronze mechanism
 for(const b of breakables)if(b.hp>0&&dist(p,b)<30){if(state.weapon>=2){b.hp=0;pickup({type:b.type});b.used=true}else{burst(b.x,b.y,'#9c7a52',5,60)}}
 for(const it of items)if(!it.used&&dist(p,it)<30)pickup(it);
 for(const e of enemies){if(e.dead)continue;e.flash-=dt;let sealed=state.sealedEnemies[e]&&state.sealedEnemies[e]>gameTime;if(sealed){burst(e.x,e.y,'#76d7ef',1,20);continue}let d=dist(p,e);if(d<330){let a=Math.atan2(p.y-e.y,p.x-e.x)+Math.sin(gameTime*1.4+e.phase)*.3;e.x+=Math.cos(a)*e.spd*dt;e.y+=Math.sin(a)*e.spd*dt;if(d<e.r+25&&e.atk<=0){hurt(e.boss?2:1);e.atk=1.1}}e.atk-=dt}
 for(const q of shots){q.x+=q.vx*dt;q.y+=q.vy*dt;q.life-=dt;for(const e of enemies)if(!e.dead&&dist(q,e)<e.r+10){damageEnemy(e,40);q.life=0}for(const s of switches)if(!s.on&&dist(q,s)<45){fireSwitch(s.x,s.y,true);q.life=0}if(q.x<0||q.x>W||q.y<0||q.y>H)q.life=0}
 shots=shots.filter(q=>q.life>0);enemies=enemies.filter(e=>!e.dead||e.dead<.7);enemies.forEach(e=>e.dead+=dt);particles.forEach(q=>{q.life-=dt;q.x+=q.vx*dt;q.y+=q.vy*dt;q.vx*=.98;q.vy=q.vy*.98+20*dt});particles=particles.filter(q=>q.life>0);labels.forEach(q=>{q.life-=dt;q.y-=28*dt});labels=labels.filter(q=>q.life>0);
 let n=nearestInteraction();if(n){let txt=n.type==='stone'?`E  ${n.used?'Rest at Sanctuary Stone':'Awaken Sanctuary Stone'}`:n.type==='chest'?'E  Open the cache':n.type==='altar'?'E  Touch the weapon altar':n.type==='break'?(state.weapon===2?'E  Shatter the fractured wall':'E  Strike the fractured wall'):n.type==='platePair'?'E  Press bronze in sequence':n.type==='lightPuzzle'?'E  Trace the hidden light':n.type==='vendor'?'E  Trade sigils':n.type==='final'?'E  Cast upon the Beacon lock':'';showPrompt(txt)}else hidePrompt();
}
addEventListener('keydown',e=>{keys[e.key]=true;if(['Space','ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.key))e.preventDefault();if(e.repeat)return;if(e.key===' '){if(modalType==='intro')startGame();else attack()}if(e.key==='e'||e.key==='E'){if(!modalType)interact()}if(e.key==='q'||e.key==='Q')shoot();if(e.key==='i'||e.key==='I'){if(modalType)closeModal();else openInventory()}if(e.key==='Escape')closeModal();if(e.key==='m'||e.key==='M'){audioOn=!audioOn;try{localStorage.setItem('wardenAudio',audioOn?'1':'0')}catch(e){}toast(audioOn?'SOUND RESTORED':'SOUND SILENCED')}});addEventListener('keyup',e=>keys[e.key]=false);
let stick=null;function movePad(e){let r=stick.getBoundingClientRect(),x=e.clientX-r.left-r.width/2,y=e.clientY-r.top-r.height/2,l=Math.hypot(x,y),m=Math.min(38,l);pad.x=l?x/l*m/38:0;pad.y=l?y/l*m/38:0;$('#nub').style.transform=`translate(${pad.x*38}px,${pad.y*38}px)`}addEventListener('pointerdown',e=>{if(e.target.id==='stick'){stick=e.target;movePad(e)}});addEventListener('pointermove',e=>{if(stick)movePad(e)});addEventListener('pointerup',()=>{stick=null;pad={x:0,y:0};$('#nub').style.transform=''});addEventListener('pointerdown',e=>{if(e.target.classList?.contains('attack'))attack();if(e.target.classList?.contains('use'))interact()});
function roundRect(x,y,w,h,r,fill,stroke){ctx.beginPath();ctx.roundRect(x,y,w,h,r);if(fill){ctx.fillStyle=fill;ctx.fill()}if(stroke){ctx.strokeStyle=stroke;ctx.stroke()}}
function drawBackdrop(){let g=ctx.createLinearGradient(0,0,0,H);const sets=[['#162633','#080e16','#7b3d25'],['#24252a','#0b1117','#8a4d2e'],['#253036','#0b141b','#5b6d6b'],['#102c37','#071219','#297281'],['#2b2735','#0c1019','#a05c32'],['#31291e','#080b12','#a25a24']][state.zone];g.addColorStop(0,sets[0]);g.addColorStop(.6,sets[1]);g.addColorStop(1,sets[2]);ctx.fillStyle=g;ctx.fillRect(0,0,W,H);ctx.save();ctx.globalAlpha=.16;ctx.strokeStyle='#8ca4ac';ctx.lineWidth=1;let off=state.zone===5?0:(gameTime*4)%80;for(let x=-80+off;x<W;x+=80){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x+H*.45,H);ctx.stroke()}ctx.strokeStyle='#c29555';for(let y=35;y<H;y+=70){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y+((y/70)%2)*20);ctx.stroke()}ctx.restore();
 // architecture
 ctx.save();ctx.globalAlpha=.25;ctx.strokeStyle='#b08a54';ctx.lineWidth=8;ctx.beginPath();ctx.arc(W*.5,H*.5,Math.min(W,H)*.43,0,7);ctx.stroke();ctx.restore();for(let i=0;i<24;i++)decor.forEach((d,j)=>{if((i+j)%7===0){d.x+=Math.sin(gameTime*.2+i)*.03;ctx.fillStyle=state.zone===3?'#8bd3db':'#d8a84f';ctx.globalAlpha=.1+.2*(Math.sin(gameTime+i)+1)/2;ctx.beginPath();ctx.arc(d.x,d.y,d.s,0,7);ctx.fill()}});ctx.globalAlpha=1;
 if(state.zone===3){for(let i=0;i<12;i++){let x=(i*137+gameTime*6)%W,y=H-((gameTime*20+i*71)%(H+80));ctx.fillStyle='#85b9bf18';ctx.beginPath();ctx.ellipse(x,y,45,9,0,0,7);ctx.fill()}}
}
function drawStone(o){let t=o.type;ctx.save();ctx.translate(o.x,o.y);let glow=t==='stone'?(o.used?'#5a9ca0':'#e7b855'):t==='altar'?(state.weapon===(o.upgrade-1)?'#d8b55e':'#ba743e'):t==='vendor'?'#c99b45':'#71818a';ctx.shadowColor=glow;ctx.shadowBlur=t==='stone'&&!o.used?25:10;ctx.fillStyle='#1c2a30';ctx.strokeStyle='#856b43';ctx.lineWidth=3;if(t==='stone'){ctx.beginPath();ctx.moveTo(-20,18);ctx.lineTo(-13,-18);ctx.lineTo(5,-28);ctx.lineTo(19,-11);ctx.lineTo(16,20);ctx.closePath();ctx.fill();ctx.stroke();if(!o.used){ctx.fillStyle=glow;ctx.beginPath();ctx.arc(0,-4,5,0,7);ctx.fill();ctx.strokeStyle=glow;ctx.beginPath();ctx.arc(0,-4,11+Math.sin(gameTime*3)*2,0,7);ctx.stroke()}}else if(t==='altar'){ctx.fillStyle='#40372c';ctx.beginPath();ctx.ellipse(0,18,32,12,0,0,7);ctx.fill();ctx.fillStyle='#5b4931';ctx.fillRect(-5,-28,10,43);ctx.strokeStyle=glow;ctx.beginPath();ctx.moveTo(0,-28);ctx.lineTo(18,-42);ctx.lineTo(4,-56);ctx.lineTo(0,-38);ctx.lineTo(-16,-53);ctx.closePath();ctx.stroke();if(state.weapon===o.upgrade-1){ctx.fillStyle=glow;ctx.globalAlpha=.2;ctx.beginPath();ctx.arc(0,-40,30,0,7);ctx.fill()}}else if(t==='vendor'){ctx.fillStyle='#3e3023';ctx.fillRect(-28,4,56,17);ctx.fillStyle='#8b6d42';ctx.beginPath();ctx.ellipse(0,0,28,10,0,0,7);ctx.fill();ctx.fillStyle='#e1b557';ctx.beginPath();ctx.arc(0,-6,6,0,7);ctx.fill();ctx.font='10px Georgia';ctx.fillStyle='#201a13';ctx.textAlign='center';ctx.fillText('MERCHANT',0,18)}else if(t==='platePair'){ctx.fillStyle='#604b31';ctx.fillRect(-42,-11,84,22);ctx.strokeStyle='#bd9253';ctx.strokeRect(-35,-7,27,14);ctx.strokeRect(8,-7,27,14);ctx.fillStyle='#e5b85e';ctx.fillRect(-22,-2,4,4);ctx.fillRect(20,-2,4,4)}else if(t==='lightPuzzle'){ctx.save();ctx.globalAlpha=.35;ctx.strokeStyle='#8ae8f0';ctx.beginPath();ctx.moveTo(-60,0);ctx.lineTo(0,0);ctx.lineTo(30,-30);ctx.lineTo(70,-30);ctx.stroke();ctx.restore();ctx.fillStyle='#284853';ctx.beginPath();ctx.arc(0,0,18,0,7);ctx.fill();ctx.strokeStyle='#7bd7e0';ctx.stroke();ctx.fillStyle='#b0fbff';ctx.beginPath();ctx.arc(0,0,5,0,7);ctx.fill()}else if(t==='final'){ctx.strokeStyle='#c18d4d';ctx.lineWidth=8;ctx.beginPath();ctx.arc(0,0,55,0,7);ctx.stroke();ctx.fillStyle='#51412c';ctx.beginPath();ctx.arc(0,0,40,0,7);ctx.fill();ctx.fillStyle=state.aether?'#ffe27a':'#70a5ac';ctx.beginPath();ctx.arc(0,0,10,0,7);ctx.fill()}else if(t==='chest'){ctx.fillStyle='#473321';ctx.fillRect(-23,-7,46,27);ctx.strokeStyle='#bd8b45';ctx.strokeRect(-23,-7,46,27);ctx.fillStyle='#dfb352';ctx.fillRect(-3,-7,6,27);ctx.fillStyle='#f4cc6a';ctx.fillRect(-4,3,8,7)}ctx.restore()}
function drawBreak(b){ctx.save();ctx.translate(b.x,b.y);ctx.fillStyle='#29343a';ctx.fillRect(-28,-38,56,76);ctx.strokeStyle='#101a20';ctx.lineWidth=2;ctx.strokeRect(-28,-38,56,76);ctx.strokeStyle=b.hp>1?'#53636a':'#9a7350';ctx.lineWidth=1.2;ctx.beginPath();ctx.moveTo(-20,-38);ctx.lineTo(-8,-12);ctx.lineTo(-15,4);ctx.lineTo(0,19);ctx.lineTo(-6,38);ctx.moveTo(-8,-12);ctx.lineTo(15,-20);ctx.moveTo(0,19);ctx.lineTo(20,3);ctx.moveTo(0,19);ctx.lineTo(17,38);ctx.stroke();if(state.lantern){ctx.fillStyle='#8ee4e866';ctx.fillRect(-26,-36,52,72);ctx.fillStyle='#bdfbff';ctx.font='11px system-ui';ctx.textAlign='center';ctx.fillText('✦',0,5)}if(state.weapon===2){ctx.shadowBlur=12;ctx.shadowColor='#ffc95c';ctx.strokeStyle='#ffc95c';ctx.strokeRect(-28,-38,56,76)}ctx.restore()}
function drawPlayer(){ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.face);let moving=Math.sin(gameTime*13)*(p.moving?2:0);ctx.shadowColor=state.weapon===2?'#ffd45a':state.weapon===1?'#ff762e':state.lantern?'#65cde5':'#000';ctx.shadowBlur=state.weapon?20:state.lantern?15:0;ctx.fillStyle=state.cloak?'#171727':'#3b3028';ctx.beginPath();ctx.moveTo(-12,0);ctx.lineTo(8,-14);ctx.lineTo(-5+ moving,-3);ctx.lineTo(5,13);ctx.lineTo(-16,15);ctx.closePath();ctx.fill();ctx.strokeStyle=state.weapon===2?'#cfb361':'#72533a';ctx.stroke();ctx.fillStyle='#d6b486';ctx.beginPath();ctx.arc(8,0,9,0,7);ctx.fill();ctx.fillStyle='#17232a';ctx.fillRect(10,-5,6,10);ctx.fillStyle='#1b2026';ctx.fillRect(7,7,14,7);if(state.band){ctx.strokeStyle='#d6e3e6';ctx.beginPath();ctx.arc(6,7,3,0,7);ctx.stroke()}if(state.cloak){ctx.strokeStyle='#665488';ctx.beginPath();ctx.moveTo(-15,11);ctx.lineTo(-4,15);ctx.stroke()}ctx.strokeStyle=state.weapon===2?'#ffdf6f':state.weapon===1?'#ff862f':'#c2b092';ctx.lineWidth=state.weapon===2?6:state.weapon===1?4:3;ctx.beginPath();ctx.moveTo(10,10);ctx.lineTo(35,2);ctx.stroke();if(state.aether){ctx.fillStyle='#8fe7f2';ctx.shadowColor='#75dff5';ctx.shadowBlur=15;ctx.beginPath();ctx.moveTo(3,-10);ctx.lineTo(10,-15);ctx.lineTo(14,-8);ctx.lineTo(8,-3);ctx.closePath();ctx.fill()}ctx.restore();if(hurtFx>0){ctx.strokeStyle=`rgba(255,60,45,${hurtFx})`;ctx.lineWidth=3;ctx.beginPath();ctx.arc(p.x,p.y,25,0,7);ctx.stroke()}}
function drawEnemy(e){if(e.dead>0)return;ctx.save();ctx.translate(e.x,e.y);ctx.rotate(Math.sin(gameTime*2+e.phase)*.08);ctx.shadowColor=e.boss?'#d55235':'#000';ctx.shadowBlur=e.boss?12:0;ctx.fillStyle=e.boss?'#3d2e30':'#30383b';ctx.strokeStyle=e.boss?'#b35d3c':'#756b56';ctx.lineWidth=2;ctx.beginPath();ctx.arc(0,0,e.r,0,7);ctx.fill();ctx.stroke();ctx.fillStyle=e.boss?'#67443a':'#566064';ctx.fillRect(-e.r*.7,-e.r*.75,e.r*1.4,e.r*1.5);ctx.fillStyle=e.flash>0?'#fff1c0':'#bb6551';ctx.beginPath();ctx.arc(0,-e.r*.35,4,0,7);ctx.fill();ctx.strokeStyle='#8d7a62';ctx.beginPath();ctx.moveTo(-e.r,-e.r);ctx.lineTo(-e.r-8,-e.r-9);ctx.moveTo(e.r,e.r*.5);ctx.lineTo(e.r+9,e.r*.5+5);ctx.stroke();if(e.boss){ctx.strokeStyle='#c15b3b';ctx.beginPath();ctx.arc(0,0,e.r+7,0,7);ctx.stroke()}ctx.restore();ctx.fillStyle='#301e1e';ctx.fillRect(e.x-e.r,e.y-e.r-9,e.r*2,3);ctx.fillStyle=e.boss?'#cf5b40':'#d49b56';ctx.fillRect(e.x-e.r,e.y-e.r-9,e.r*2*Math.max(0,e.hp/e.max),3)}
function draw(){ctx.clearRect(0,0,W,H);drawBackdrop();drawPlayer();props.filter(o=>!o.used||o.type==='stone').forEach(drawStone);if(npc)drawStone(npc);if(shopAltar)drawStone({...shopAltar,type:'vendor'});breakables.filter(b=>b.hp>0).forEach(drawBreak);for(const s of switches){ctx.save();ctx.strokeStyle=s.on?'#8bf0b2':'#70bdd0';ctx.shadowColor=ctx.strokeStyle;ctx.shadowBlur=s.on?25:12;ctx.fillStyle='#183540';ctx.beginPath();ctx.arc(s.x,s.y,18,0,7);ctx.fill();ctx.stroke();ctx.fillStyle=ctx.strokeStyle;ctx.beginPath();ctx.arc(s.x,s.y,5,0,7);ctx.fill();ctx.restore()}for(const it of items)if(!it.used){ctx.save();ctx.shadowColor=it.type==='gold'?'#e6b957':'#8be6ee';ctx.shadowBlur=15;ctx.fillStyle=it.type==='gold'?'#e5b64f':it.type==='health'?'#ed7774':it.type==='aether'?'#8be5f0':'#c9f5de';ctx.beginPath();ctx.arc(it.x,it.y,6+Math.sin(gameTime*4)*2,0,7);ctx.fill();ctx.restore()}enemies.forEach(drawEnemy);for(const q of shots){ctx.save();ctx.shadowColor='#7ee8ff';ctx.shadowBlur=18;ctx.strokeStyle='#b8f6ff';ctx.lineWidth=3;ctx.beginPath();ctx.moveTo(q.x-q.vx*.035,q.y-q.vy*.035);ctx.lineTo(q.x,q.y);ctx.stroke();ctx.restore()}for(const q of particles){ctx.globalAlpha=q.life/q.max;ctx.fillStyle=q.c;ctx.beginPath();ctx.arc(q.x,q.y,q.r*q.life/q.max,0,7);ctx.fill()}ctx.globalAlpha=1;for(const q of labels){ctx.globalAlpha=Math.min(1,q.life*2);ctx.fillStyle=q.c;ctx.font='bold 12px system-ui';ctx.textAlign='center';ctx.fillText(q.t,q.x,q.y)}ctx.globalAlpha=1;if(modalType==='intro'){ctx.fillStyle='#0b111966';ctx.fillRect(0,0,W,H)}if(shake>0){ctx.restore?ctx.setTransform(DPR,0,0,DPR,rand(-shake,shake),rand(-shake,shake)):0}}
function loop(t){let dt=Math.min(.033,(t-last)/1000||0);last=t;update(dt);draw();requestAnimationFrame(loop)}
intro();requestAnimationFrame(loop);
</script>
</body></html>
<!-- agent-meta {"model":"stealth/space-bunny-alpha","provider":"openrouter","persona":"full","sessionId":"30bf8c46-deec-42be-b70a-8e94a7e4cf61","tokensIn":535546,"tokensOut":18676,"tokensTotal":554222,"cost":0,"turns":16,"toolCalls":15,"failedToolCalls":1,"timestamp":"2026-09-23T18:06:33.156Z"} -->