← Warden results

Warden

GPT 6 Sol · 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 45,219 bytes · SHA-256 c08b97411881
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>The Warden's Corrosion</title><style>
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=DM+Sans:wght@400;500;600&display=swap');
:root{--gold:#dcb66c;--pale:#f4e3bd;--muted:#9aa9ae;--blue:#88cbdc}*{box-sizing:border-box}html,body{margin:0;width:100%;min-height:100%;background:#070e18;color:var(--pale);font-family:'DM Sans',sans-serif}body{overflow-x:hidden;background:radial-gradient(ellipse at 50% 40%,#1c2a37 0,#0a111b 70%)}.shell{width:min(1120px,100%);margin:auto;padding:22px 20px 30px}.top{display:flex;align-items:center;justify-content:space-between;gap:15px;margin-bottom:13px}.eyebrow{font-size:10px;letter-spacing:.32em;color:#b99260;text-transform:uppercase}.brand{font:700 clamp(21px,3vw,31px) 'Cinzel',Georgia,serif;letter-spacing:.065em;line-height:1.25;color:#f5dfad;text-shadow:0 0 25px #c7833244}.topright{display:flex;gap:13px;align-items:center}.smallbtn{background:#172632;border:1px solid #7b6746;color:#eacb93;border-radius:3px;padding:9px 13px;font:600 11px 'DM Sans',sans-serif;letter-spacing:.09em;cursor:pointer}.smallbtn:hover{background:#293a42;border-color:#dfbd79}.stage{position:relative;border:1px solid #95764d;box-shadow:0 0 0 5px #111d27,0 14px 45px #000b, inset 0 0 20px #000;border-radius:3px;overflow:hidden;background:#14212d}canvas{display:block;width:100%;height:auto;aspect-ratio:16/9}.hud{position:absolute;top:0;left:0;right:0;pointer-events:none;background:linear-gradient(#071019e8,#071019af 71%,transparent);padding:13px 19px 23px;display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.zoneLabel{font:600 16px 'Cinzel',serif;letter-spacing:.07em;color:#f0d8a1}.zoneSub{font-size:10px;letter-spacing:.16em;color:#99abb0;text-transform:uppercase;margin-top:2px}.hudright{text-align:right}.hearts{font-size:23px;letter-spacing:2px;line-height:1;color:#db765c;text-shadow:0 0 10px #d44735aa;white-space:nowrap}.hearts .empty{color:#56606a;text-shadow:none}.hearts .half{color:#cf9b8c}.stats{font-size:11px;letter-spacing:.06em;color:#d9c39a;margin-top:5px}.bottomOverlay{position:absolute;bottom:0;left:0;right:0;padding:22px 18px 13px;background:linear-gradient(transparent,#08111ddc);pointer-events:none;display:flex;justify-content:space-between;align-items:end;gap:16px}.objective{font-size:12px;line-height:1.45;color:#e5d5b6;max-width:70%}.objective b{color:#e9ab5e;text-transform:uppercase;letter-spacing:.14em;font-size:9px;display:block;margin-bottom:4px}.roomCount{font:12px 'Cinzel',serif;color:#ceb682;white-space:nowrap}.prompt{position:absolute;left:50%;bottom:70px;transform:translateX(-50%);background:#081722eb;border:1px solid #b68b51;padding:8px 14px;color:#ffdfa3;font-size:12px;letter-spacing:.03em;pointer-events:none;white-space:nowrap;box-shadow:0 3px 15px #0008}.prompt:empty{display:none}.toast{position:absolute;top:81px;left:50%;transform:translateX(-50%);text-align:center;min-width:250px;max-width:80%;background:#091721ee;border:1px solid #af8953;padding:12px 22px;color:#f6e4bc;font:13px 'Cinzel',serif;letter-spacing:.04em;pointer-events:none;opacity:0;transition:opacity .3s;box-shadow:0 10px 30px #0009}.toast.show{opacity:1}.below{display:grid;grid-template-columns:1fr auto;gap:16px;margin-top:17px;align-items:start}.story{border-left:2px solid #9e764a;padding:3px 0 3px 16px;color:#a9b3b5;font:13px/1.6 'DM Sans',sans-serif;max-width:700px}.story em{color:#e1c699;font-style:normal}.controls{text-align:right;color:#94a4a9;font-size:11px;line-height:1.8;letter-spacing:.025em}.controls kbd{font:600 10px 'DM Sans';color:#edd6a3;border:1px solid #776a54;border-radius:2px;padding:2px 5px;background:#14222c}.modalWrap{position:absolute;inset:0;background:#050b14cf;display:none;align-items:center;justify-content:center;padding:20px;z-index:5}.modalWrap.open{display:flex}.modal{width:min(610px,100%);max-height:90%;overflow:auto;background:linear-gradient(145deg,#1c2d38,#101b27 75%);border:1px solid #c19a5d;box-shadow:0 20px 60px #000d, inset 0 0 0 5px #17232a;padding:24px 28px}.modal h2{font:600 25px 'Cinzel',serif;margin:0 0 5px;color:#f7dba0}.modal .lead{font:12px/1.55 'DM Sans';color:#adb9ba;margin:0 0 19px}.modal .sectionTitle{font:11px 'Cinzel';letter-spacing:.16em;color:#d7b477;border-bottom:1px solid #5b625f;padding-bottom:8px;margin:16px 0 6px}.row{display:flex;justify-content:space-between;align-items:center;gap:15px;border-bottom:1px solid #63707236;padding:10px 0}.row strong{font:600 13px 'Cinzel';color:#f0d9ab}.row small{display:block;color:#9aadb2;font-size:11px;line-height:1.45;margin-top:3px}.row button,.modal .close,.primary{background:#62472c;color:#fff0c9;border:1px solid #d4a761;border-radius:2px;padding:8px 12px;min-width:84px;cursor:pointer;font:600 11px 'DM Sans';white-space:nowrap}.row button:hover,.modal .close:hover,.primary:hover{background:#946834}.row button:disabled{opacity:.4;cursor:not-allowed}.modal .close{margin-top:20px;background:#1c303c;float:right}.intro{position:absolute;inset:0;z-index:4;background:radial-gradient(ellipse at 50% 48%,#263743 0%,#0b1521 73%);display:flex;align-items:center;justify-content:center;text-align:center;padding:30px}.intro.hidden{display:none}.introInner{max-width:620px}.intro .glyph{font-size:55px;color:#daa75d;text-shadow:0 0 35px #e59c4b;line-height:1}.intro h1{font:600 clamp(30px,5vw,51px) 'Cinzel',serif;color:#f7dcac;margin:20px 0 8px;text-shadow:0 0 30px #b9753877}.intro .tag{font:11px 'Cinzel';letter-spacing:.23em;text-transform:uppercase;color:#cb9e60}.intro p{font-size:14px;line-height:1.7;color:#aebfc0;margin:24px auto}.intro .primary{font:600 13px 'Cinzel';letter-spacing:.15em;padding:12px 27px}.intro .hint{color:#819599;font-size:11px;margin-top:25px}.touch{display:none;justify-content:space-between;margin-top:15px;user-select:none;touch-action:none}.touch button{width:49px;height:46px;background:#223541;border:1px solid #8c724f;color:#eacb96;font-size:19px;border-radius:3px}.touch .pad{display:grid;grid-template-columns:repeat(3,49px);gap:3px}.touch .actions{display:flex;gap:6px;align-items:end;flex-wrap:wrap;justify-content:end}.touch .actions button{font-size:11px;width:56px}@media(max-width:700px){.shell{padding:12px 10px}.topright{gap:5px}.smallbtn{padding:7px;font-size:10px}.brand{font-size:19px}.hud{padding:8px 9px 20px}.zoneLabel{font-size:11px}.zoneSub{font-size:8px}.hearts{font-size:16px}.stats{font-size:9px}.bottomOverlay{padding:12px 8px 6px}.objective{font-size:9px}.objective b{font-size:8px}.roomCount{font-size:9px}.below{grid-template-columns:1fr}.controls{text-align:left}.touch{display:flex}.prompt{font-size:10px;bottom:48px;padding:5px 7px}.toast{top:47px;font-size:10px;padding:7px}.modal{padding:18px}}
</style></head><body><div class="shell"><header class="top"><div><div class="eyebrow">Aurelion's Sunken Observatory</div><div class="brand">THE WARDEN'S CORROSION</div></div><div class="topright"><button class="smallbtn" id="inventoryBtn">INVENTORY [I]</button><button class="smallbtn" id="soundBtn">♪ SOUND ON</button></div></header><div class="stage"><canvas id="game" width="960" height="540" aria-label="Warden's Corrosion game world"></canvas><div class="hud"><div><div class="zoneLabel" id="zoneLabel"></div><div class="zoneSub" id="zoneSub"></div></div><div class="hudright"><div class="hearts" id="hearts"></div><div class="stats" id="stats"></div></div></div><div class="toast" id="toast"></div><div class="prompt" id="prompt"></div><div class="bottomOverlay"><div class="objective" id="objective"></div><div class="roomCount" id="roomCount"></div></div><div class="intro" id="intro"><div class="introInner"><div class="glyph">✦</div><div class="tag">A journey through a dying sky</div><h1>THE WARDEN'S<br>CORROSION</h1><p>Above a valley of endless storms, the last light of Aurelion is failing. You are the Last Warden. Reignite the Core Beacon before black iron consumes the observatory.</p><button class="primary" id="startBtn">BEGIN THE VIGIL</button><div class="hint">WASD / ARROWS move · SPACE slash · K fire · E interact · I inventory</div></div></div><div class="modalWrap" id="modalWrap"><div class="modal" id="modal"></div></div></div><div class="below"><div class="story" id="story"></div><div class="controls"><div><kbd>WASD</kbd> Move &nbsp; <kbd>SPACE</kbd> Slash &nbsp; <kbd>K</kbd> Aether bolt &nbsp; <kbd>E</kbd> Interact</div><div><kbd>Q</kbd> Vital Stem &nbsp; <kbd>F</kbd> Binding Seal &nbsp; <kbd>I</kbd> Inventory &nbsp; <kbd>ESC</kbd> Close</div></div></div><div class="touch"><div class="pad"><span></span><button data-key="ArrowUp">▲</button><span></span><button data-key="ArrowLeft">◀</button><button data-key="ArrowDown">▼</button><button data-key="ArrowRight">▶</button></div><div class="actions"><button data-key="Space">SLASH</button><button data-key="KeyK">BOLT</button><button data-key="KeyE">USE</button><button data-key="KeyQ">STEM</button><button data-key="KeyI">BAG</button></div></div></div><script>
(()=>{'use strict';
const c=document.getElementById('game'),ctx=c.getContext('2d'),W=960,H=540;const $=id=>document.getElementById(id);const names=['The Ashen Threshold','The Rusted Archives','The Collapsed Gallery','The Sunken Vault','The Twin Spire','The Core Chamber'];const subtitles=['THE LAST VIGIL BEGINS','IRON, DUST & FORGOTTEN WORDS','THE FIRST FLAME','BENEATH THE STAR-WATER','ASCENT THROUGH THE STORM','THE HEART OF AURELION'];const stories=["The Observatory remembers your footsteps. Its first sanctuary waits to the west; the constructs ahead do not remember mercy.","Bronze shelves buckle under centuries of dust. A merchant altar glows beyond the sentinels; something silver sleeps in a northern chest.","A cracked wall conceals a heartbeat. Beyond the altar, two bronze plates must be pressed before their mechanism forgets.","Under cold mist, the smallest cracks guard the greatest gift. A distant prism answers only to a shard of aether.","The spires break against the storm. Rusted floor vents spit black iron; a Shadow Cloak softens their bite. An old altar and a far crystal await.","The Beacon stands dark. Avoid the corroded floor vents, strike the remote prism, and silence the Corrosion's champion."];
let sound=true,audio=null;function tone(freq,dur=.1,type='sine',vol=.08,delay=0){if(!sound)return;try{audio=audio||new(window.AudioContext||window.webkitAudioContext)();if(audio.state==='suspended')audio.resume();let o=audio.createOscillator(),g=audio.createGain();o.type=type;o.frequency.setValueAtTime(freq,audio.currentTime+delay);g.gain.setValueAtTime(vol,audio.currentTime+delay);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+delay+dur);o.connect(g).connect(audio.destination);o.start(audio.currentTime+delay);o.stop(audio.currentTime+delay+dur+.01)}catch(e){}}function sfx(kind){if(kind==='step'){tone(S.zone===1?145:S.zone===3?115:90,.055,S.zone===1?'square':'triangle',.023);if(S.zone===3)tone(330,.12,'sine',.012,.04)}if(kind==='attack'){tone(370,.11,'sawtooth',.06);tone(165,.14,'triangle',.06,.045);if(S.upgrade)tone(S.upgrade===2?90:720,.17,'sawtooth',.045,.05)}if(kind==='hit'){tone(170,.18,'square',.055);tone(720,.13,'sine',.035,.03)}if(kind==='bolt'){tone(950,.19,'sine',.11);tone(420,.17,'triangle',.05,.04)}if(kind==='coin'){tone(570,.08,'sine',.08);tone(870,.12,'sine',.065,.07)}if(kind==='item'){[420,560,700,940].forEach((f,i)=>tone(f,.32,'sine',.055,i*.08))}if(kind==='save'){[260,390,520,780].forEach((f,i)=>tone(f,.65,'sine',.045,i*.09))}if(kind==='door'){tone(125,.4,'sawtooth',.055);tone(320,.26,'triangle',.055,.13)}}
const defaultState=()=>({zone:0,x:125,y:295,dirX:1,dirY:0,hp:6,max:6,gold:0,upgrade:0,shard:false,ammo:0,stems:1,seals:0,band:false,lantern:false,cloak:false,equipped:{band:false,lantern:false,cloak:false},flags:{},checkpoint:0,shopStock:{stem:3,seal:2,heart:1},won:false});let S=defaultState(),P=[],enemies=[],bolts=[],particles=[],textFx=[],keys={},modalType='',toastTimer=0,flash=0,shimmer=0,attackTime=0,attackCD=0,boltCD=0,iframes=0,stepTimer=0,frame=0,last=0,screenShake=0;
const flag=k=>!!S.flags[k],setFlag=k=>S.flags[k]=true;const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));const dist=(x,y,a,b)=>Math.hypot(x-a,y-b);function show(msg,t=3){$('toast').textContent=msg;$('toast').classList.add('show');toastTimer=t}function burst(x,y,col,n=16,power=100){for(let i=0;i<n;i++){let a=Math.random()*Math.PI*2,sp=(.3+Math.random())*power;particles.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-25,life:.45+Math.random()*.6,max:1,r:1+Math.random()*3,col})}}function floater(x,y,txt,col='#ffe2a0'){textFx.push({x,y,txt,col,life:1.15})}function persist(){try{localStorage.setItem('warden-save-v2',JSON.stringify(S))}catch(e){}}function load(){try{let v=JSON.parse(localStorage.getItem('warden-save-v2'));if(v&&typeof v.zone==='number'){S=Object.assign(defaultState(),v);S.equipped=Object.assign(defaultState().equipped,S.equipped||{});S.flags=S.flags||{};S.shopStock=S.shopStock||{stem:3,seal:2,heart:1};return true}}catch(e){}return false}
const defs=[{x:405,y:230,type:'sentinel',hp:4},{x:690,y:355,type:'instrument',hp:3}],enemyDefs=[defs,[{x:330,y:275,type:'sentinel',hp:5},{x:660,y:310,type:'instrument',hp:4}],[{x:345,y:260,type:'sentinel',hp:6},{x:645,y:300,type:'sentinel',hp:6}],[{x:305,y:275,type:'instrument',hp:6},{x:680,y:360,type:'sentinel',hp:7}],[{x:315,y:260,type:'sentinel',hp:8},{x:560,y:230,type:'instrument',hp:6},{x:715,y:340,type:'sentinel',hp:8}],[{x:310,y:230,type:'sentinel',hp:9},{x:510,y:345,type:'instrument',hp:8},{x:705,y:260,type:'guardian',hp:25}]];function spawn(){enemies=enemyDefs[S.zone].map((d,i)=>({...d,id:i,dead:flag('dead'+S.zone+'_'+i),max:d.hp,hit:0,frozen:0,cool:Math.random(),wander:Math.random()*6}));bolts=[];particles=[];P=[];if(S.zone===0)P.push({x:105,y:245,type:'stone',id:0});if(S.zone===1)P.push({x:465,y:270,type:'stone',id:1},{x:675,y:170,type:'shop'},{x:265,y:142,type:'chest',id:'band'},{x:775,y:405,type:'cache',id:'archive'});if(S.zone===2)P.push({x:490,y:145,type:'altar',id:1},{x:190,y:147,type:'wall',id:'heart4',hp:flag('wallheart4')?0:3},{x:350,y:385,type:'plate',id:0},{x:630,y:385,type:'plate',id:1});if(S.zone===3)P.push({x:495,y:270,type:'wall',id:'shard',hp:flag('wallshard')?0:4},{x:625,y:270,type:'relic',id:'shard'},{x:750,y:145,type:'switch',id:'vault'},{x:805,y:287,type:'relic',id:'lantern'});if(S.zone===4)P.push({x:130,y:265,type:'stone',id:2},{x:445,y:145,type:'altar',id:2},{x:205,y:400,type:'chest',id:'cloak'},{x:595,y:400,type:'jar',id:'seal'},{x:735,y:400,type:'chest',id:'bonus'},{x:785,y:137,type:'switch',id:'spire'},{x:837,y:282,type:'relic',id:'heart5'});if(S.zone===5)P.push({x:778,y:144,type:'switch',id:'core'},{x:813,y:288,type:'beacon'});refresh()}
function alive(){return enemies.filter(e=>!e.dead)}function allClear(){return alive().length===0}function objective(){switch(S.zone){case 0:return allClear()?'Cross the eastern arch into the Archives.':'Defeat the constructs. Activate the sanctuary stone with E.';case 1:return allClear()?'Explore the chest and merchant altar, then travel east.':'Clear the constructs to awaken the merchant altar.';case 2:return !allClear()?'Defeat the Gallery sentinels; seek cracks and the ember altar.':!flag('altar1')?'Claim the Ignited Edge at the altar.':!flag('platesDone')?'Step on both bronze plates within 8 seconds. Look for a cracked wall.':'The eastern gate is open. The Vault awaits.';case 3:return !S.shard?'Break the hairline-cracked wall; claim what lies beyond.':!flag('switchvault')?'Fire your Aether Shard at the distant blue prism (K).':'A hidden light path shines. Take the lantern and advance east.';case 4:return !allClear()?'Survive the ascent; search the rubble for supplies.':!flag('altar2')?'Claim the Solar Falchion at the altar.':!flag('switchspire')?'Fire a bolt at the high crystal to reveal a heart.':'Collect the revealed heart, then pass into the Core.';case 5:return !flag('switchcore')?'Fire at the distant crystal; the Beacon remains sealed.':!allClear()?'Defeat the Corrosion champion and its guardians.':'Approach the Core Beacon and reignite it.'}}
function refresh(){let z=S.zone;$('zoneLabel').textContent=names[z];$('zoneSub').textContent=subtitles[z];let hearts='';for(let i=0;i<S.max/2;i++){hearts+=`<span class="${S.hp>=i*2+2?'':S.hp===i*2+1?'half':'empty'}">${S.hp>=i*2+2?'♥':S.hp===i*2+1?'♥':'♡'}</span>`}$('hearts').innerHTML=hearts;$('stats').textContent=`✦ ${S.gold} SIGILS   ·   ${['EMBER BLADE','IGNITED EDGE','SOLAR FALCHION'][S.upgrade]}   ·   ${S.shard?'◇ '+S.ammo+' BOLTS':'NO AETHER'}`;$('objective').innerHTML='<b>Current path</b>'+objective();$('roomCount').textContent=`0${z+1} / 06`;$('story').innerHTML='<em>FIELD NOTES · '+names[z]+'</em><br>'+stories[z]}
function enterZone(z,side){S.zone=z;S.x=side==='west'?835:125;S.y=300;spawn();persist();show(names[z],3);sfx('door');refresh()}function interact(){if(modalType||$('intro').classList.contains('hidden')===false)return;let near=P.filter(o=>dist(S.x,S.y,o.x,o.y)<76).sort((a,b)=>dist(S.x,S.y,a.x,a.y)-dist(S.x,S.y,b.x,b.y))[0];if(!near)return;let o=near,id=o.id;if(o.type==='stone'){S.checkpoint=S.zone;S.hp=S.max;persist();burst(o.x,o.y,'#f8d483',26);sfx('save');show('Sanctuary bound · Health restored · Progress saved');refresh()}else if(o.type==='shop'){if(!allClear()){show('The merchant altar waits for quiet.');return}openModal('shop')}else if(o.type==='altar'){if(!allClear()){show('The altar cannot awaken during combat.');return}if(S.upgrade>=id){show('The altar rests. Its fire is yours.');return}if(id===2&&!S.shard){show('The altar requires the Aether Shard.');return}S.upgrade=id;setFlag('altar'+id);shimmer=2;burst(o.x,o.y,id===1?'#ef8238':'#f9d77d',44);sfx('save');show(id===1?'IGNITED EDGE · Faster strikes, burning embers.':'SOLAR FALCHION · Heavy, far-reaching solar sweep.',4);persist();refresh()}else if(o.type==='chest'||o.type==='cache'||o.type==='jar'){if(flag('taken'+id)){show('Only dust remains.');return}setFlag('taken'+id);let msg='';if(id==='band'){S.band=true;msg="Warden's Band found · Equip it in Inventory to strike faster."}if(id==='archive'){S.stems++;S.gold+=40;msg='Hidden cache · Vital Stem + 40 sigils.'}if(id==='cloak'){S.cloak=true;msg='Shadow Cloak found · Equip to soften trap damage.'}if(id==='seal'){S.seals++;msg='Binding Seal found · Freeze a nearby foe with F.'}if(id==='bonus'){S.gold+=175;S.stems++;msg='Spire treasury · 175 sigils + a Vital Stem.'}burst(o.x,o.y,'#f8e7b4',30);sfx('item');show(msg,4);persist();refresh()}else if(o.type==='relic'){if(id==='shard'){if(!flag('wallshard')){show('A fragile wall conceals the relic.');return}if(S.shard){show('The crystal cradle is empty.');return}S.shard=true;S.ammo=12;setFlag('takenshard');show('AETHER SHARD · Press K to fire. 12 charges acquired.',4)}if(id==='lantern'){if(!flag('switchvault')){show('The light has not found this pedestal.');return}if(S.lantern){show('The lantern already lights your way.');return}S.lantern=true;show('PHANTOM LANTERN · Equip to reveal hidden light paths.',4)}if(id==='heart5'){if(!flag('switchspire')){show('A distant mechanism seals this reliquary.');return}if(S.max>=10){show('Your heart is already whole.');return}if(S.max<8){show('This final heart needs the lost Gallery heart to hold it.');return}gainHeart()}burst(o.x,o.y,'#e9efff',32);sfx('item');persist();refresh()}else if(o.type==='beacon'){if(!flag('switchcore')||!allClear()){show('The Beacon remains bound by iron and shadow.');return}S.won=true;persist();openModal('ending');sfx('save');burst(o.x,o.y,'#ffe6a7',70)}else if(o.type==='wall'){show('Hairline cracks. Strike this wall with SPACE.')}else if(o.type==='switch'){show('Too distant to touch. A crystal bolt might reach it.')}else if(o.type==='plate'){show('The bronze plate sinks beneath your weight.')}}function gainHeart(){if(S.max>=10)return;S.max+=2;S.hp=Math.min(S.max,S.hp+2);shimmer=2;burst(S.x,S.y,'#ffce78',43);sfx('save');show('NEW HEART CONTAINER · Your life grows by 2 HP.',4)}
function slash(){if(attackCD>0||modalType||!$('intro').classList.contains('hidden'))return;attackTime=S.upgrade===2?.34:.24;attackCD=(S.upgrade===2?.68:S.upgrade===1?.32:.46)*(S.equipped.band?.82:1);sfx('attack');let range=S.upgrade===2?105:75;let hits=0;enemies.forEach(e=>{if(!e.dead&&dist(S.x,S.y,e.x,e.y)<range+15){e.hp-=S.upgrade===2?5:S.upgrade===1?3:2;e.hit=.24;hits++;burst(e.x,e.y,S.upgrade===2?'#ffe49b':'#ef8e47',13);floater(e.x,e.y-20,'−'+(S.upgrade===2?5:S.upgrade===1?3:2));if(e.hp<=0)killEnemy(e)}});P.filter(o=>o.type==='wall'&&o.hp>0).forEach(o=>{if(dist(S.x,S.y,o.x,o.y)<range+31){o.hp-=S.upgrade===2?3:1;hits++;burst(o.x,o.y,'#c49768',13);if(o.hp<=0){setFlag('wall'+o.id);if(o.id==='heart4'){if(S.max<8)gainHeart();show('Behind the ruined wall: a hidden heart container!',4)}else show('The wall falls. A crystal cradle lies beyond.',4);persist();refresh()}else show('The cracked masonry gives way...')}});if(hits){screenShake=S.upgrade===2?8:3;tone(S.upgrade===2?95:155,.16,'triangle',.11)}burst(S.x+S.dirX*30,S.y+S.dirY*30,S.upgrade===2?'#f6cc6c':S.upgrade===1?'#ea793a':'#c9d4ce',S.upgrade===2?15:7,70)}function killEnemy(e){e.dead=true;setFlag('dead'+S.zone+'_'+e.id);let g=e.type==='guardian'?45:8+Math.floor(Math.random()*8);S.gold+=g;burst(e.x,e.y,'#daa75b',24);floater(e.x,e.y-28,'+'+g+' ✦','#ffda83');sfx('coin');if(S.zone===4&&e.type==='guardian'&&!S.cloak){S.cloak=true;show('The Guardian drops a Shadow Cloak! Equip it in Inventory.',4)}persist();refresh()}
function fire(){if(!S.shard){show('Find the Aether Shard in the Sunken Vault.');return}if(S.ammo<=0){show('No charges left. Return to the Archives merchant.');return}if(boltCD>0||modalType)return;S.ammo--;boltCD=.4;sfx('bolt');let targets=[...P.filter(o=>o.type==='switch'&&!flag('switch'+o.id)),...enemies.filter(e=>!e.dead)];let target=targets.sort((a,b)=>dist(S.x,S.y,a.x,a.y)-dist(S.x,S.y,b.x,b.y))[0];let dx=target?target.x-S.x:S.dirX*100,dy=target?target.y-S.y:S.dirY*100,mag=Math.hypot(dx,dy)||1;bolts.push({x:S.x,y:S.y-8,vx:dx/mag*610,vy:dy/mag*610,life:1.6});burst(S.x,S.y,'#8aeaff',8);refresh()}
function useStem(){if(!S.stems){show('No Vital Stems remain.');return}if(S.hp===S.max){show('Your hearts are already full.');return}S.stems--;S.hp=Math.min(S.max,S.hp+4);burst(S.x,S.y,'#91dfa1',23);sfx('item');show('Vital Stem · 4 HP restored.');persist();refresh()}function useSeal(){if(!S.seals){show('No Binding Seals remain.');return}let e=alive().sort((a,b)=>dist(S.x,S.y,a.x,a.y)-dist(S.x,S.y,b.x,b.y))[0];if(!e||dist(S.x,S.y,e.x,e.y)>230){show('No construct close enough to bind.');return}S.seals--;e.frozen=3.2;burst(e.x,e.y,'#72d5f9',24);sfx('bolt');show('Binding Seal · Construct frozen for 3 seconds.');persist();refresh()}
function hurt(dmg=1,trap=false){if(iframes>0)return;let actual=trap&&S.equipped.cloak?Math.max(0,dmg-1):dmg;if(!actual){show('Shadow Cloak absorbs the hazard.');iframes=.6;return}S.hp-=actual;iframes=1.05;flash=.38;screenShake=7;burst(S.x,S.y,'#f05f5d',24);floater(S.x,S.y-23,'−'+actual,'#ff8e84');sfx('hit');refresh();if(S.hp<=0){S.hp=0;setTimeout(()=>{if(S.hp<=0){S.zone=S.checkpoint;S.x=130;S.y=300;S.hp=S.max;spawn();show('The sanctuary draws you back from the dark.',4);refresh()}},800)}}
function openModal(type){modalType=type;$('modalWrap').classList.add('open');renderModal()}function closeModal(){modalType='';$('modalWrap').classList.remove('open')}function renderModal(){let m=$('modal');if(modalType==='inventory'){let row=(name,desc,btn,action,disabled=false)=>`<div class="row"><div><strong>${name}</strong><small>${desc}</small></div><button data-action="${action}" ${disabled?'disabled':''}>${btn}</button></div>`;m.innerHTML=`<h2>Warden's satchel</h2><p class="lead">Choose your accessories. Consumables are spent on use; their effects never trigger by themselves.</p><div class="sectionTitle">EQUIPMENT · ${['Ember Blade','Ignited Edge','Solar Falchion'][S.upgrade]} ${S.shard?' · Aether Shard ('+S.ammo+' bolts)':''}</div>${row("Warden's Band",S.band?'Silver wrist-ring · slightly faster attacks.':'Not yet found · hidden in the Rusted Archives.',S.equipped.band?'UNEQUIP':'EQUIP','band',!S.band)}${row('Phantom Lantern',S.lantern?'Blue shoulder light · reveals concealed light paths.':'Not yet found · solve the Vault light puzzle.',S.equipped.lantern?'UNEQUIP':'EQUIP','lantern',!S.lantern)}${row('Shadow Cloak',S.cloak?'Dark cloak hem · reduces environmental trap damage.':'Not yet found · search the Twin Spire.',S.equipped.cloak?'UNEQUIP':'EQUIP','cloak',!S.cloak)}<div class="sectionTitle">PROVISIONS</div>${row('Vital Stem × '+S.stems,'Restores 4 HP · also press Q.','USE','stem',!S.stems||S.hp===S.max)}${row('Binding Seal × '+S.seals,'Freezes the nearest construct for 3 seconds · also press F.','USE','seal',!S.seals)}<button class="close" data-action="close">CLOSE [ESC]</button>`}else if(modalType==='shop'){let shoprow=(name,desc,cost,stock,action,disabled)=>`<div class="row"><div><strong>${name}</strong><small>${desc} · ${stock} remaining</small></div><button data-action="${action}" ${disabled?'disabled':''}>${cost} ✦</button></div>`;m.innerHTML=`<h2>The Brass Merchant</h2><p class="lead">“Even at the edge of the sun, a trade can keep a flame alive.”<br>Your purse: <b style="color:#edd096">${S.gold} golden sigils</b></p>${shoprow('Vital Stem','Restores 4 HP',50,S.shopStock.stem,'buyStem',S.gold<50||!S.shopStock.stem)}${shoprow('Binding Seal','Freezes one nearby foe',75,S.shopStock.seal,'buySeal',S.gold<75||!S.shopStock.seal)}${shoprow('Heart Container','A permanent +2 maximum HP',300,S.shopStock.heart,'buyHeart',S.gold<300||!S.shopStock.heart||S.max!==8)}${S.shard?shoprow('Aether charge','One crystal bolt',10,'∞','buyAmmo',S.gold<10):'<p class="lead" style="margin-top:15px">Aether ammunition appears here once the shard is found.</p>'}<button class="close" data-action="close">LEAVE ALTAR [ESC]</button>`}else if(modalType==='ending'){m.innerHTML=`<h2>The Beacon Rekindled</h2><p class="lead" style="font-size:14px;line-height:1.8">Gold floods the broken observatory. The gears turn once more beneath Aurelion's wounded sky. The Corrosion recedes into the seams of the world.<br><br>You did not restore what was lost. You made a light for what remains.</p><div class="sectionTitle">THE LAST WARDEN · VIGIL COMPLETE</div><p class="lead">${S.max/2} heart containers · ${['Ember Blade','Ignited Edge','Solar Falchion'][S.upgrade]} · ${S.gold} sigils carried</p><button class="close" data-action="close">WALK AMONG THE STARS</button>`}}function handleAction(a){if(a==='close'){closeModal();return}if(['band','lantern','cloak'].includes(a)){S.equipped[a]=!S.equipped[a];sfx('item');show(S.equipped[a]?'Equipped '+a+'.':'Unequipped '+a+'.')}else if(a==='stem')useStem();else if(a==='seal')useSeal();else{let costs={buyStem:50,buySeal:75,buyHeart:300,buyAmmo:10},k={buyStem:'stem',buySeal:'seal',buyHeart:'heart'}[a];if(S.gold<costs[a]||(k&&!S.shopStock[k]))return;S.gold-=costs[a];if(k)S.shopStock[k]--;if(a==='buyStem')S.stems++;if(a==='buySeal')S.seals++;if(a==='buyHeart')gainHeart();if(a==='buyAmmo')S.ammo++;sfx('coin');sfx('item');show('Purchase complete · The merchant bows.');}persist();refresh();renderModal()}
$('modal').addEventListener('click',e=>{let a=e.target.closest('[data-action]');if(a)handleAction(a.dataset.action)});$('inventoryBtn').onclick=()=>modalType?closeModal():openModal('inventory');$('soundBtn').onclick=()=>{sound=!sound;$('soundBtn').textContent=sound?'♪ SOUND ON':'♪ SOUND OFF'};$('startBtn').onclick=()=>{$('intro').classList.add('hidden');if(load()){S.x=clamp(S.x,60,900);S.y=clamp(S.y,100,445);show('Your vigil resumes at '+names[S.zone]+'.',3)}else show('The last sanctuary welcomes you. Press E near the stone.',4);spawn();refresh()};function press(k){if(k==='Escape'){closeModal();return}if(k==='KeyI'){modalType?closeModal():openModal('inventory');return}if(modalType)return;if(k==='Space'||k==='KeyJ')slash();if(k==='KeyK')fire();if(k==='KeyE')interact();if(k==='KeyQ')useStem();if(k==='KeyF')useSeal()}window.addEventListener('keydown',e=>{let k=e.code;if(['Space','ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(k))e.preventDefault();if(!keys[k])press(k);keys[k]=true});window.addEventListener('keyup',e=>keys[e.code]=false);document.querySelectorAll('.touch button').forEach(b=>{b.addEventListener('pointerdown',e=>{e.preventDefault();let k=b.dataset.key;keys[k]=true;press(k);b.setPointerCapture(e.pointerId)});b.addEventListener('pointerup',()=>keys[b.dataset.key]=false);b.addEventListener('pointercancel',()=>keys[b.dataset.key]=false)});
function update(dt){frame+=dt;if(toastTimer>0){toastTimer-=dt;if(toastTimer<=0)$('toast').classList.remove('show')}attackCD=Math.max(0,attackCD-dt);attackTime=Math.max(0,attackTime-dt);boltCD=Math.max(0,boltCD-dt);iframes=Math.max(0,iframes-dt);flash=Math.max(0,flash-dt);shimmer=Math.max(0,shimmer-dt);screenShake=Math.max(0,screenShake-dt*26);if(!$('intro').classList.contains('hidden')||modalType)return;let dx=(keys.KeyD||keys.ArrowRight?1:0)-(keys.KeyA||keys.ArrowLeft?1:0),dy=(keys.KeyS||keys.ArrowDown?1:0)-(keys.KeyW||keys.ArrowUp?1:0);if(dx||dy){let l=Math.hypot(dx,dy);dx/=l;dy/=l;S.dirX=dx;S.dirY=dy;S.x+=dx*195*dt;S.y+=dy*195*dt;S.y=clamp(S.y,106,447);stepTimer-=dt;if(stepTimer<=0){stepTimer=S.zone===3?.26:.32;sfx('step');if(S.upgrade)burst(S.x,S.y+15,S.upgrade===2?'#e4bd60':'#aa5939',2,15)}}S.x=clamp(S.x,30,930);
if(S.x>915){if(canExit()){enterZone(S.zone+1,'east');return}else{S.x=914;if(Math.random()<dt*.8)show(exitHint(),2)}}if(S.x<45){if(S.zone>0){enterZone(S.zone-1,'west');return}else S.x=45}
if(S.zone>=4){let vents=S.zone===4?[[385,365],[675,270]]:[[390,375],[635,245]];for(let [vx,vy] of vents){if(dist(S.x,S.y,vx,vy)<30&&Math.sin(frame*2.5+vx)>-.18)hurt(2,true)}}if(S.zone===2){P.filter(o=>o.type==='plate').forEach(o=>{if(dist(S.x,S.y,o.x,o.y)<38&&!flag('platesDone')){if(!o.pressed){o.pressed=1;o.time=frame;burst(o.x,o.y,'#e7b974',12);sfx('door');show('Bronze plate engaged. Reach the other within 8 seconds!')}let other=P.find(q=>q.type==='plate'&&q!==o);if(other.pressed&&Math.abs(o.time-other.time)<8){setFlag('platesDone');show('Both plates answered. The eastern gate unlocks!',4);burst(890,270,'#e5bf76',30);sfx('save');persist();refresh()}}});if(!flag('platesDone')&&P.some(o=>o.type==='plate'&&o.pressed&&frame-o.time>8)){P.filter(o=>o.type==='plate').forEach(o=>o.pressed=0);show('The plates rise again. Try the sequence anew.')}}
for(let e of enemies){if(e.dead)continue;e.hit=Math.max(0,e.hit-dt);e.cool-=dt;if(e.frozen>0){e.frozen-=dt;continue}let d=dist(S.x,S.y,e.x,e.y);if(d<290&&d>35){let sp=e.type==='guardian'?54:e.type==='instrument'?79:67;e.x+=(S.x-e.x)/d*sp*dt;e.y+=(S.y-e.y)/d*sp*dt}else if(d>=290){e.wander+=dt;e.x+=Math.sin(e.wander+e.id)*9*dt;e.y+=Math.cos(e.wander*.7+e.id)*7*dt}e.x=clamp(e.x,65,880);e.y=clamp(e.y,112,445);if(d<42&&e.cool<=0){hurt(e.type==='guardian'?2:1);e.cool=e.type==='guardian'?1.6:1.35}}
for(let b of bolts){b.x+=b.vx*dt;b.y+=b.vy*dt;b.life-=dt;particles.push({x:b.x,y:b.y,vx:0,vy:0,life:.22,max:.22,r:3,col:'#8deaff'});let sw=P.find(o=>o.type==='switch'&&!flag('switch'+o.id)&&dist(b.x,b.y,o.x,o.y)<29);if(sw){b.life=0;setFlag('switch'+sw.id);burst(sw.x,sw.y,'#a7e9ff',37);sfx('save');show(sw.id==='vault'?'A hidden light path illuminates the Phantom Lantern.':sw.id==='spire'?'The distant reliquary opens. A heart shines below.':'The Core seal fractures in pale light.',4);persist();refresh()}let en=enemies.find(e=>!e.dead&&dist(b.x,b.y,e.x,e.y)<26);if(en&&b.life>0){b.life=0;en.hp-=3;en.hit=.3;burst(en.x,en.y,'#76d6ff',17);floater(en.x,en.y-20,'−3','#8fe8ff');tone(800,.2,'sine',.07);if(en.hp<=0)killEnemy(en)}}bolts=bolts.filter(b=>b.life>0&&b.x>0&&b.x<W&&b.y>60&&b.y<H-55);
particles.forEach(p=>{p.x+=p.vx*dt;p.y+=p.vy*dt;p.vx*=.98;p.vy+=24*dt;p.life-=dt});particles=particles.filter(p=>p.life>0);textFx.forEach(t=>{t.y-=26*dt;t.life-=dt});textFx=textFx.filter(t=>t.life>0);let near=P.filter(o=>dist(S.x,S.y,o.x,o.y)<76).sort((a,b)=>dist(S.x,S.y,a.x,a.y)-dist(S.x,S.y,b.x,b.y))[0];$('prompt').textContent=near?promptFor(near):''}
function canExit(){if(S.zone===0)return allClear();if(S.zone===1)return allClear();if(S.zone===2)return allClear()&&S.upgrade>=1&&flag('platesDone');if(S.zone===3)return S.shard&&flag('switchvault');if(S.zone===4)return allClear()&&S.upgrade>=2&&flag('switchspire');return false}function exitHint(){return ['Defeat the constructs to open the arch.','Clear the Archives before leaving.','The altar and both plates hold the gate shut.','The Shard and prism hold the Vault gate.','The Spire requires the solar blade and crystal.','The Beacon is here.'][S.zone]}function promptFor(o){let t={stone:'E  ·  Rest & bind sanctuary',shop:'E  ·  Visit the Brass Merchant',altar:'E  ·  Awaken weapon altar',wall:'SPACE  ·  Break cracked wall',plate:'Step onto the bronze plate',switch:'K  ·  Fire at distant prism',beacon:'E  ·  Reignite the Core Beacon',jar:'E  ·  Break the old jar',chest:'E  ·  Open ancient chest',cache:'E  ·  Search hidden cache',relic:'E  ·  Claim relic'};return t[o.type]||''}
function path(x1,y1,x2,y2,col,w=1){ctx.strokeStyle=col;ctx.lineWidth=w;ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke()}function circle(x,y,r,col){ctx.fillStyle=col;ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fill()}function glow(x,y,r,col){let g=ctx.createRadialGradient(x,y,1,x,y,r);g.addColorStop(0,col);g.addColorStop(1,'transparent');ctx.fillStyle=g;ctx.fillRect(x-r,y-r,r*2,r*2)}function poly(pts,col){ctx.fillStyle=col;ctx.beginPath();ctx.moveTo(pts[0][0],pts[0][1]);pts.slice(1).forEach(p=>ctx.lineTo(...p));ctx.closePath();ctx.fill()}function rounded(x,y,w,h,r,col){ctx.fillStyle=col;ctx.beginPath();ctx.roundRect(x,y,w,h,r);ctx.fill()}
function background(){let z=S.zone;let g=ctx.createLinearGradient(0,0,0,H);g.addColorStop(0,['#344051','#3c4145','#433c38','#243f4a','#343e50','#48434a'][z]);g.addColorStop(.52,['#202c38','#30373b','#3a3331','#203541','#2c3643','#39383d'][z]);g.addColorStop(1,'#192630');ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
// open sky, distant storm, ruined silhouette
ctx.fillStyle='#0c1824';ctx.fillRect(0,0,W,78);for(let i=0;i<23;i++){let x=(i*157+z*63)%970,y=8+(i*39)%65;circle(x,y,(i%3?1:1.6),'#bdac8b88')}glow(777,55,155,z===5?'#c9a46540':'#918a7640');circle(780,53,28,'#a4aa9b');circle(789,48,25,'#172837');for(let i=0;i<7;i++){let x=i*165-30;poly([[x,78],[x+40,20+(i%3)*16],[x+75,78]],'#14212bbb')}
// grand rear arcade
for(let i=0;i<5;i++){let x=i*208-23;rounded(x,76,23,350,4,'#101b25');rounded(x+159,76,19,350,3,'#111c25');ctx.strokeStyle='#9f815454';ctx.lineWidth=3;ctx.beginPath();ctx.arc(x+89,188,80,Math.PI,0);ctx.stroke();path(x+8,78,x+171,78,'#9e7d5366',3);rounded(x+23,86,136,155,65,'#192732aa');glow(x+87,145,92,z===3?'#42829a25':'#d7914620');path(x+6,217,x+171,217,'#b3956744',2)}
// floor perspective and inlay
ctx.fillStyle=['#28333a','#343838','#3b3835','#273e45','#333b43','#3b3837'][z];ctx.fillRect(0,235,W,305);ctx.fillStyle='#0b192449';ctx.fillRect(0,454,W,86);for(let y=255;y<550;y+=51){path(0,y,W,y,'#b9986050',1);for(let x=0;x<960;x+=110)path(x+(Math.floor(y/51)%2)*55,y,x+(Math.floor(y/51)%2)*55,y+51,'#a5875940',1)}path(0,237,W,237,'#d5ac684d',5);path(0,477,W,477,'#d5ac6855',4);for(let i=0;i<10;i++){let x=48+i*99;circle(x,249,2,'#d1b67888');circle(x,472,2,'#d1b67877')}
// circular celestial floor diagram
ctx.save();ctx.translate(477,352);ctx.strokeStyle='#ba92583c';for(let r of [63,101,145]){ctx.lineWidth=r===101?3:1;ctx.beginPath();ctx.arc(0,0,r,0,Math.PI*2);ctx.stroke()}for(let i=0;i<12;i++){let a=i*Math.PI/6;path(Math.cos(a)*104,Math.sin(a)*104,Math.cos(a)*144,Math.sin(a)*144,'#d2ab6544',2)}ctx.restore();
// decorative gears and rubble
for(let i=0;i<4;i++){let x=73+i*260,y=191+(i%2)*15;ctx.save();ctx.translate(x,y);ctx.rotate(frame*.015*(i%2?1:-1));ctx.strokeStyle='#846f5488';ctx.lineWidth=8;ctx.beginPath();ctx.arc(0,0,32,0,7);ctx.stroke();for(let k=0;k<10;k++){let a=k*Math.PI/5;path(Math.cos(a)*30,Math.sin(a)*30,Math.cos(a)*42,Math.sin(a)*42,'#a4865d77',7)}circle(0,0,11,'#1b2830');ctx.restore()}for(let i=0;i<12;i++){let x=(i*213+z*79)%970,y=218+(i*67)%240;poly([[x,y],[x+8,y-4],[x+16,y+3],[x+10,y+7]],'#131f26aa')}
// arch exits
if(z>0){glow(15,320,85,'#eab3682c');rounded(0,268,27,128,12,'#b6884c88');path(25,268,25,396,'#f5ca7977',3)}if(z<5){glow(948,315,90,canExit()?'#dfba765e':'#a445283b');rounded(930,264,30,135,10,canExit()?'#af8552':'#713e36');for(let i=0;i<5;i++)path(938,277+i*23,958,277+i*23,'#e3c47a99',2)}if(z>=4){let vents=z===4?[[385,365],[675,270]]:[[390,375],[635,245]];for(let [vx,vy] of vents){let active=Math.sin(frame*2.5+vx)>-.18;rounded(vx-30,vy-19,60,38,5,'#171d22');for(let j=-2;j<=2;j++)path(vx+j*10-3,vy-11,vx+j*10+3,vy+11,active?'#d3543c':'#7e563f',3);if(active){glow(vx,vy,44,'#e95a374e');for(let j=0;j<4;j++)circle(vx+Math.sin(frame*3+j*2)*20,vy-21-(frame*34+j*13)%25,1.7,'#e89765')}}}if(z===3){for(let i=0;i<14;i++){let x=(i*97)%960,y=370+(i*53)%90;circle(x+Math.sin(frame+i)*7,y-frame*10%115,2,'#a9dfeb45')}}if(z===1){for(let i=0;i<16;i++){let x=(i*73+17)%960,y=(i*107+frame*5)%450+85;circle(x,y,1.4,'#d4bc8a66')}}}
function drawObject(o){let {x,y,type,id}=o;ctx.save();if(type==='stone'){glow(x,y-22,69,'#d6b16d88');rounded(x-27,y+11,54,16,3,'#4e5352');rounded(x-20,y-30,40,42,9,'#81847c');rounded(x-15,y-25,30,31,6,'#4c6266');circle(x,y-12,9,'#ffe6a4');glow(x,y-11,26,'#f7dc8a99');ctx.strokeStyle='#f9e6bc';ctx.lineWidth=1;ctx.beginPath();ctx.arc(x,y-13,15,-2.7,-.4);ctx.stroke();for(let i=0;i<3;i++)circle(x+Math.sin(frame*2+i*3)*29,y-35-i*15-frame*8%12,1.5,'#f4d795')}else if(type==='shop'){glow(x,y-32,90,'#cd9c6260');rounded(x-38,y+12,76,13,3,'#6c6658');poly([[x-26,y+12],[x-21,y-40],[x,y-66],[x+21,y-40],[x+26,y+12]],'#4e605e');path(x-17,y-12,x+17,y-12,'#d5b57a',3);circle(x,y-33,10,'#edc981');glow(x,y-33,40,'#f2cd8b77');for(let i=0;i<3;i++)circle(x-28+i*28,y+13,5,'#d7ae64')}else if(type==='altar'){let on=S.upgrade>=id;glow(x,y-13,80,on?(id===1?'#ed784770':'#ffdc8d88'):'#a2764254');rounded(x-39,y+18,78,13,3,'#777365');rounded(x-28,y-10,56,31,5,'#666b65');poly([[x-13,y-10],[x,y-55],[x+13,y-10]],on?(id===1?'#fa783e':'#fff2a8'):'#ae8a61');circle(x,y-23,7,'#fff0b4');path(x-22,y+5,x+22,y+5,'#d2ab72',2)}else if(type==='wall'&&o.hp>0){rounded(x-31,y-38,63,79,3,'#171e25');rounded(x-29,y-36,59,75,2,'#615b52');ctx.strokeStyle='#191b20';ctx.lineWidth=3;ctx.beginPath();ctx.moveTo(x-15,y-35);ctx.lineTo(x+2,y-12);ctx.lineTo(x-8,y+6);ctx.lineTo(x+13,y+24);ctx.lineTo(x+5,y+38);ctx.moveTo(x+23,y-29);ctx.lineTo(x+2,y-12);ctx.lineTo(x+22,y+4);ctx.stroke();if(S.equipped.lantern)glow(x,y,57,'#86c9ff50')}else if(type==='plate'){rounded(x-31,y-12,62,33,5,o.pressed?'#9c8252':'#5c5b53');ctx.strokeStyle='#ccad6c';ctx.lineWidth=2;ctx.strokeRect(x-23,y-5,46,17);circle(x,y+3,5,'#d3b46b')}else if(type==='switch'){glow(x,y,50,flag('switch'+id)?'#d4ba8d7d':'#6fcedb80');poly([[x,y-26],[x+18,y],[x,y+26],[x-18,y]],flag('switch'+id)?'#f5d486':'#7bd4e3');poly([[x,y-17],[x+11,y],[x,y+17],[x-11,y]],'#e2f4ed');ctx.strokeStyle='#b7a577';ctx.lineWidth=3;ctx.strokeRect(x-27,y-30,54,60)}else if(type==='relic'){let visible=id==='shard'?flag('wallshard')&&!S.shard:id==='lantern'?flag('switchvault')&&!S.lantern:id==='heart5'?flag('switchspire')&&S.max<10:false;if(visible){glow(x,y-20,62,id==='heart5'?'#ffb68291':'#87d4e995');rounded(x-26,y+12,52,10,3,'#8e7d62');if(id==='heart5'){ctx.font='33px serif';ctx.fillStyle='#ffbb9a';ctx.fillText('♥',x-18,y+7)}else{poly([[x,y-46],[x+17,y-18],[x,y+6],[x-17,y-18]],'#b1eafa');circle(x,y-19,7,'#e1ffff')}}else if((id==='heart5'&&flag('switchspire'))||(id==='lantern'&&flag('switchvault')))rounded(x-24,y+12,48,8,2,'#777c75')}else if(type==='chest'||type==='cache'||type==='jar'){if(flag('taken'+id)){rounded(x-21,y+10,42,8,2,'#3d3934');ctx.restore();return}if(type==='jar'){rounded(x-13,y-20,26,34,7,'#716155');rounded(x-16,y-24,32,8,3,'#ae8760');glow(x,y,26,'#91bad14f')}else{glow(x,y-10,34,'#d7b56935');rounded(x-23,y-15,46,32,4,'#785d3d');rounded(x-25,y-21,50,15,5,'#a68652');path(x-23,y-1,x+23,y-1,'#d7ae6a',2);circle(x,y+3,4,'#f4d789')}}else if(type==='beacon'){glow(x,y-40,120,S.won?'#ffe9a2ae':'#ddaa6266');rounded(x-65,y+36,130,18,5,'#655d54');rounded(x-40,y-15,80,57,8,'#626c69');poly([[x-22,y-15],[x,y-100],[x+22,y-15]],S.won?'#fff5bd':'#93794e');circle(x,y-21,21,S.won?'#fff7c0':'#1b2b33');ctx.strokeStyle='#d6ad70';ctx.lineWidth=5;ctx.beginPath();ctx.arc(x,y-20,29,0,7);ctx.stroke();glow(x,y-20,36,S.won?'#fff8cddd':'#498da433')}ctx.restore()}
function drawEnemy(e){if(e.dead)return;let {x,y}=e,t=e.type,big=t==='guardian';ctx.save();ctx.translate(x,y);if(e.hit>0)glow(0,-10,big?60:38,'#ff9f6eaa');if(e.frozen>0)glow(0,0,45,'#78dcff99');circle(0,17,big?28:20,'#09121b99');ctx.fillStyle='#2c3032';poly([[-(big?24:16),-12],[-(big?30:21),14],[0,20],[(big?30:21),14],[(big?24:16),-12]],'#303335');poly([[-12,-19],[0,-31],[12,-19],[15,7],[0,16],[-15,7]],e.hit>0?'#b56d50':'#76695d');poly([[-10,-24],[0,-38],[10,-24]],'#a98a64');if(big){poly([[-35,-14],[-18,-29],[-13,0],[-31,4]],'#8f7860');poly([[35,-14],[18,-29],[13,0],[31,4]],'#8f7860');glow(0,-13,40,'#9b342e88')}circle(-5,-17,2.5,'#fc774d');circle(5,-17,2.5,'#fc774d');path(-10,0,10,0,'#cc684a',2);if(t==='instrument'){ctx.save();ctx.rotate(frame*.8);ctx.strokeStyle='#ad825c';ctx.lineWidth=4;ctx.beginPath();ctx.arc(0,-13,27,0,7);ctx.stroke();ctx.restore()}if(e.frozen>0){ctx.strokeStyle='#b7f3fa';ctx.lineWidth=2;ctx.beginPath();ctx.arc(0,-12,30,0,7);ctx.stroke()}ctx.restore();let w=big?65:38;rounded(x-w/2,y-(big?53:44),w,4,1,'#200e14');rounded(x-w/2,y-(big?53:44),w*e.hp/e.max,4,1,big?'#df8e51':'#c66c56')}
function drawPlayer(){let x=S.x,y=S.y;ctx.save();ctx.translate(x,y);let moving=keys.KeyW||keys.KeyS||keys.KeyA||keys.KeyD||keys.ArrowUp||keys.ArrowDown||keys.ArrowLeft||keys.ArrowRight,bob=moving?Math.sin(frame*15)*2:Math.sin(frame*2)*1.2;circle(0,18,20,'#07111dbb');if(S.upgrade)glow(0,-10,S.upgrade===2?60:40,S.upgrade===2?'#e7ba5a56':'#f4753948');if(S.equipped.lantern)glow(-18,-17,64,'#65bcff65');if(iframes>0&&Math.sin(frame*35)>0)glow(0,-14,43,'#fa5d5cbb');ctx.translate(0,bob);poly([[-13,-18],[-23,18],[0,29],[23,18],[13,-18]],S.equipped.cloak?'#131a29':'#2b4050');poly([[-11,-15],[-18,15],[0,22],[18,15],[11,-15]],'#425263');if(S.equipped.cloak){path(-20,17,0,28,'#7780a2',3);path(0,28,20,17,'#7780a2',3)}poly([[-13,-16],[-18,-3],[-5,1],[-3,-14]],'#9e9076');poly([[13,-16],[18,-3],[5,1],[3,-14]],'#9e9076');circle(0,-24,12,'#d1b186');poly([[-14,-27],[0,-45],[14,-27],[8,-21],[-8,-21]],'#374e5e');path(-7,-28,7,-28,'#efce94',2);if(S.upgrade===2){poly([[-22,8],[-14,-10],[-8,13]],'#576579');poly([[22,8],[14,-10],[8,13]],'#576579')}
// blade at right and shard in off hand
ctx.save();ctx.translate(18,-3);if(attackTime>0)ctx.rotate(-.8+attackTime*4);else ctx.rotate(.35+Math.sin(frame*2)*.05);let bladeColor=S.upgrade===2?'#ffea91':S.upgrade===1?'#ff904e':'#cfdae1';if(S.upgrade)glow(2,-26,S.upgrade===2?33:23,S.upgrade===2?'#ffc8529c':'#f9743a9c');path(0,4,0,-37,bladeColor,S.upgrade===2?9:6);poly([[-4,-33],[0,-51],[4,-33]],bladeColor);path(-10,0,10,0,'#e4b765',4);ctx.restore();if(S.shard){glow(-17,-5,23,'#74ddf98c');poly([[-18,-20],[-9,-7],[-17,5],[-26,-7]],'#9deafb');circle(-17,-7,3,'#f1ffff')}if(S.equipped.band){circle(17,1,5,'#dce8ef');glow(17,1,10,'#d9eeff99')}if(S.equipped.lantern){circle(-12,-17,5,'#9ce9ff')}ctx.restore();if(attackTime>0){let a=Math.atan2(S.dirY||0,S.dirX||1);ctx.strokeStyle=S.upgrade===2?'#ffdd80b9':S.upgrade===1?'#f48a50ac':'#d9e3e2aa';ctx.lineWidth=S.upgrade===2?11:7;ctx.beginPath();ctx.arc(x,y,S.upgrade===2?75:52,a-.8,a+.8);ctx.stroke()}}
function draw(){ctx.save();if(screenShake>0)ctx.translate((Math.random()-.5)*screenShake,(Math.random()-.5)*screenShake);background();P.filter(o=>o.type==='plate'||o.type==='wall').forEach(drawObject);P.filter(o=>o.type!=='plate'&&o.type!=='wall').forEach(drawObject);enemies.forEach(drawEnemy);drawPlayer();bolts.forEach(b=>{glow(b.x,b.y,19,'#86dfffb0');circle(b.x,b.y,5,'#d5ffff')});particles.forEach(p=>{ctx.globalAlpha=Math.min(1,p.life/(p.max||1)*1.5);circle(p.x,p.y,p.r,p.col)});ctx.globalAlpha=1;textFx.forEach(t=>{ctx.globalAlpha=Math.min(1,t.life);ctx.font='bold 18px DM Sans';ctx.fillStyle=t.col;ctx.textAlign='center';ctx.fillText(t.txt,t.x,t.y)});ctx.globalAlpha=1;if(flash>0){ctx.fillStyle=`rgba(195,40,38,${flash*.38})`;ctx.fillRect(0,0,W,H)}if(shimmer>0){let g=ctx.createRadialGradient(480,270,145,480,270,570);g.addColorStop(0,'#ffdf8700');g.addColorStop(1,`rgba(245,191,97,${shimmer*.19})`);ctx.fillStyle=g;ctx.fillRect(0,0,W,H)}ctx.restore()}
function loop(t){let dt=Math.min(.035,(t-last)/1000||.016);last=t;update(dt);draw();requestAnimationFrame(loop)}S.dirX=1;S.dirY=0;spawn();refresh();requestAnimationFrame(loop);
})();</script></body></html>
<!-- agent-meta {"model":"openai/gpt-6-sol","provider":"openrouter","persona":"full","sessionId":"67f50a87-5ec7-473e-8571-5bd71f4b66c4","tokensIn":313084,"tokensOut":20366,"tokensTotal":333450,"cost":0.32870920000000003,"turns":13,"toolCalls":12,"failedToolCalls":0,"timestamp":"2026-09-22T21:44:58.289Z"} -->