← Warden results

Warden

GPT 6 Luna · 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 44,855 bytes · SHA-256 23af16817ce7
<!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>
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=DM+Sans:wght@400;500;600;700&display=swap');
:root{--gold:#e9bd70;--cream:#f3e8d0;--muted:#a89b87;--ink:#0b1017;--panel:#131a21;--line:#39414a}*{box-sizing:border-box}body{margin:0;background:radial-gradient(ellipse at 50% 0%,#202a32,#0a0e14 70%);color:var(--cream);font-family:'DM Sans',sans-serif;min-height:100vh}.shell{max-width:1330px;margin:auto;padding:18px 24px 22px}.top{height:64px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #ffffff17;margin-bottom:14px}.brand{display:flex;gap:13px;align-items:center}.sigil{width:38px;height:38px;border:1px solid #d4a657;border-radius:50%;display:grid;place-items:center;color:#f0cb83;font-size:20px;box-shadow:0 0 20px #d4a65724}.brand h1{font:600 17px Cinzel,serif;letter-spacing:.15em;margin:0}.brand small{display:block;color:#9c917f;font-size:9px;letter-spacing:.25em;margin-top:4px}.topright{display:flex;gap:18px;align-items:center;color:#b8ad9a;font-size:12px}.objective{border:1px solid #c69a523d;padding:8px 13px;color:#ddc28b;letter-spacing:.03em}.layout{display:grid;grid-template-columns:minmax(0,1fr) 278px;gap:15px}.gameframe{position:relative;border:1px solid #6d604d;background:#090d12;box-shadow:0 18px 60px #0009;overflow:hidden}.gameframe:before{content:'';position:absolute;inset:0;pointer-events:none;z-index:2;border:1px solid #e9bd7018}.canvaswrap{position:relative;width:100%;aspect-ratio:16/9}.canvaswrap canvas{display:block;width:100%;height:100%;image-rendering:pixelated}.hudtop{position:absolute;z-index:4;left:18px;right:18px;top:14px;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none}.hearts{display:flex;gap:5px}.heart{font-size:24px;line-height:1;color:#ed7363;text-shadow:0 2px 8px #000;filter:drop-shadow(0 0 4px #dc635c4a)}.heart.empty{color:#424249;filter:none}.zonebadge{font:500 11px Cinzel,serif;letter-spacing:.15em;color:#f2dfbb;background:#101720c9;border:1px solid #d2b17449;padding:8px 12px}.hudpills{position:absolute;z-index:4;left:18px;bottom:15px;display:flex;gap:8px;pointer-events:none}.pill{background:#111923db;border:1px solid #c0a16a47;padding:7px 10px;color:#f0d297;font-size:11px;letter-spacing:.03em}.pill b{color:#fff0cd;margin-left:4px}.hint{position:absolute;z-index:4;right:18px;bottom:15px;background:#111923db;color:#c8bba2;padding:8px 11px;font-size:10px;border:1px solid #ffffff20}.sidebar{background:linear-gradient(155deg,#182028,#11171d);border:1px solid #ffffff1c;min-height:100%;padding:17px 16px;display:flex;flex-direction:column}.sidehead{font:500 11px Cinzel,serif;letter-spacing:.17em;color:#d5b777;margin:0 0 13px}.zone-title{font:500 18px Cinzel,serif;color:#f1e5ce;line-height:1.3;margin:0 0 8px}.story{font-size:11px;line-height:1.65;color:#a89f91;margin:0 0 14px}.rule{height:1px;background:#ffffff15;margin:4px 0 14px}.objectivecard{font-size:11px;color:#d1c3aa;line-height:1.6;padding:10px;background:#0b1117;border-left:2px solid #bb9456;margin-bottom:14px}.objectivecard strong{color:#edce91;font-weight:600}.minirow{display:flex;justify-content:space-between;font-size:10px;color:#8d918e;margin:6px 0}.minirow span:last-child{color:#e5d4b2}.inventoryhead{display:flex;align-items:center;justify-content:space-between}.bag{display:grid;gap:7px;margin:3px 0 13px}.itemrow{display:flex;align-items:center;gap:9px;background:#ffffff06;border:1px solid #ffffff12;padding:8px}.itemicon{width:27px;height:27px;display:grid;place-items:center;background:#d9b66c17;border:1px solid #d9b66c38;color:#e4c47c;font-size:14px}.itemname{font-size:10px;color:#e3d6bd}.itemmeta{font-size:9px;color:#8c908e;margin-top:3px}.itemrow button{margin-left:auto;background:none;border:1px solid #89714b;color:#d8bd84;font-size:9px;padding:5px 7px;cursor:pointer}.itemrow button:hover{background:#d5a85622}.emptybag{font-size:10px;color:#737b7e;padding:10px 0}.sidebottom{margin-top:auto}.weaponbox{padding:10px;background:#0d141a;border:1px solid #ffffff15;margin-bottom:10px}.weaponbox .label{color:#888e8d;font-size:9px;letter-spacing:.15em}.weaponbox .weapon{font:500 12px Cinzel,serif;color:#e5ca90;margin-top:5px}.controls{display:flex;flex-wrap:wrap;gap:5px}.key{border:1px solid #ffffff20;color:#99988e;padding:4px 6px;font-size:9px}.toast{position:absolute;z-index:10;top:22%;left:50%;transform:translate(-50%,-8px);padding:12px 18px;background:#121a22ed;border:1px solid #d8b66f;color:#f2dcad;font:500 12px Cinzel,serif;letter-spacing:.06em;opacity:0;transition:.25s;pointer-events:none;text-align:center;max-width:80%;box-shadow:0 8px 30px #0009}.toast.show{opacity:1;transform:translate(-50%,0)}.modalback{position:fixed;inset:0;z-index:20;background:#070a0dd9;display:none;place-items:center;padding:18px}.modalback.open{display:grid}.modal{width:min(520px,100%);background:linear-gradient(145deg,#1a232b,#10161b);border:1px solid #b18d54;padding:24px;box-shadow:0 20px 80px #000}.modalhead{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #ffffff19;padding-bottom:13px;margin-bottom:15px}.modalhead h2{font:500 19px Cinzel,serif;color:#e9d5aa;margin:0}.close{background:none;border:1px solid #ffffff2b;color:#ddd;padding:5px 9px;cursor:pointer}.shoprow{display:flex;align-items:center;justify-content:space-between;padding:12px 5px;border-bottom:1px solid #ffffff13}.shoprow strong{display:block;font:500 12px Cinzel,serif;color:#e6d1a3}.shoprow small{display:block;color:#96968d;font-size:10px;margin-top:4px}.buy{border:1px solid #c19b5d;background:#bc915329;color:#e9ca8b;padding:8px 12px;cursor:pointer;font-size:10px}.buy:disabled{opacity:.38;cursor:not-allowed}.modalnote{font-size:10px;color:#8e938f;line-height:1.6;margin-top:13px}.mobilebar{display:none}
@media(max-width:900px){.layout{grid-template-columns:1fr}.sidebar{min-height:0}.sidebottom{margin-top:8px}.shell{padding:10px}.top{margin-bottom:9px}.sidebar{display:grid;grid-template-columns:1fr 1fr;gap:0 15px}.sidebar>.sidehead,.sidebar>.zone-title,.sidebar>.story,.sidebar>.rule,.sidebar>.objectivecard{grid-column:1/-1}.sidebottom{grid-column:1/-1}.bag{grid-template-columns:1fr 1fr}}@media(max-width:520px){.mobilebar{display:flex;justify-content:space-between;align-items:center;padding:9px 12px;background:#10171d;border-top:1px solid #ffffff17}.touchpad{text-align:center}.touchpad button,.touchactions button{background:#1a242b;border:1px solid #b89a624d;color:#e4d2ab;border-radius:4px;min-width:39px;height:34px;margin:2px;font-size:12px;touch-action:none}.touchactions{display:flex;flex-direction:column}.touchactions button{font-size:9px;height:30px;padding:0 8px}.top{height:55px}.brand h1{font-size:12px}.topright{gap:7px;font-size:10px}.objective{padding:6px;font-size:9px}.canvaswrap{aspect-ratio:4/3}.sidebar{padding:12px;display:block}.story{margin-bottom:8px}.bag{grid-template-columns:1fr 1fr}.itemrow{padding:5px;gap:5px}.itemicon{width:23px;height:23px}.itemrow button{padding:4px}.hint{font-size:8px;right:8px;bottom:8px}.hudpills{left:8px;bottom:8px}.pill{padding:5px 7px;font-size:9px}.zonebadge{font-size:9px;padding:6px}.hearts{gap:2px}.heart{font-size:19px}.hudtop{left:9px;right:9px;top:9px}}
</style></head><body><div class="shell"><header class="top"><div class="brand"><div class="sigil">✧</div><div><h1>THE WARDEN'S CORROSION</h1><small>AURELION'S SUNKEN OBSERVATORY</small></div></div><div class="topright"><div class="objective" id="objectiveTop">RESTORE THE CORE BEACON</div><div>◉ <span id="goldTop">0</span></div></div></header><main class="layout"><section class="gameframe"><div class="canvaswrap"><canvas id="game" width="960" height="540" aria-label="The Warden's Corrosion game"></canvas><div class="hudtop"><div class="hearts" id="hearts"></div><div class="zonebadge" id="zoneBadge">I · THE ASHEN THRESHOLD</div></div><div class="hudpills"><div class="pill">✦ SIGILS <b id="goldHud">0</b></div><div class="pill">⚔ <b id="weaponHud">Ember Blade</b></div><div class="pill" id="ammoPill" style="display:none">✧ AMMO <b id="ammoHud">0</b></div></div><div class="hint" id="gameHint">WASD move · SPACE strike · E interact</div><div class="toast" id="toast"></div></div><div class="mobilebar"><div class="touchpad"><button data-key="w">▲</button><div><button data-key="a">◀</button><button data-key="s">▼</button><button data-key="d">▶</button></div></div><div class="touchactions"><button data-action="attack">⚔ STRIKE</button><button data-action="interact">E · USE</button><button data-action="shoot">✧ BOLT</button></div></div></section><aside class="sidebar"><div class="sidehead">FIELD JOURNAL · 01 / 06</div><h2 class="zone-title" id="sideTitle">The Ashen Threshold</h2><p class="story" id="story">A dead telescope points toward the storm. Somewhere below, the Beacon's last ember waits.</p><div class="rule"></div><div class="objectivecard" id="objectiveCard"><strong>First light.</strong><br>Clear the constructs and find the Sanctuary Stone.</div><div class="minirow"><span>CONSTRUCTS</span><span id="enemyCount">—</span></div><div class="minirow"><span>RELICS FOUND</span><span id="relicCount">0 / 5</span></div><div class="rule"></div><div class="inventoryhead"><div class="sidehead" style="margin:0">SATCHEL</div><span style="font-size:9px;color:#858b89">I TO MANAGE</span></div><div class="bag" id="bag"></div><div class="sidebottom"><div class="weaponbox"><div class="label">EQUIPPED ARMAMENT</div><div class="weapon" id="weaponName">Ember Blade</div></div><div class="controls"><span class="key">WASD / ARROWS MOVE</span><span class="key">SPACE ATTACK</span><span class="key">F AETHER BOLT</span><span class="key">E INTERACT</span></div></div></aside></main></div><div class="modalback" id="modalBack"><section class="modal" id="modal"></section></div><script>
(()=>{'use strict';const cv=document.getElementById('game'),ctx=cv.getContext('2d');const W=960,H=540;const names=['The Ashen Threshold','The Rusted Archives','The Collapsed Gallery','The Sunken Vault','The Twin Spire','The Core Chamber'];const roman=['I','II','III','IV','V','VI'];const lore=['A dead telescope points toward the storm. Somewhere below, the Beacon’s last ember waits.','Dust drifts through the archive stacks. A merchant’s lamp still burns beside the old bronze scales.','The gallery has folded in on itself. Beneath the rubble, an altar remembers fire.','Halfway into the drowned observatory, something crystalline answers the dark.','Two spires lean together over the valley. The final ascent begins here.','The Core is a black sun behind the storm. The Warden’s last watch begins.'];
const state={zone:0,x:98,y:380,hp:6,maxHp:6,gold:0,weapon:0,ranged:false,items:{},equipped:{},kills:0,dead:false,won:false,inventoryOpen:false,shopOpen:false,toastTimer:0,flash:0,shake:0,edge:0,frame:0,attack:0,shotCd:0,damageCd:0,interactText:'',projectiles:[],particles:[],enemies:[],drops:[],objects:[],doorOpen:false,plate:false,altar:false,stone:false,lanternPuzzle:false,switchOn:false,bossHp:0,checkpoint:0,shopStock:{vital:3,seal:2,heart:1,ammo:99},ammo:0,saved:false};
const player={x:98,y:380,r:13,face:1,speed:190};const keys={};let last=0;const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
function toast(s){const t=document.getElementById('toast');t.textContent=s;t.classList.add('show');clearTimeout(state.toastTimer);state.toastTimer=setTimeout(()=>t.classList.remove('show'),2200)}
function sound(type){try{const A=window.AudioContext||window.webkitAudioContext;if(!A)return;const ac=sound.ac||(sound.ac=new A);if(ac.state==='suspended')ac.resume();const o=ac.createOscillator(),g=ac.createGain();o.connect(g);g.connect(ac.destination);let f=type==='hit'?105:type==='pickup'?660:type==='shot'?900:type==='damage'?145:type==='upgrade'?330:220;o.frequency.setValueAtTime(f,ac.currentTime);o.frequency.exponentialRampToValueAtTime(Math.max(45,f*.56),ac.currentTime+.16);o.type=type==='shot'?'triangle':'sine';g.gain.setValueAtTime(.055,ac.currentTime);g.gain.exponentialRampToValueAtTime(.001,ac.currentTime+.2);o.start();o.stop(ac.currentTime+.21)}catch(e){}}
function particle(x,y,color,n=8){for(let i=0;i<n;i++){const a=Math.random()*6.28,v=20+Math.random()*105;state.particles.push({x,y,vx:Math.cos(a)*v,vy:Math.sin(a)*v,life:.35+Math.random()*.5,max:.85,color,size:1+Math.random()*3})}}
const itemInfo={band:['Warden’s Band','Accessory · attack speed','◉'],vital:['Vital Stem','Consumable · restore 4 HP','✿'],lantern:['Phantom Lantern','Accessory · reveals secrets','☼'],cloak:['Shadow Cloak','Accessory · trap protection','◒'],seal:['Binding Seal','Consumable · freeze one enemy','⌘']};
function addItem(id,n=1){state.items[id]=(state.items[id]||0)+n;state.items[id]=Math.min(state.items[id],99);sound('pickup');particle(player.x,player.y,'#f7e5ad',16);toast(`${itemInfo[id][0]} acquired`);renderBag()}
function makeEnemy(x,y,type='construct',hp=2){return{x,y,hp,max:hp,type,r:type==='boss'?27:15,cd:.5,freeze:0,hit:0,phase:Math.random()*7}}
function obj(type,x,y,label,extra={}){return{type,x,y,label,...extra}}
function setupZone(z){state.zone=z;state.doorOpen=false;state.plate=false;state.altar=false;state.stone=false;state.lanternPuzzle=false;state.switchOn=false;state.projectiles=[];state.enemies=[];state.drops=[];state.objects=[];state.kills=0;state.bossHp=0;player.x=90;player.y=380;state.x=player.x;state.y=player.y;
const stone=(x,y)=>state.objects.push(obj('stone',x,y,'Sanctuary Stone'));
if(z===0){stone(95,145);state.enemies=[makeEnemy(430,300),makeEnemy(650,185)];state.objects.push(obj('exit',896,270,'Eastern arch'));}
if(z===1){state.enemies=[makeEnemy(360,180),makeEnemy(580,345),makeEnemy(730,190)];stone(480,145);state.objects.push(obj('chest',245,390,'Hidden archive chest',{item:'band'}),obj('cache',440,435,'Dust cache',{gold:35}),obj('merchant',125,145,'Merchant altar'),obj('exit',895,270,'Gallery passage'));}
if(z===2){state.enemies=[makeEnemy(370,180),makeEnemy(560,320),makeEnemy(725,180,'mini',4)];state.objects.push(obj('crack',270,270,'Hairline-cracked wall',{item:'heart4'}),obj('plate',480,420,'Bronze pressure plate'),obj('altar',710,125,'Ignition altar'),obj('exit',895,270,'Vault descent'));}
if(z===3){state.enemies=[makeEnemy(400,175),makeEnemy(685,350),makeEnemy(540,220,'instrument',3)];state.objects.push(obj('crack',260,235,'Faintly cracked masonry',{item:'shard'}),obj('reflect',468,125,'Light-reflection lens'),obj('crystalSwitch',750,150,'Distant crystal switch'),obj('cache',370,440,'Sunken coffer',{gold:50}),obj('exit',895,270,'Twin Spire lift'));}
if(z===4){state.enemies=[makeEnemy(340,180),makeEnemy(500,350),makeEnemy(680,190,'sentinel',4),makeEnemy(785,350,'sentinel',4)];state.objects.push(obj('stone',92,145,'Sanctuary Stone'),obj('altar',250,130,'Solar altar'),obj('crystalSwitch',780,130,'High crystal switch'),obj('heartSwitch',820,255,'Distant heart mechanism'),obj('treasure',350,430,'Spire treasure',{gold:100,item:'cloak'}),obj('jar',610,430,'Broken blue jar',{item:'seal'}),obj('exit',895,270,'Core threshold'));}
if(z===5){state.enemies=[makeEnemy(670,270,'boss',12)];state.objects.push(obj('crystalSwitch',350,145,'Beacon relay'),obj('beacon',810,270,'Core Beacon'));}
updateUI();}
function updateUI(){const z=state.zone;document.getElementById('zoneBadge').textContent=`${roman[z]} · ${names[z].toUpperCase()}`;document.getElementById('sideTitle').textContent=names[z];document.querySelector('.sidehead').textContent=`FIELD JOURNAL · 0${z+1} / 06`;document.getElementById('story').textContent=lore[z];document.getElementById('goldTop').textContent=state.gold;document.getElementById('goldHud').textContent=state.gold;document.getElementById('weaponHud').textContent=state.weapon===0?'Ember Blade':state.weapon===1?'Ignited Edge':'Solar Falchion';document.getElementById('weaponName').textContent=state.weapon===0?'Ember Blade':state.weapon===1?'Ignited Edge':'Solar Falchion';document.getElementById('ammoPill').style.display=state.ranged?'block':'none';document.getElementById('ammoHud').textContent=state.ammo;let h='';for(let i=0;i<state.maxHp/2;i++){const val=clamp(state.hp-i*2,0,2);h+=`<span class="heart ${val===0?'empty':''}">${val===1?'♥':'♥'}</span>`}document.getElementById('hearts').innerHTML=h;document.getElementById('enemyCount').textContent=z===5?(state.enemies.length?`WARDEN ${state.bossHp||12} / 12`:'BEACON UNSEALED'):state.enemies.length?`${state.enemies.length} REMAINING`:'AREA CLEAR';document.getElementById('relicCount').textContent=`${['band','vital','lantern','cloak','seal'].filter(k=>state.items[k]).length} / 5`;document.getElementById('objectiveCard').innerHTML=objective();renderBag()}
function objective(){let z=state.zone;if(z===0)return `<strong>${state.enemies.length?'Hold the threshold.':'The arch is clear.'}</strong><br>${state.enemies.length?'Strike the Corrupted Constructs, then activate the Sanctuary Stone.':'Activate the glowing stone and pass through the eastern arch.'}`;if(z===1)return `<strong>Search the Archives.</strong><br>Find the silver Band, speak with the merchant, and clear the way east. The stone rests among the stacks.`;if(z===2)return `<strong>Ignite the old edge.</strong><br>Crack-marked wall conceals a heart. Stand on the bronze plate, then claim the altar’s fire.`;if(z===3)return `<strong>Reach the halfway relic.</strong><br>The hairline wall hides the Aether Shard. Break it; strike the distant crystal. Solve the lens for a Phantom Lantern.`;if(z===4)return `<strong>Prepare the final ascent.</strong><br>Claim the Solar Falchion, find the Shadow Cloak and Binding Seal. Fire at the high switch for the fifth heart.`;return `<strong>Rekindle the Core Beacon.</strong><br>Strike the relay with the Aether Shard, defeat the Warden construct, then interact with the Beacon.`}
function renderBag(){const bag=document.getElementById('bag');const ids=Object.keys(itemInfo).filter(id=>state.items[id]);if(!ids.length){bag.innerHTML='<div class="emptybag">No relics carried. The halls keep their own counsel.</div>';return}bag.innerHTML=ids.map(id=>{let [name,meta,icon]=itemInfo[id],access=['band','lantern','cloak'].includes(id),on=state.equipped[id];return `<div class="itemrow"><div class="itemicon">${icon}</div><div><div class="itemname">${name}${!access&&state.items[id]>1?' ×'+state.items[id]:''}</div><div class="itemmeta">${on?'EQUIPPED':meta}</div></div><button onclick="window.itemAction('${id}')">${access?(on?'REMOVE':'EQUIP'):'USE'}</button></div>`}).join('')}
window.itemAction=(id)=>{if(['band','lantern','cloak'].includes(id)){state.equipped[id]=!state.equipped[id];toast(`${itemInfo[id][0]} ${state.equipped[id]?'equipped':'removed'}`)}else if(state.items[id]>0){if(id==='vital'){if(state.hp===state.maxHp){toast('Health already full');return}state.hp=Math.min(state.maxHp,state.hp+4);particle(player.x,player.y,'#8bd38b',20);state.items[id]--;sound('pickup');toast('Vital Stem restores 4 HP')}else if(id==='seal'){let e=nearestEnemy(250);if(!e){toast('No construct close enough to bind');return}e.freeze=3;state.items[id]--;sound('pickup');particle(e.x,e.y,'#80c8ed',13);toast('Binding Seal: construct bound for 3 seconds')}if(!state.items[id])delete state.items[id]}renderBag();updateUI()};
function nearestEnemy(r=68){return state.enemies.filter(e=>Math.hypot(e.x-player.x,e.y-player.y)<r).sort((a,b)=>Math.hypot(a.x-player.x,a.y-player.y)-Math.hypot(b.x-player.x,b.y-player.y))[0]}
function move(dt){let dx=(keys.d||keys.arrowright?1:0)-(keys.a||keys.arrowleft?1:0),dy=(keys.s||keys.arrowdown?1:0)-(keys.w||keys.arrowup?1:0);if(dx&&dy){dx*=.707;dy*=.707}const speed=player.speed;player.x=clamp(player.x+dx*speed*dt,35,925);player.y=clamp(player.y+dy*speed*dt,62,488);if(dx)player.face=dx>0?1:-1;state.x=player.x;state.y=player.y;}
function attack(){if(state.attack>0||state.dead||state.inventoryOpen)return;if(breakObject())return;state.attack=state.weapon===2?.52:(state.weapon===1?.25:.33);if(state.equipped.band)state.attack*=.86;sound(state.weapon===2?'upgrade':'hit');const reach=state.weapon===2?82:63;let hit=false;for(const e of state.enemies){const dx=e.x-player.x,dy=e.y-player.y,dist=Math.hypot(dx,dy);if(dist<reach+e.r&&((dx*player.face)>-15||Math.abs(dy)>24)){e.hp-=state.weapon===2?3:state.weapon===1?2:1;e.hit=.3;particle(e.x,e.y,state.weapon?'#ff9d45':'#d9c7a2',state.weapon===2?15:9);hit=true;if(e.hp<=0){state.enemies.splice(state.enemies.indexOf(e),1);state.kills++;const gold=5+Math.floor(Math.random()*11);state.gold+=gold;state.drops.push({x:e.x,y:e.y-8,gold,life:7});sound('pickup');toast(`Construct broken · +${gold} Golden Sigils`);if(e.type==='mini')toast('Gallery sentinel felled · the cracked wall is yours')} } }if(!hit)particle(player.x+player.face*40,player.y,'#e8c37c',4);updateUI()}
function shoot(){if(!state.ranged){toast('The Aether Shard lies deeper in the Sunken Vault');return}if(state.shotCd>0)return;if(state.ammo<=0){toast('Aether Shard empty · restock at the Archives merchant');return}state.ammo--;updateUI();state.shotCd=.55;sound('shot');state.projectiles.push({x:player.x+player.face*20,y:player.y-4,vx:player.face*460,life:1.5});particle(player.x+player.face*18,player.y,'#9cecff',5)}
function damage(e){if(state.damageCd>0)return;let reduction=state.equipped.cloak?.65:1;state.hp=Math.max(0,state.hp-2*reduction);state.hp=Math.max(0,Math.round(state.hp/2)*2);state.damageCd=1.05;state.flash=.35;sound('damage');particle(player.x,player.y,'#ec5d53',15);toast(`Corrosion strike · ${state.hp} HP remain`);if(!state.hp){state.dead=true;setTimeout(()=>openDeath(),450)}updateUI()}
function interact(){let o=state.objects.filter(o=>Math.hypot(o.x-player.x,o.y-player.y)<67).sort((a,b)=>Math.hypot(a.x-player.x,a.y-player.y)-Math.hypot(b.x-player.x,b.y-player.y))[0];if(!o){if(player.x>850){advance()}else toast('Nothing answers nearby');return}
if(o.type==='stone'){state.stone=true;state.hp=state.maxHp;state.checkpoint=state.zone;state.saved=true;sound('upgrade');particle(o.x,o.y,'#f5d68e',22);if(state.zone===0&&state.enemies.length){toast('Sanctuary kindled · checkpoint saved · clear the threshold');}else toast('Sanctuary kindled · progress saved · health restored');updateUI()}
else if(o.type==='merchant'){if(state.enemies.length){toast('The merchant waits until the archives are safe');return}openShop()}
else if(o.type==='altar'){if(state.zone===2&&!state.altar){state.weapon=1;state.altar=true;state.edge=2;sound('upgrade');particle(o.x,o.y,'#ff9a3e',24);toast('IGNITED EDGE · faster ember strikes unlocked')}else if(state.zone===4&&!state.altar){state.weapon=2;state.altar=true;state.edge=2;sound('upgrade');particle(o.x,o.y,'#f2cd67',28);toast('SOLAR FALCHION · wide, heavy light arc')}else toast('The altar is quiet')}
else if(o.type==='plate'){state.plate=true;state.doorOpen=true;particle(o.x,o.y,'#d2a961',10);toast('Bronze plate sinks · the gallery bridge settles')}
else if(o.type==='reflect'){state.lanternPuzzle=true;if(!state.items.lantern)addItem('lantern');else toast('The reflected path glimmers blue');particle(o.x,o.y,'#8bd4ff',18)}
else if(o.type==='merchant')openShop();
else if(o.type==='exit')advance();
else if(o.type==='beacon'){if(state.enemies.length){toast('The Corrupted Warden still holds the Beacon');return}if(!state.switchOn){toast('The Beacon relay is dark. Strike the crystal switch first.');return}state.won=true;sound('upgrade');particle(o.x,o.y,'#ffd978',60);openWin()}
else if(o.type==='crystalSwitch'||o.type==='heartSwitch'){toast('A distant switch needs an Aether Shard bolt');}
else if(o.type==='chest'||o.type==='treasure'||o.type==='jar'||o.type==='cache'){if(o.open){toast('Only dust remains');return}o.open=true;if(o.item==='band')addItem('band');if(o.item==='cloak')addItem('cloak');if(o.item==='seal')addItem('seal');if(o.gold){state.gold+=o.gold;sound('pickup');toast(`Coffer opened · +${o.gold} Golden Sigils`)}particle(o.x,o.y,'#ffe7a8',14);updateUI()}}
function advance(){if(player.x<830){toast('The passage lies to the east');return}if(state.zone===0&&state.enemies.length){toast('The arch is sealed while constructs remain');return}if(state.zone===1&&state.enemies.length){toast('Clear the Archives before descending');return}if(state.zone===2&&!state.altar){toast('Claim the Ignition altar before the Vault');return}if(state.zone===3&&!state.ranged){toast('The Vault’s hidden relic remains undiscovered');return}if(state.zone===4&&!state.altar){toast('The Solar altar must be claimed');return}if(state.zone===5){toast('The Core is beyond reach');return}setupZone(Math.min(5,state.zone+1));sound('upgrade');toast(`Entered ${names[state.zone]}`)}
function breakObject(){let o=state.objects.find(o=>o.type==='crack'&&!o.broken&&Math.hypot(o.x-player.x,o.y-player.y)<100);if(!o)return false;o.broken=true;particle(o.x,o.y,'#b18f6a',24);sound('hit');if(o.item==='heart4'){state.maxHp+=2;state.hp+=2;state.edge=2;toast('Hidden heart container · maximum health increased')}else if(o.item==='shard'){state.ranged=true;state.ammo=8;toast('AETHER SHARD discovered · 8 charges · press F to fire crystalline bolts')}updateUI();return true}
function shootHit(p){for(const o of state.objects){if(['crystalSwitch','heartSwitch'].includes(o.type)&&!o.on&&Math.hypot(o.x-p.x,o.y-p.y)<37){o.on=true;if(o.type==='crystalSwitch'){state.switchOn=true;state.doorOpen=true;toast('Crystal relay ignites · the sealed way opens');if(state.zone===5)toast('Beacon relay aligned · defeat the Warden')}else{state.maxHp=Math.min(10,state.maxHp+2);state.hp=Math.min(state.maxHp,state.hp+2);state.edge=2;toast('Distant heart mechanism · fifth container secured')}sound('upgrade');particle(o.x,o.y,'#91e9ff',20);updateUI();return true}}return false}
function update(dt){state.frame+=dt;move(dt);state.attack=Math.max(0,state.attack-dt);state.shotCd=Math.max(0,state.shotCd-dt);state.damageCd=Math.max(0,state.damageCd-dt);state.flash=Math.max(0,state.flash-dt);state.edge=Math.max(0,state.edge-dt);for(const e of state.enemies){e.hit=Math.max(0,e.hit-dt);e.freeze=Math.max(0,e.freeze-dt);e.phase+=dt;if(e.freeze)continue;const dx=player.x-e.x,dy=player.y-e.y,d=Math.hypot(dx,dy);if(d>34){e.x+=dx/d*(e.type==='boss'?49:39)*dt;e.y+=dy/d*(e.type==='boss'?49:39)*dt}else{e.cd-=dt;if(e.cd<=0){e.cd=e.type==='boss'?.95:1.4;damage(e)}}}for(const p of state.projectiles){p.x+=p.vx*dt;p.life-=dt;if(p.x<20||p.x>940)p.life=0;if(shootHit(p))p.life=0;for(const e of state.enemies){if(Math.hypot(e.x-p.x,e.y-p.y)<e.r+7){e.hp-=2;e.hit=.25;particle(e.x,e.y,'#9cecff',9);p.life=0;if(e.hp<=0){state.enemies.splice(state.enemies.indexOf(e),1);state.gold+=10;toast('Ranged construct shattered · +10 sigils')}break}}}state.projectiles=state.projectiles.filter(p=>p.life>0);for(const d of state.drops){d.life-=dt;if(Math.hypot(d.x-player.x,d.y-player.y)<27){state.gold+=d.gold;d.life=0;sound('pickup');particle(d.x,d.y,'#efc86d',9)}}state.drops=state.drops.filter(d=>d.life>0);for(const p of state.particles){p.x+=p.vx*dt;p.y+=p.vy*dt;p.vx*=.95;p.vy*=.95;p.life-=dt}state.particles=state.particles.filter(p=>p.life>0);if(state.zone===5&&state.enemies.length)state.bossHp=state.enemies[0].hp;updateUI()}
function draw(){ctx.clearRect(0,0,W,H);const z=state.zone;drawBackground(z);drawObjects();drawDrops();drawEnemies();drawProjectiles();drawPlayer();drawParticles();drawForeground();if(state.flash){ctx.fillStyle=`rgba(235,48,52,${state.flash*.35})`;ctx.fillRect(0,0,W,H)}if(state.edge){ctx.save();ctx.strokeStyle=`rgba(255,200,100,${Math.min(.48,state.edge*.25)})`;ctx.lineWidth=22;ctx.strokeRect(8,8,W-16,H-16);ctx.restore()}if(state.dead)drawOverlay('THE WATCH FALLS','The Corrosion takes another breath','Press R to return to the last Sanctuary');if(state.won)drawOverlay('THE BEACON REMEMBERS','Aurelion rises above the storm. The watch is over.','Press R to stand once more in the threshold')}
function drawBackground(z){const palettes=[['#182129','#28302d','#66533c'],['#171f24','#29302d','#766244'],['#1b2024','#35302a','#806343'],['#111d25','#1f3339','#4f6765'],['#181d22','#342d29','#896547'],['#10171e','#242a2a','#987143']][z];let gr=ctx.createLinearGradient(0,0,0,H);gr.addColorStop(0,palettes[0]);gr.addColorStop(.72,palettes[1]);gr.addColorStop(1,palettes[2]);ctx.fillStyle=gr;ctx.fillRect(0,0,W,H); // distant architecture
ctx.save();ctx.globalAlpha=.35;for(let i=0;i<8;i++){let x=i*145-45;ctx.fillStyle=i%2?'#090f14':'#11171a';ctx.beginPath();ctx.moveTo(x,55);ctx.lineTo(x+15,55);ctx.lineTo(x+22,170);ctx.lineTo(x+108,170);ctx.lineTo(x+118,55);ctx.lineTo(x+133,55);ctx.lineTo(x+141,360);ctx.lineTo(x-8,360);ctx.closePath();ctx.fill();ctx.strokeStyle='#a48654';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(x+22,170);ctx.lineTo(x+118,170);ctx.stroke()}ctx.restore();
// patterned stone floor
for(let y=65;y<520;y+=48){ctx.strokeStyle=`rgba(209,176,122,${.055+(y/520)*.025})`;ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();for(let x=(Math.floor(y/48)%2)*55;x<W;x+=110){ctx.beginPath();ctx.moveTo(x,y);ctx.lineTo(x,y+48);ctx.stroke()}}
// environmental set pieces
for(let i=0;i<4;i++){let x=170+i*205,y=i%2?94:465;ctx.fillStyle='#080d10';ctx.fillRect(x-16,y-8,34,25);ctx.fillStyle='#566052';ctx.fillRect(x-13,y-15,27,10);ctx.fillStyle='#ad8751';ctx.fillRect(x-2,y-31,4,17);ctx.fillStyle='#f4b85c';ctx.globalAlpha=.55+Math.sin(state.frame*2+i)*.16;ctx.beginPath();ctx.arc(x,y-34,5,0,7);ctx.fill();ctx.globalAlpha=1}
// zone motifs
if(z===1){for(let i=0;i<6;i++){let x=220+i*120;ctx.fillStyle='#352e25';ctx.fillRect(x,195,38,195);ctx.fillStyle='#6c5637';ctx.fillRect(x+4,200,30,4);for(let j=0;j<5;j++){ctx.fillStyle=j%2?'#80745c':'#b0925c';ctx.fillRect(x+7,218+j*31,23,2)}}}if(z===3){ctx.fillStyle='#32505a24';for(let i=0;i<20;i++){let x=(i*87+state.frame*9)%980,y=(i*127+Math.sin(state.frame+i)*12)%530;ctx.beginPath();ctx.arc(x,y,2+(i%3),0,7);ctx.fill()}}if(z===5){ctx.save();let glow=ctx.createRadialGradient(800,270,12,800,270,230);glow.addColorStop(0,'#edae464c');glow.addColorStop(1,'#edae4600');ctx.fillStyle=glow;ctx.fillRect(550,40,410,460);ctx.restore()}
// eastern gate
ctx.fillStyle='#090e12';ctx.fillRect(902,194,58,158);ctx.strokeStyle=state.doorOpen||!state.enemies.length&&z<2?'#d2b270':'#5b5347';ctx.lineWidth=4;ctx.strokeRect(904,196,54,154);ctx.fillStyle='#090c10';ctx.fillRect(912,205,37,139);if(z<5){ctx.fillStyle='#d4b978';ctx.font='10px Cinzel';ctx.fillText('E',928,280)}
// stone grit
for(let i=0;i<70;i++){let x=(i*137+47)%W,y=(i*79+16)%H;ctx.fillStyle=`rgba(220,204,172,${.08+(i%3)*.025})`;ctx.fillRect(x,y,1.5,1.5)}}
function drawObjects(){for(const o of state.objects){const pulse=.5+Math.sin(state.frame*2.2+o.x)*.5;ctx.save();if(o.type==='stone'){ctx.shadowColor='#7ee5cf';ctx.shadowBlur=state.stone?12:24;ctx.fillStyle='#526c68';ctx.beginPath();ctx.moveTo(o.x-24,o.y+18);ctx.lineTo(o.x-20,o.y-18);ctx.lineTo(o.x,o.y-32);ctx.lineTo(o.x+22,o.y-14);ctx.lineTo(o.x+26,o.y+18);ctx.closePath();ctx.fill();ctx.fillStyle=state.stone?'#84bca2':'#9bdfc2';ctx.beginPath();ctx.arc(o.x,o.y-8,7+pulse*2,0,7);ctx.fill();ctx.strokeStyle='#c6dfb3';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(o.x,o.y-22);ctx.lineTo(o.x,o.y+8);ctx.stroke()}
else if(o.type==='altar'){ctx.fillStyle='#514638';ctx.fillRect(o.x-35,o.y+3,70,19);ctx.fillStyle='#82704e';ctx.beginPath();ctx.moveTo(o.x-27,o.y+3);ctx.lineTo(o.x-19,o.y-25);ctx.lineTo(o.x+20,o.y-25);ctx.lineTo(o.x+28,o.y+3);ctx.fill();ctx.shadowColor=state.zone===2?'#ff7e29':'#ffe07b';ctx.shadowBlur=20;ctx.strokeStyle=state.zone===2?'#f78c35':'#f2d779';ctx.lineWidth=4;ctx.beginPath();ctx.moveTo(o.x-10,o.y-26);ctx.lineTo(o.x+13,o.y-63);ctx.lineTo(o.x+20,o.y-26);ctx.stroke();ctx.fillStyle='#d8a858';ctx.beginPath();ctx.arc(o.x,o.y-32,4+pulse*2,0,7);ctx.fill()}
else if(o.type==='crack'){ctx.fillStyle=o.broken?'#181b1b':'#27251f';ctx.fillRect(o.x-35,o.y-38,70,75);ctx.strokeStyle=o.broken?'#8e7857':'#c0a47a';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(o.x-18,o.y-24);ctx.lineTo(o.x-4,o.y-13);ctx.lineTo(o.x-13,o.y+1);ctx.lineTo(o.x+8,o.y+8);ctx.lineTo(o.x+2,o.y+25);ctx.moveTo(o.x+11,o.y-19);ctx.lineTo(o.x+4,o.y-7);ctx.lineTo(o.x+21,o.y+1);ctx.stroke();if(o.broken){ctx.fillStyle='#dab75f';ctx.beginPath();ctx.arc(o.x,o.y,8+pulse*3,0,7);ctx.fill()}}
else if(o.type==='plate'){ctx.fillStyle=state.plate?'#c99d4a':'#716047';ctx.fillRect(o.x-38,o.y-19,76,38);ctx.strokeStyle='#c9a75e';ctx.lineWidth=2;ctx.strokeRect(o.x-31,o.y-12,62,24);ctx.beginPath();ctx.arc(o.x,o.y,8,0,7);ctx.stroke()}
else if(o.type==='crystalSwitch'||o.type==='heartSwitch'){ctx.fillStyle='#353e3c';ctx.fillRect(o.x-20,o.y-20,40,45);ctx.shadowColor=o.on?'#8af1ff':'#b2e8f4';ctx.shadowBlur=o.on?22:10;ctx.fillStyle=o.on?'#9ef2e9':'#6aabb5';ctx.beginPath();ctx.moveTo(o.x,o.y-24);ctx.lineTo(o.x+13,o.y-8);ctx.lineTo(o.x+8,o.y+10);ctx.lineTo(o.x-8,o.y+10);ctx.lineTo(o.x-13,o.y-8);ctx.closePath();ctx.fill();ctx.fillStyle='#d7f4e8';ctx.beginPath();ctx.arc(o.x,o.y-7,3,0,7);ctx.fill()}
else if(o.type==='reflect'){ctx.fillStyle='#4b4337';ctx.beginPath();ctx.arc(o.x,o.y,28,0,7);ctx.fill();ctx.strokeStyle='#b8a06a';ctx.lineWidth=4;ctx.beginPath();ctx.arc(o.x,o.y,22,0,7);ctx.stroke();ctx.shadowColor='#79d8ed';ctx.shadowBlur=20;ctx.fillStyle='#9be4e9';ctx.beginPath();ctx.arc(o.x,o.y,8+pulse*2,0,7);ctx.fill();ctx.strokeStyle='#c4fcf7';ctx.beginPath();ctx.moveTo(o.x-25,o.y);ctx.lineTo(o.x+25,o.y);ctx.stroke()}
else if(['chest','treasure','jar','cache','merchant'].includes(o.type)){if(o.type==='merchant'){ctx.fillStyle='#4b4030';ctx.beginPath();ctx.arc(o.x,o.y-9,18,Math.PI,0);ctx.lineTo(o.x+20,o.y+14);ctx.lineTo(o.x-20,o.y+14);ctx.fill();ctx.fillStyle='#dfb863';ctx.beginPath();ctx.arc(o.x,o.y-14,5,0,7);ctx.fill();ctx.fillStyle='#918054';ctx.fillRect(o.x-20,o.y+12,40,6)}else{ctx.fillStyle=o.open?'#423a2b':'#6c5030';ctx.fillRect(o.x-20,o.y-13,40,30);ctx.fillStyle='#ad8a4a';ctx.fillRect(o.x-20,o.y-14,40,8);ctx.fillStyle='#d4b768';ctx.fillRect(o.x-3,o.y-6,6,9);if(o.type==='jar'){ctx.fillStyle='#517b7d';ctx.fillRect(o.x-11,o.y-23,22,33);ctx.fillStyle='#c0e1cf';ctx.fillRect(o.x-8,o.y-25,16,5)}}}
else if(o.type==='beacon'){ctx.shadowColor='#ffcf68';ctx.shadowBlur=state.switchOn?34:18;ctx.fillStyle='#493e2e';ctx.beginPath();ctx.moveTo(o.x-30,o.y+35);ctx.lineTo(o.x-25,o.y-20);ctx.lineTo(o.x,o.y-48);ctx.lineTo(o.x+25,o.y-20);ctx.lineTo(o.x+30,o.y+35);ctx.fill();ctx.fillStyle=state.switchOn?'#ffe59b':'#544838';ctx.beginPath();ctx.arc(o.x,o.y-8,12+pulse*4,0,7);ctx.fill();ctx.strokeStyle='#d6b566';ctx.lineWidth=2;ctx.beginPath();ctx.arc(o.x,o.y-8,24,0,7);ctx.stroke()}
ctx.restore();if(Math.hypot(player.x-o.x,player.y-o.y)<75){ctx.fillStyle='#f5e8c8';ctx.font='10px DM Sans';ctx.textAlign='center';ctx.fillText(`E · ${o.label}`,o.x,o.y-48)}}}
function drawEnemies(){for(const e of state.enemies){ctx.save();ctx.translate(e.x,e.y);ctx.fillStyle=e.hit?'#fff1cc':e.type==='boss'?'#282126':e.type==='instrument'?'#485a5c':'#353b3a';ctx.shadowColor='#000';ctx.shadowBlur=12;ctx.beginPath();ctx.ellipse(0,5,e.r,e.r*1.1,0,0,7);ctx.fill();ctx.shadowBlur=0;ctx.strokeStyle=e.type==='boss'?'#a2794d':'#927d59';ctx.lineWidth=e.type==='boss'?4:2;ctx.stroke();ctx.fillStyle='#ad8754';ctx.beginPath();ctx.moveTo(-e.r*.65,-e.r*.45);ctx.lineTo(0,-e.r*1.6);ctx.lineTo(e.r*.65,-e.r*.45);ctx.closePath();ctx.fill();ctx.fillStyle=e.freeze?'#81d8f3':e.type==='boss'?'#ef7659':'#f0a655';ctx.shadowColor=ctx.fillStyle;ctx.shadowBlur=10;ctx.beginPath();ctx.arc(-5,-3,2.5,0,7);ctx.arc(5,-3,2.5,0,7);ctx.fill();ctx.shadowBlur=0;if(e.freeze){ctx.strokeStyle='#9de8ff';ctx.lineWidth=2;ctx.beginPath();ctx.arc(0,0,e.r+7,0,7);ctx.stroke()}ctx.restore();if(e.type==='boss'||e.hp<e.max){ctx.fillStyle='#171717';ctx.fillRect(e.x-22,e.y-e.r-19,44,4);ctx.fillStyle=e.type==='boss'?'#d96b56':'#d0a05b';ctx.fillRect(e.x-22,e.y-e.r-19,44*e.hp/e.max,4)}}}
function drawPlayer(){ctx.save();ctx.translate(player.x,player.y);if(state.flash)ctx.shadowColor='#f55';ctx.shadowBlur=state.flash?24:0;ctx.fillStyle='#111617';ctx.beginPath();ctx.ellipse(0,16,18,8,0,0,7);ctx.fill();ctx.fillStyle='#55483a';ctx.beginPath();ctx.moveTo(-13,-7);ctx.lineTo(-20,18);ctx.lineTo(18,18);ctx.lineTo(11,-8);ctx.closePath();ctx.fill();ctx.strokeStyle='#a88a57';ctx.lineWidth=1.5;ctx.stroke();ctx.fillStyle='#d3af77';ctx.beginPath();ctx.arc(0,-15,9,0,7);ctx.fill();ctx.fillStyle='#171c20';ctx.beginPath();ctx.arc(0,-19,10,Math.PI,0);ctx.lineTo(8,-15);ctx.lineTo(-9,-15);ctx.fill();ctx.fillStyle='#e2b365';ctx.fillRect(player.face*3,-16,4,2);if(state.equipped.band){ctx.fillStyle='#c6e5d4';ctx.beginPath();ctx.arc(player.face*10,2,3,0,7);ctx.fill()}if(state.equipped.cloak){ctx.strokeStyle='#222a37';ctx.lineWidth=4;ctx.beginPath();ctx.moveTo(-13,0);ctx.lineTo(-17,15);ctx.lineTo(12,16);ctx.stroke()}if(state.equipped.lantern){ctx.fillStyle='#87d8ff';ctx.shadowColor='#79d9ff';ctx.shadowBlur=13;ctx.beginPath();ctx.arc(-12,5,4,0,7);ctx.fill();ctx.shadowBlur=0}if(state.ranged){ctx.fillStyle='#93e7fc';ctx.shadowColor='#6fe4ff';ctx.shadowBlur=12;ctx.beginPath();ctx.moveTo(-10,0);ctx.lineTo(-5,-9);ctx.lineTo(1,0);ctx.lineTo(-5,9);ctx.closePath();ctx.fill();ctx.shadowBlur=0}let col=state.weapon===2?'#ffdc77':state.weapon===1?'#ff8e37':'#c9c4ac';ctx.strokeStyle=col;ctx.lineWidth=state.weapon===2?4:3;ctx.shadowColor=state.weapon===2?'#ffd45e':state.weapon===1?'#ff7928':'transparent';ctx.shadowBlur=state.weapon?13:0;let angle=state.attack>0?(-.8+state.attack*4)*player.face:0;ctx.beginPath();ctx.moveTo(player.face*7,0);ctx.lineTo(player.face*(25+Math.sin(state.frame*8)*2),-15+angle*9);ctx.stroke();if(state.weapon===2){ctx.strokeStyle='#fff0a2';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(player.face*8,-3);ctx.lineTo(player.face*28,-17);ctx.stroke()}ctx.restore()}
function drawProjectiles(){for(const p of state.projectiles){ctx.save();ctx.shadowColor='#9bedff';ctx.shadowBlur=18;ctx.fillStyle='#a9f3ff';ctx.beginPath();ctx.moveTo(p.x+10,p.y);ctx.lineTo(p.x,p.y-5);ctx.lineTo(p.x-8,p.y);ctx.lineTo(p.x,p.y+5);ctx.fill();ctx.restore()}}
function drawDrops(){for(const d of state.drops){ctx.fillStyle='#ffd76c';ctx.shadowColor='#ffc74b';ctx.shadowBlur=12;ctx.beginPath();ctx.arc(d.x,d.y,5,0,7);ctx.fill();ctx.shadowBlur=0;ctx.fillStyle='#fff1b9';ctx.font='10px sans-serif';ctx.fillText(`+${d.gold}`,d.x+8,d.y-5)}}function drawParticles(){for(const p of state.particles){ctx.globalAlpha=clamp(p.life/p.max,0,1);ctx.fillStyle=p.color;ctx.fillRect(p.x,p.y,p.size,p.size)}ctx.globalAlpha=1}
function drawForeground(){// atmospheric vignetting
let g=ctx.createRadialGradient(480,270,180,480,270,590);g.addColorStop(0,'transparent');g.addColorStop(1,'#030609a8');ctx.fillStyle=g;ctx.fillRect(0,0,W,H);if(state.zone===0&&!state.stone){ctx.fillStyle='#09100d';ctx.font='10px Cinzel';ctx.textAlign='center';ctx.fillText('THE FIRST SANCTUARY',95,108)}}
function drawOverlay(title,sub,foot){ctx.fillStyle='#080b10cc';ctx.fillRect(0,0,W,H);ctx.textAlign='center';ctx.fillStyle='#f1dcae';ctx.font='26px Cinzel';ctx.fillText(title,W/2,H/2-22);ctx.fillStyle='#c4b69a';ctx.font='14px DM Sans';ctx.fillText(sub,W/2,H/2+10);ctx.fillStyle='#8e938e';ctx.font='11px DM Sans';ctx.fillText(foot,W/2,H/2+42)}
function openShop(){state.shopOpen=true;state.inventoryOpen=true;renderShop();document.getElementById('modalBack').classList.add('open')}
function renderShop(){const list=[['vital','Vital Stem','Restore 4 HP · 3 in stock',50],['seal','Binding Seal','Freeze a nearby construct for 3 sec · 2 in stock',75],['heart','Heart Container #5','Expand maximum health by 2 HP · one only',300],['ammo','Aether charge','Available after the Vault · replenish shard',10]];document.getElementById('modal').innerHTML=`<div class="modalhead"><h2>THE MERCHANT ALTAR</h2><button class="close" onclick="window.closeModal()">ESC ✕</button></div><div style="font-size:11px;color:#a69c89;margin-bottom:7px">Golden Sigils held: <b style="color:#e7c77f">${state.gold}</b></div>${list.map(([id,n,d,c])=>{let stock=id==='vital'?state.shopStock.vital:id==='seal'?state.shopStock.seal:id==='heart'?state.shopStock.heart:state.shopStock.ammo;let locked=id==='ammo'&&!state.ranged;return `<div class="shoprow"><div><strong>${n}</strong><small>${locked?'Locked until the Aether Shard is found':d}</small></div><button class="buy" ${state.gold<c||!stock||locked?'disabled':''} onclick="window.buyItem('${id}',${c})">${c} ✦</button></div>`}).join('')}<p class="modalnote">The altar hums beneath your hand. Sigils are earned from every construct and hidden coffer. A heart container adds exactly one full heart (2 HP).</p>`}
window.buyItem=(id,c)=>{if(state.gold<c)return;state.gold-=c;if(id==='vital'){state.items.vital=(state.items.vital||0)+1;state.shopStock.vital--}if(id==='seal'){state.items.seal=(state.items.seal||0)+1;state.shopStock.seal--}if(id==='heart'){state.maxHp=Math.min(10,state.maxHp+2);state.hp=Math.min(state.maxHp,state.hp+2);state.shopStock.heart--;state.edge=2;particle(player.x,player.y,'#ffdb84',25)}if(id==='ammo'){state.shopStock.ammo--;state.ammo=Math.min(30,state.ammo+5);toast('Aether charge restocked · +5 bolts')}sound('pickup');updateUI();renderShop();toast(id==='heart'?'Heart container purchased · maximum health increased':'Purchase confirmed · item added to satchel')};
window.closeModal=()=>{document.getElementById('modalBack').classList.remove('open');state.inventoryOpen=false;state.shopOpen=false};function openInventory(){state.inventoryOpen=true;document.getElementById('modal').innerHTML=`<div class="modalhead"><h2>WARDEN'S SATCHEL</h2><button class="close" onclick="window.closeModal()">ESC ✕</button></div><p class="modalnote">Accessories change your silhouette when equipped. Consumables can be used here; Binding Seals affect the nearest construct.</p><div class="bag" style="grid-template-columns:1fr 1fr;margin-top:14px">${document.getElementById('bag').innerHTML}</div>`;document.getElementById('modalBack').classList.add('open')}
function openDeath(){document.getElementById('modalBack').classList.add('open');document.getElementById('modal').innerHTML=`<div class="modalhead"><h2>THE WATCH FALLS</h2></div><p class="modalnote">The Corrosion closes around the Warden. Your last Sanctuary remembers the way.</p><button class="buy" onclick="window.restartGame()">RETURN TO SANCTUARY · R</button>`}window.restartGame=()=>{state.dead=false;state.hp=state.maxHp;state.gold=Math.max(0,state.gold-15);setupZone(state.checkpoint);window.closeModal();toast('Returned to the last Sanctuary · 15 sigils lost')};function openWin(){document.getElementById('modalBack').classList.add('open');document.getElementById('modal').innerHTML=`<div class="modalhead"><h2>THE BEACON REMEMBERS</h2></div><p class="modalnote">Aurelion lifts above the storm. The Warden’s watch is over; warm gold pours through the broken observatory.</p><button class="buy" onclick="window.restartGame()">WALK THE RUINS AGAIN</button>`}
function keyDown(e){let k=e.key.toLowerCase();if([' ','arrowup','arrowdown','arrowleft','arrowright'].includes(k))e.preventDefault();keys[k]=true;if(k===' '&&!e.repeat)attack();if(k==='f'&&!e.repeat)shoot();if(k==='e'&&!e.repeat&&!state.inventoryOpen)interact();if(k==='i'&&!e.repeat){if(state.inventoryOpen)window.closeModal();else openInventory()}if(k==='escape'&&state.inventoryOpen)window.closeModal();if(k==='r'&&state.dead)window.restartGame();if(k==='enter'&&state.won){setupZone(0);state.won=false;window.closeModal()}}
function loop(t){let dt=Math.min(.04,(t-last)/1000||0);last=t;if(!state.inventoryOpen&&!state.dead&&!state.won)update(dt);draw();requestAnimationFrame(loop)}
window.addEventListener('keydown',keyDown);window.addEventListener('keyup',e=>keys[e.key.toLowerCase()]=false);document.querySelectorAll('.touchpad button').forEach(b=>{const k=b.dataset.key;b.addEventListener('pointerdown',e=>{e.preventDefault();keys[k]=true;b.setPointerCapture(e.pointerId)});['pointerup','pointercancel','lostpointercapture'].forEach(ev=>b.addEventListener(ev,()=>keys[k]=false))});document.querySelectorAll('.touchactions button').forEach(b=>b.addEventListener('pointerdown',e=>{e.preventDefault();const a=b.dataset.action;if(a==='attack')attack();if(a==='shoot')shoot();if(a==='interact')interact()}));cv.addEventListener('pointerdown',e=>{const r=cv.getBoundingClientRect(),x=(e.clientX-r.left)/r.width*W,y=(e.clientY-r.top)/r.height*H;if(x>W*.86)interact();else if(x>player.x+15)attack();else shoot()});setupZone(0);requestAnimationFrame(loop);
})();
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-6-luna","provider":"openrouter","persona":"full","sessionId":"a8016289-1c5e-40dc-b900-3156c1a1bf6c","tokensIn":669339,"tokensOut":22662,"tokensTotal":692001,"cost":0.022010329999999998,"turns":24,"toolCalls":23,"failedToolCalls":4,"timestamp":"2026-09-23T00:39:50.798Z"} -->