Warden
GPT 6.1 Sol · full

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 43,398 bytes · SHA-256 dcf67ddd415c
<!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>
:root{--gold:#e0b66c;--dim:#8b9295;--ink:#0c141c;--line:#354047}*{box-sizing:border-box}body{margin:0;background:#080e14;color:#ddd7c8;font-family:Georgia,serif;overflow:hidden}button{font:inherit;color:inherit;cursor:pointer}button:hover{filter:brightness(1.25)}.top{height:86px;display:flex;align-items:center;justify-content:space-between;padding:0 32px;border-bottom:1px solid #394047;background:#0e1820}.brand{font-size:21px;letter-spacing:2px}.brand small{display:block;font:10px Arial;letter-spacing:4px;color:var(--gold);margin-top:9px}.stats{display:flex;align-items:center;gap:30px}.hearts{color:#ebad79;font-size:26px;letter-spacing:3px}.currency{color:var(--gold);font:18px Georgia}.label{font:9px Arial;letter-spacing:2px;color:var(--dim);display:block;margin-bottom:6px}.top button,.btn{background:#182630;border:1px solid #4b5150;padding:11px 17px;font:11px Arial;letter-spacing:2px}.layout{height:calc(100vh - 86px);display:grid;grid-template-columns:1fr 285px}.stage{position:relative;min-width:0;background:#0d1923}canvas{width:100%;height:100%;display:block}.zonehead{pointer-events:none;position:absolute;top:25px;left:30px}.eyebrow{font:10px Arial;letter-spacing:3px;color:var(--gold)}h1{font-weight:normal;font-size:28px;margin:8px 0}.zonehead p{font:12px Arial;color:#a4adb1;margin:0}.side{padding:27px 24px;background:#101b24;border-left:1px solid #354047;overflow:auto}h3{font-size:11px;font-family:Arial;letter-spacing:2px;color:#e2be80;font-weight:normal;margin:0 0 18px}.objective{font-size:19px;line-height:1.5;margin-bottom:12px}.hint{font:12px/1.8 Arial;color:#9ba8ae}.divider{height:1px;background:#34414a;margin:25px 0}.route{display:grid;gap:16px}.route div{font:12px Arial;color:#6c7a83;display:flex;gap:12px;align-items:center}.route i{font-style:normal;border:1px solid #3b4951;border-radius:50%;width:23px;height:23px;display:grid;place-items:center;font-size:10px}.route .current{color:#e5c18b}.route .current i{border-color:var(--gold);box-shadow:0 0 14px #dba04922}.route .done{color:#a3b4b0}.keys{display:grid;grid-template-columns:1fr 1fr;gap:14px;font:11px Arial;color:#adb5b7}.keys b{display:inline-block;color:#e5c38e;border:1px solid #48505a;padding:5px 7px;margin-right:6px;font-weight:normal}.bottom{position:absolute;bottom:22px;left:30px;right:30px;display:flex;justify-content:space-between;align-items:end;pointer-events:none}.weapon{font:12px Arial;color:#e9c18a;letter-spacing:2px}.weapon small{display:block;color:#85949e;letter-spacing:1px;margin-top:8px}.prompt{background:#0a141ce8;border:1px solid #a88a54;padding:12px 18px;font:12px Arial;color:#e9c68c;display:none}.toast{position:absolute;left:50%;top:110px;transform:translateX(-50%);background:#14222ded;border:1px solid #9d8354;padding:14px 24px;font:13px Arial;opacity:0;transition:opacity .3s;max-width:85%;text-align:center;pointer-events:none}.shimmer{position:absolute;inset:0;box-shadow:inset 0 0 95px #e8b554;opacity:0;pointer-events:none;transition:opacity .3s}.overlay{position:fixed;inset:0;z-index:5;background:#061019bb;backdrop-filter:blur(9px);display:flex;align-items:center;justify-content:center}.panel{background:linear-gradient(140deg,#1a2a35,#0d1822);border:1px solid #706044;max-width:720px;width:90%;padding:38px;box-shadow:0 20px 100px #0009}.panel h2{font-weight:normal;color:#efcf91;font-size:34px;margin:14px 0 18px}.panel p{color:#abb7bd;font:14px/1.9 Arial}.panel .btn{margin:12px 10px 0 0;background:#ae8745;color:#09121a;border-color:#d2b171;padding:15px 20px}.items{display:grid;gap:10px}.item{display:flex;align-items:center;gap:16px;border:1px solid #3f4c52;padding:14px;background:#14222b}.item .icon{font-size:26px;width:35px;color:#a8d3d0}.item .desc{flex:1;font:12px/1.6 Arial;color:#9eacb4}.item strong{display:block;color:#e6c58f;font:16px Georgia;margin-bottom:3px}.item button{background:#23333c;border:1px solid #637168;padding:9px 12px;font:11px Arial}.hidden{display:none!important}.introMark{font-size:11px;letter-spacing:4px;color:#d6b574}.fine{font:10px Arial!important;letter-spacing:1px;color:#7d8c98!important}.sound{color:#a9bcb7!important} @media(max-width:850px){.layout{grid-template-columns:1fr 220px}.side{padding:20px 16px}.brand{font-size:15px}.top{padding:0 18px}.stats{gap:12px}.hearts{font-size:20px}}@media(max-width:620px){.layout{grid-template-columns:1fr}.side{display:none}.top{height:70px}.layout{height:calc(100vh - 70px)}.brand{font-size:12px;letter-spacing:1px}.brand small{font-size:8px;letter-spacing:2px}.top button{padding:9px;font-size:9px}.hearts{font-size:17px;letter-spacing:0}.currency{font-size:14px}.zonehead{top:18px;left:18px}h1{font-size:22px}.bottom{left:18px;right:18px;bottom:85px}.panel{padding:25px}.panel h2{font-size:27px}.touch{display:flex!important}.keys{gap:8px}}.touch{display:none;position:absolute;bottom:10px;left:10px;right:10px;justify-content:space-between}.touch button{background:#203540bb;border:1px solid #7d8068;border-radius:4px;padding:13px;color:#f1cd90;touch-action:none}.touch div{display:flex;gap:5px}
</style></head><body>
<header class="top"><div class="brand">THE WARDEN’S CORROSION<small>AURELION’S SUNKEN OBSERVATORY</small></div><div class="stats"><div><span class="label">VITALITY</span><span id="hearts" class="hearts">♥♥♥</span></div><div><span class="label">GOLDEN SIGILS</span><span id="gold" class="currency">◈ 0</span></div><button id="invBtn">SATCHEL · I</button><button id="soundBtn" class="sound" title="Toggle sound">♪ ON</button></div></header>
<main class="layout"><section class="stage"><canvas id="game"></canvas><div class="zonehead"><div class="eyebrow" id="zoneNum">01 / THE OUTER REACHES</div><h1 id="zoneTitle">The Ashen Threshold</h1><p id="zoneSubtitle">Above the storm, a last light remains.</p></div><div class="bottom"><div class="weapon" id="weapon">EMBER BLADE<small>PHYSICAL · WARDEN ISSUE</small></div><div id="prompt" class="prompt"></div></div><div id="toast" class="toast"></div><div id="shimmer" class="shimmer"></div><div class="touch"><div><button data-key="a">◀</button><button data-key="w">▲</button><button data-key="s">▼</button><button data-key="d">▶</button></div><div><button data-key="j">⚔</button><button data-key="k">✧</button><button data-key="e">E</button></div></div></section><aside class="side"><h3>THE WARDEN’S CHARGE</h3><div id="objective" class="objective"></div><div id="hint" class="hint"></div><div class="divider"></div><h3>PATH TO THE BEACON</h3><div class="route" id="route"></div><div class="divider"></div><h3>FIELD CONTROLS</h3><div class="keys"><span><b>WASD</b> Move</span><span><b>J</b> Strike</span><span><b>E</b> Interact</span><span><b>K</b> Shard</span><span><b>I</b> Satchel</span><span><b>H</b> Heal</span><span><b>Space</b> Dash</span><span><b>B</b> Seal</span></div><div class="divider"></div><p class="hint">Three Sanctuary Stones hold your memory. Rest to mend; fall to return.<br><br>Hairline fractures conceal what the Corrosion could not consume.</p></aside></main>
<div id="modal" class="overlay"><div id="panel" class="panel"></div></div>
<script>
'use strict';
const $=s=>document.getElementById(s),canvas=$('game'),ctx=canvas.getContext('2d');
const names=['The Ashen Threshold','The Rusted Archives','The Collapsed Gallery','The Sunken Vault','The Twin Spire','The Core Chamber'];
const subs=['Above the storm, a last light remains.','The records rust. The debt remains.','Under broken heavens, embers remember.','Halfway into the dark, a crystal waits.','Two spires. One final ascent.','Let the light find its way home.'];
let W=1000,H=640,scale=1,keys={},running=false,paused=true,zone=0,time=0,last=0,soundOn=true,audio,toastTimer,saveData=null,won=false;
let p={x:110,y:340,hp:6,max:6,gold:0,upgrade:0,shard:false,ammo:0,face:1,fy:0,inv:0,attack:0,dash:0,cd:0,items:{stem:1,seal:0,band:false,lantern:false,cloak:false},equipped:{band:false,lantern:false,cloak:false}};
let worlds=[],enemies=[],objects=[],particles=[],bolts=[],floats=[],shopStock={stem:3,seal:2,heart:1},flags={galleryHeart:false,fifth:false,light:false,vaultDone:false,beaconA:false,beaconB:false},plateTimes=[-99,-99];
const rand=(a,b)=>a+Math.random()*(b-a),dist=(a,b)=>Math.hypot(a.x-b.x,a.y-b.y);
function sound(kind){if(!soundOn)return;try{audio ||=new (window.AudioContext||window.webkitAudioContext)();audio.resume();const types={step:[80,.06,'triangle'],hit:[120,.12,'sawtooth'],swing:[240,.1,'triangle'],fire:[180,.16,'sawtooth'],solar:[65,.35,'triangle'],shot:[850,.18,'sine'],hurt:[135,.23,'square'],coin:[1250,.09,'sine'],item:[520,.32,'sine'],rare:[330,.65,'sine'],save:[220,.8,'sine'],shop:[110,.35,'sine'],break:[75,.25,'sawtooth'],dash:[320,.12,'triangle']};let [f,d,t]=types[kind]||types.item;for(let i=0;i<(kind==='rare'||kind==='save'?3:1);i++){let o=audio.createOscillator(),g=audio.createGain();o.type=t;o.frequency.setValueAtTime(f*(1+i*.5),audio.currentTime);o.frequency.exponentialRampToValueAtTime(f*(kind==='shot'? .45:1.8),audio.currentTime+d);g.gain.setValueAtTime(.045,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+d);o.connect(g).connect(audio.destination);o.start();o.stop(audio.currentTime+d)}}catch(e){}}
function notify(text){$('toast').textContent=text;$('toast').style.opacity=1;clearTimeout(toastTimer);toastTimer=setTimeout(()=>$('toast').style.opacity=0,3500)}
function burst(x,y,c,n=18){for(let i=0;i<n;i++)particles.push({x,y,vx:rand(-90,90),vy:rand(-110,60),life:rand(.3,1),c})}
function glow(){ $('shimmer').style.opacity=.65;setTimeout(()=>$('shimmer').style.opacity=0,1300)}
function obj(type,x,y,extra={}){return {type,x,y,...extra}}
function foe(x,y,hp=3,type='guard'){return {x,y,hp,max:hp,type,cd:1,freeze:0,hit:0,dead:false}}
function makeWorld(){worlds=[
{enemies:[foe(485,315),foe(680,390)],objects:[obj('stone',145,285,{id:1}),obj('cache',350,425,{gold:20}),obj('gate',910,340)]},
{enemies:[foe(350,270,4),foe(490,405,4),foe(645,260,4)],objects:[obj('stone',505,325,{id:2}),obj('shop',670,405),obj('chest',265,440,{item:'band'}),obj('cache',740,235,{gold:35,item:'stem'}),obj('gate',910,340),obj('back',85,340)]},
{enemies:[foe(430,290,5),foe(720,405,8,'heavy')],objects:[obj('plate',290,380,{id:0}),obj('plate',425,380,{id:1}),obj('altar',600,275,{level:1}),obj('wall',735,225,{hp:4,reward:'heart4'}),obj('cache',600,450,{gold:45}),obj('gate',910,340),obj('back',85,340)]},
{enemies:[foe(335,310,6),foe(655,390,6)],objects:[obj('wall',370,220,{hp:5,reward:'shard'}),obj('mirror',535,260,{turn:0}),obj('switch',780,200,{id:'light'}),obj('cache',760,445,{gold:55,item:'stem'}),obj('gate',910,340),obj('back',85,340)]},
{enemies:[foe(365,290,8),foe(500,420,8),foe(700,350,12,'heavy')],objects:[obj('stone',145,285,{id:3}),obj('altar',440,220,{level:2}),obj('switch',810,190,{id:'heart5'}),obj('chest',700,460,{item:'cloak'}),obj('cache',310,455,{gold:120,item:'seal'}),obj('trap',545,315),obj('trap',615,400),obj('gate',910,340),obj('back',85,340)]},
{enemies:[foe(690,335,40,'boss'),foe(440,250,7),foe(445,435,7)],objects:[obj('switch',750,195,{id:'beaconA'}),obj('switch',750,470,{id:'beaconB'}),obj('plate',300,340,{id:2}),obj('beacon',860,335),obj('back',85,340)]}
]}
function loadZone(z,back=false){zone=z;if(z===1&&worlds[z].enemies.every(e=>e.dead)){worlds[z].enemies=[foe(350,270,4),foe(490,405,4),foe(645,260,4)]}enemies=worlds[z].enemies;objects=worlds[z].objects;p.x=back?835:115;p.y=340;bolts=[];plateTimes=[-99,-99];updateUI();notify(names[z]);}
function alive(){return enemies.filter(e=>!e.dead)}
function gateReady(){if(alive().length)return false;if(zone===2)return p.upgrade>=1&&worlds[2].bridge;if(zone===3)return p.shard&&flags.light;if(zone===4)return p.upgrade===2;return true}
function objective(){if(zone===0)return ['Wake the threshold','Rest at the glowing stone. Defeat the two sentinels, then enter the eastern arch.'];if(zone===1)return [alive().length?'Clear the archive':'Search what remains','A silver band lies in the western chest. The merchant and Sanctuary awaken after combat.'];if(zone===2)return [!worlds[2].bridge?'Rebuild the passage':p.upgrade<1?'Ignite the ancient edge':'Uncover the fractured wall','Cross both bronze plates within 4 seconds to lower the bridge. Strike the cracked northern wall for a heart.'];if(zone===3)return [!p.shard?'A light beneath the stone':!flags.light?'Align the forgotten light':'The way to the spires','Break the hairline wall to the northwest. Turn the mirror twice, then fire at the distant blue crystal.'];if(zone===4)return [p.upgrade<2?'Claim the Solar Falchion':alive().length?'Survive the final ascent':'Follow the golden light','Shoot the high eastern crystal for the fifth heart. Search the southern chest and broken jar.'];return [alive().length?'Silence the Core Warden':'Reignite the Core Beacon','Stand on the bronze plate to lower the Warden’s shield. Light both distant crystals, then approach the Beacon.']}
function updateUI(){let hearts='';for(let i=0;i<p.max/2;i++)hearts+=p.hp>=i*2+2?'♥':p.hp===i*2+1?'◐':'♡';$('hearts').textContent=hearts;$('gold').textContent='◈ '+p.gold;$('zoneNum').textContent=String(zone+1).padStart(2,'0')+' / '+(zone===3?'THE HALFWAY DESCENT':'AURELION’S OBSERVATORY');$('zoneTitle').textContent=names[zone];$('zoneSubtitle').textContent=subs[zone];let o=objective();$('objective').textContent=o[0];$('hint').textContent=o[1];$('route').innerHTML=names.map((n,i)=>`<div class="${i===zone?'current':i<zone?'done':''}"><i>${i<zone?'✓':i+1}</i>${n.replace('The ','')}</div>`).join('');$('weapon').innerHTML=['EMBER BLADE<small>PHYSICAL · WARDEN ISSUE</small>','IGNITED EDGE<small>EMBER · QUICKENED STRIKE</small>','SOLAR FALCHION<small>LIGHT · HEAVY ARC</small>'][p.upgrade]+(p.shard?`<small>✧ AETHER SHARD · ${p.ammo} CHARGES</small>`:'')}
function showPanel(html){paused=true;$('panel').innerHTML=html;$('modal').classList.remove('hidden')}
function closePanel(){paused=false;$('modal').classList.add('hidden');keys={}}
function start(fresh=true){if(fresh){p={x:110,y:340,hp:6,max:6,gold:0,upgrade:0,shard:false,ammo:0,face:1,fy:0,inv:0,attack:0,dash:0,cd:0,items:{stem:1,seal:0,band:false,lantern:false,cloak:false},equipped:{band:false,lantern:false,cloak:false}};flags={galleryHeart:false,fifth:false,light:false,vaultDone:false,beaconA:false,beaconB:false};shopStock={stem:3,seal:2,heart:1};makeWorld();loadZone(0);won=false}else{restoreSave()}running=true;closePanel();sound('rare');updateUI()}
function writeSave(){p.hp=p.max;saveData=JSON.parse(JSON.stringify({p,zone,worlds,flags,shopStock}));try{localStorage.setItem('wardenCorrosionSave',JSON.stringify(saveData))}catch(e){}sound('save');burst(p.x,p.y,'#edd28b',35);notify('Sanctuary awakened · health restored · memory secured');glow();updateUI()}
function restoreSave(){let s=saveData;try{s ||= JSON.parse(localStorage.getItem('wardenCorrosionSave'))}catch(e){}if(!s){start(true);return}p=JSON.parse(JSON.stringify(s.p));worlds=JSON.parse(JSON.stringify(s.worlds));flags={...s.flags};shopStock={...s.shopStock};loadZone(s.zone);p.hp=p.max;saveData=s;}
function hurt(n){if(p.inv>0||won)return;p.hp=Math.max(0,p.hp-n);p.inv=1.2;sound('hurt');burst(p.x,p.y,'#de5550',25);floats.push({x:p.x,y:p.y-40,t:1,text:'−'+n,c:'#f59d91'});updateUI();if(p.hp<=0){showPanel('<div class="eyebrow">THE CORROSION TAKES HOLD</div><h2>Your light is not lost.</h2><p>The Sanctuary remembers. Return to your last rested memory; unsaved discoveries fall into the storm.</p><button class="btn" onclick="restoreSave();closePanel()">RETURN TO SANCTUARY</button>')}}
function addHeart(which){if(which===4&&flags.galleryHeart||which===5&&flags.fifth)return;if(which===4)flags.galleryHeart=true;else flags.fifth=true;p.max+=2;p.hp=p.max;glow();burst(p.x,p.y,'#ffdb84',45);sound('rare');notify('A heart rekindled · +2 maximum HP');updateUI()}
function gain(item,gold=0){p.gold+=gold;if(gold){sound('coin');burst(p.x,p.y,'#e6c371');floats.push({x:p.x,y:p.y-30,t:1.4,text:'+'+gold+' sigils',c:'#e4c27f'})}if(item){if(item==='stem'||item==='seal')p.items[item]++;else p.items[item]=true;sound('rare');burst(p.x,p.y,'#d5eee8',25);notify({band:'Warden’s Band found · equip it in your satchel',stem:'Vital Stem found · H restores 4 HP',seal:'Binding Seal found · B binds the nearest enemy',lantern:'Phantom Lantern earned · hidden light revealed',cloak:'Shadow Cloak found · equip to soften traps'}[item])}updateUI()}
function useItem(item){if(!p.items[item]){notify('No '+(item==='stem'?'Vital Stems':'Binding Seals')+' remaining');return}if(item==='stem'){if(p.hp===p.max){notify('Your hearts are already full');return}p.items.stem--;p.hp=Math.min(p.max,p.hp+4);burst(p.x,p.y,'#89d8a0',25);sound('item');notify('Vital Stem · four vitality restored')}else{let e=alive().sort((a,b)=>dist(a,p)-dist(b,p))[0];if(!e||dist(e,p)>350){notify('No construct close enough to bind');return}p.items.seal--;e.freeze=3;burst(e.x,e.y,'#8bcce4');sound('shot');notify('Construct bound for three seconds')}updateUI()}
function inventory(){showPanel('<div class="eyebrow">THE WARDEN’S SATCHEL</div><h2>Relics & remedies</h2><p>Accessories must be equipped to take effect. Each relic has its own place on your cloak.</p><div class="items">'+[['band','◉','Warden’s Band','Silver wrist ring · 15% faster strikes'],['lantern','♧','Phantom Lantern','Blue shoulder lantern · reveals hidden light'],['cloak','◩','Shadow Cloak','Dark cloak trim · traps deal 1 instead of 2 HP'],['stem','❧','Vital Stem','Restore 4 HP · '+p.items.stem+' carried'],['seal','✥','Binding Seal','Freeze one nearby construct for 3 seconds · '+p.items.seal+' carried']].map(([id,icon,n,d])=>`<div class="item"><span class="icon">${icon}</span><div class="desc"><strong>${n}</strong>${d}</div><button onclick="inventoryAction('${id}')" ${!p.items[id]?'disabled':''}>${id==='stem'||id==='seal'?'USE':p.equipped[id]?'UNEQUIP':p.items[id]?'EQUIP':'UNDISCOVERED'}</button></div>`).join('')+'</div><button class="btn" onclick="closePanel()">RETURN · I / ESC</button>')}
function inventoryAction(id){if(id==='stem'||id==='seal')useItem(id);else{p.equipped[id]=!p.equipped[id];sound('item')}inventory()}
function shop(){sound('shop');showPanel('<div class="eyebrow">MERCHANT OF THE RUSTED ARCHIVES</div><h2>Trade in small hopes.</h2><p>Golden Sigils: '+p.gold+' · Supplies do not replenish.</p><div class="items">'+[['stem','Vital Stem',50,shopStock.stem+' left · restores 4 HP'],['seal','Binding Seal',75,shopStock.seal+' left · bind a construct'],['heart','Heart of the Spire',300,flags.fifth?'Already claimed':'One only · fifth heart container'],...(flags.vaultDone?[['ammo','Aether charge',10,'Refill one crystal bolt']]:[])].map(([id,n,c,d])=>`<div class="item"><span class="icon">${id==='heart'?'♡':'✧'}</span><div class="desc"><strong>${n}</strong>${d}</div><button onclick="buy('${id}',${c})">${c} ◈</button></div>`).join('')+'</div><button class="btn" onclick="closePanel()">LEAVE ALTAR</button>')}
function buy(id,c){if(id==='heart'&&(flags.fifth||!flags.galleryHeart)){notify(flags.fifth?'The fifth heart is already yours':'Find the Gallery heart first');return}if(id!=='ammo'&&shopStock[id]<=0){notify('This offering is exhausted');return}if(p.gold<c){notify('Not enough Golden Sigils');return}p.gold-=c;if(id==='ammo')p.ammo++;else{shopStock[id]--;if(id==='heart')addHeart(5);else gain(id)}sound('coin');sound('item');updateUI();shop()}
function nearObject(){return objects.filter(o=>!o.gone&&o.type!=='trap'&&o.type!=='plate'&&o.type!=='switch'&&dist(p,o)<74).sort((a,b)=>dist(p,a)-dist(p,b))[0]}
function interact(){let o=nearObject();if(!o)return;if(o.type==='stone'){if(zone===1&&alive().length){notify('The archive stone is veiled until the constructs fall');return}writeSave()}else if(o.type==='shop'){if(alive().length){notify('The merchant sleeps until the archive is safe');return}shop()}else if(o.type==='cache'||o.type==='chest'){o.gone=true;gain(o.item,o.gold||25)}else if(o.type==='altar'){if(o.level===1&&!worlds[2].bridge){notify('The altar awaits the two bronze plates');return}if(o.level===2&&p.upgrade!==1){notify('The ember altar must awaken first');return}if(p.upgrade>=o.level){notify('This altar has given all its light');return}p.upgrade=o.level;o.used=true;glow();burst(o.x,o.y,o.level===1?'#f49748':'#ffe194',40);sound(o.level===1?'fire':'solar');notify(o.level===1?'Ignited Edge · faster ember strikes':'Solar Falchion · a wider, heavier solar arc');updateUI()}else if(o.type==='gate'){if(!gateReady()){notify(alive().length?'The constructs hold this passage shut':zone===2?'Awaken the altar and lower the bridge':zone===3?'Find the Shard and complete the light path':'Claim the Solar Falchion first');return}if(zone===3){flags.vaultDone=true;p.ammo=Math.max(p.ammo,18)}loadZone(zone+1)}else if(o.type==='back')loadZone(zone-1,true);else if(o.type==='mirror'){o.turn=(o.turn+1)%3;if(p.shard&&p.ammo===0){p.ammo=3;updateUI();notify('The mirror gathers three stray Aether charges')}sound('hit');notify(['Mirror faces the dark','Mirror turns toward the broken telescope','Light aligned · fire at the eastern crystal'][o.turn])}else if(o.type==='wall')notify('Fractured bronze · strike with J');else if(o.type==='beacon'){if(alive().length||!flags.beaconA||!flags.beaconB){notify('Silence the Core Warden and light both crystals');return}won=true;sound('save');glow();showPanel('<div class="eyebrow">THE CORE BEACON · RESTORED</div><h2>Even ruin remembers the sun.</h2><p>Light pours through Aurelion’s broken lenses. Far below, the storm begins to loosen its grip. The gears turn once more—not as they were, but as they may yet become.</p><p>The Last Warden remains. And for the first time, the watch is not a lonely one.</p><button class="btn" onclick="closePanel()">STAY IN THE LIGHT</button><button class="btn" onclick="start(true)">BEGIN ANOTHER WATCH</button>')}}
function attack(){if(p.cd>0)return;let dmg=[2,3,6][p.upgrade],range=[78,88,120][p.upgrade];p.cd=[.43,.29,.62][p.upgrade]*(p.equipped.band?.85:1);p.attack=.24;sound(['swing','fire','solar'][p.upgrade]);if(p.upgrade===2){$('shimmer').style.opacity=.18;setTimeout(()=>$('shimmer').style.opacity=0,160)}for(let e of alive()){let d=dist(p,e),dot=((e.x-p.x)*p.face+(e.y-p.y)*p.fy)/(d||1);if(d<range&&dot>-.25){damageEnemy(e,dmg)}}for(let o of objects){if(o.type==='wall'&&!o.gone&&dist(p,o)<range+15){o.hp-=p.upgrade===2?3:1;burst(o.x,o.y,'#b18e60',12);sound('break');if(o.hp<=0){o.gone=true;if(o.reward==='heart4')addHeart(4);else{p.shard=true;p.ammo=22;glow();sound('rare');notify('Aether Shard discovered · K fires toward your cursor or facing');updateUI()}}}}}
function damageEnemy(e,d){if(e.type==='boss'&&!worlds[5].shieldDown){burst(e.x,e.y,'#89c9e8');sound('shot');notify('Shielded · stand on the western bronze plate');return}e.hp-=d;e.hit=.2;e.x+=p.face*9;e.y+=p.fy*9;burst(e.x,e.y,p.upgrade===2?'#ffe195':p.upgrade===1?'#fa9958':'#e4a565',15);sound('hit');floats.push({x:e.x,y:e.y-25,t:.7,text:String(d),c:'#f8d492'});if(e.hp<=0){e.dead=true;gain(null,Math.floor(rand(5,16)));if(e.type==='boss'){gain(null,60);notify('The Core Warden falls. The Beacon waits.')}updateUI()}}
let mouse=null;
function shoot(){if(!p.shard){notify('The Aether Shard lies in the Sunken Vault');return}if(p.cd>0)return;if(p.ammo<=0){notify('No charges · the archive merchant can restock');return}p.ammo--;p.cd=.35;let dx=mouse?mouse.x-p.x:p.face,dy=mouse?mouse.y-p.y:p.fy,len=Math.hypot(dx,dy)||1;bolts.push({x:p.x,y:p.y-10,vx:dx/len*600,vy:dy/len*600,t:1.65});sound('shot');updateUI()}
function switchHit(o){if(o.on)return;if(o.id==='light'){let mirror=objects.find(q=>q.type==='mirror');if(mirror.turn!==2){notify('The mirror must turn twice to align the light');return}flags.light=true;gain('lantern',35)}else if(o.id==='heart5'){if(!flags.galleryHeart){notify('Seek the first hidden heart in the Gallery');return}if(!flags.fifth)addHeart(5);gain(null,25)}else{flags[o.id]=true;gain(null,15);notify('A Beacon conduit rekindles')}o.on=true;sound('rare');burst(o.x,o.y,'#a7dfed',30);updateUI()}
function update(dt){time+=dt;p.inv=Math.max(0,p.inv-dt);p.cd=Math.max(0,p.cd-dt);p.attack=Math.max(0,p.attack-dt);p.dash=Math.max(0,p.dash-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){let len=Math.hypot(dx,dy);p.face=dx/len;p.fy=dy/len;let speed=p.dash>0?440:155;p.x=Math.max(72,Math.min(928,p.x+dx/len*speed*dt));p.y=Math.max(180,Math.min(500,p.y+dy/len*speed*dt));if(Math.floor(time*3.3)!==Math.floor((time-dt)*3.3))sound('step');if(p.upgrade&&Math.random()<dt*15)particles.push({x:p.x-8,y:p.y+8,vx:rand(-10,10),vy:-20,life:.4,c:p.upgrade===1?'#e79249':'#e8cd88'})}if(keys.j)attack();if(keys.k)shoot();
for(let o of objects){if(o.gone)continue;if(o.type==='plate'&&dist(p,o)<35){o.pressed=true;if(zone===2){plateTimes[o.id]=time;if(Math.abs(plateTimes[0]-plateTimes[1])<4&&plateTimes.every(t=>time-t<4)&&!worlds[2].bridge){worlds[2].bridge=true;gain(null,30);sound('save');notify('The paired plates answer · the bridge lowers');updateUI()}}if(zone===5)worlds[5].shieldDown=4}else if(o.type==='plate')o.pressed=false;if(o.type==='trap'&&dist(p,o)<29&&Math.sin(time*2)>.2)hurt(p.equipped.cloak?1:2)}if(zone===5)worlds[5].shieldDown=Math.max(0,(worlds[5].shieldDown||0)-dt);
for(let e of alive()){e.freeze=Math.max(0,e.freeze-dt);e.hit=Math.max(0,e.hit-dt);e.cd-=dt;if(e.freeze>0)continue;let d=dist(p,e);if(d<430&&d>34){let speed=e.type==='boss'?48:e.type==='heavy'?42:62;e.x+=(p.x-e.x)/d*speed*dt;e.y+=(p.y-e.y)/d*speed*dt}if(d<(e.type==='boss'?62:43)&&e.cd<=0){hurt(e.type==='heavy'||e.type==='boss'?2:1);e.cd=1.4}if(e.type==='boss'&&e.cd<=0&&d>=62){bolts.push({x:e.x,y:e.y,vx:(p.x-e.x)/d*180,vy:(p.y-e.y)/d*180,t:3,evil:true});e.cd=2.8}}
for(let b of bolts){b.x+=b.vx*dt;b.y+=b.vy*dt;b.t-=dt;if(b.evil){if(dist(b,p)<24){hurt(2);b.t=0}}else{for(let e of alive())if(dist(b,e)<27){damageEnemy(e,4);b.t=0;break}if(b.t>0)for(let o of objects){if(!o.gone&&dist(b,o)<27){if(o.type==='switch'){switchHit(o);b.t=0;break}if(o.type==='wall'){o.hp--;burst(o.x,o.y,'#97c5d7');b.t=0;break}}}}}bolts=bolts.filter(b=>b.t>0);for(let q of particles){q.x+=q.vx*dt;q.y+=q.vy*dt;q.life-=dt}particles=particles.filter(q=>q.life>0);for(let f of floats){f.y-=dt*27;f.t-=dt}floats=floats.filter(f=>f.t>0);let o=nearObject();$('prompt').style.display=o?'block':'none';if(o)$('prompt').textContent=(o.type==='wall'?'J · BREAK FRACTURED WALL':'E · '+({stone:'REST & SAVE',shop:'TRADE SIGILS',chest:'OPEN CHEST',cache:'SEARCH CACHE',altar:o.used?'ALTAR RESTORED':'AWAKEN BLADE',gate:gateReady()?'ENTER NEXT ZONE':'PASSAGE SEALED',back:'RETURN',mirror:'ROTATE MIRROR',beacon:'REIGNITE BEACON'}[o.type]||'INTERACT'));
}
</script>
<script>
function line(x1,y1,x2,y2,c,w=1){ctx.strokeStyle=c;ctx.lineWidth=w;ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke()}
function ellipse(x,y,rx,ry,c){ctx.fillStyle=c;ctx.beginPath();ctx.ellipse(x,y,rx,ry,0,0,Math.PI*2);ctx.fill()}
function poly(points,c,stroke){ctx.beginPath();points.forEach(([x,y],i)=>i?ctx.lineTo(x,y):ctx.moveTo(x,y));ctx.closePath();ctx.fillStyle=c;ctx.fill();if(stroke){ctx.strokeStyle=stroke;ctx.stroke()}}
function gear(x,y,r,rot=0){ctx.save();ctx.translate(x,y);ctx.rotate(rot);ctx.strokeStyle='#876a4249';ctx.lineWidth=5;ctx.beginPath();ctx.arc(0,0,r,0,7);ctx.stroke();ctx.lineWidth=2;ctx.beginPath();ctx.arc(0,0,r*.72,0,7);ctx.stroke();for(let i=0;i<16;i++){let a=i/16*Math.PI*2;line(Math.cos(a)*r*.86,Math.sin(a)*r*.86,Math.cos(a)*r*1.12,Math.sin(a)*r*1.12,'#876a4255',9)}for(let i=0;i<6;i++){let a=i*Math.PI/3;line(0,0,Math.cos(a)*r*.7,Math.sin(a)*r*.7,'#79603d55',4)}ellipse(0,0,r*.12,r*.12,'#97764466');ctx.restore()}
function halo(x,y,r,color){let g=ctx.createRadialGradient(x,y,0,x,y,r);g.addColorStop(0,color);g.addColorStop(1,'transparent');ctx.fillStyle=g;ctx.fillRect(x-r,y-r,r*2,r*2)}
function text(t,x,y,c='#b1b3a6',size=10){ctx.font=size+'px Arial';ctx.textAlign='center';ctx.fillStyle=c;ctx.fillText(t,x,y)}
function arch(x,y,width,height,open){ctx.save();ctx.translate(x,y);let w=width/2;ctx.fillStyle='#080f17';ctx.beginPath();ctx.moveTo(-w,0);ctx.lineTo(-w,-height+width/2);ctx.arc(0,-height+width/2,w,Math.PI,0);ctx.lineTo(w,0);ctx.closePath();ctx.fill();ctx.strokeStyle='#715e42';ctx.lineWidth=8;ctx.stroke();ctx.strokeStyle='#d0ad6344';ctx.lineWidth=1;ctx.stroke();if(open){halo(0,-height/2,90,'#edb76830');for(let i=0;i<5;i++)line(-w+12+i*15,0,-w+12+i*15,-height+25,'#bca0641a',1)}else{for(let i=-w+12;i<w;i+=15)line(i,0,i,-height+35,'#665b46',3);line(-w,-height/2,w,-height/2,'#675b44',4)}poly([[-w-20,0],[w+20,0],[w+30,13],[-w-30,13]],'#52616b','#8c7a55');ctx.restore()}
function floor(){let bg=ctx.createLinearGradient(0,0,0,640);bg.addColorStop(0,'#101d28');bg.addColorStop(.5,zone===3?'#203944':'#1b2c37');bg.addColorStop(1,'#0b1723');ctx.fillStyle=bg;ctx.fillRect(0,0,1000,640);
// storm valley and bronze superstructure
for(let i=0;i<6;i++){poly([[i*190-100,550],[i*190+80,420+Math.sin(i*3)*60],[i*190+280,620]],'#142432');}
for(let i=0;i<5;i++){let x=(i*263+time*7)%1250-150;ellipse(x,550+i*13,145,15,'#a4bcc008')}
gear(90,140,125,time*.016);gear(830,130,115,-time*.018);gear(905,500,100,time*.025);gear(225,520,90,-time*.02);
poly([[55,175],[945,175],[977,520],[24,520]],'#0a121bcc','#5f665d');poly([[64,188],[936,188],[950,505],[50,505]],zone===3?'#263c41':'#26343d','#7b7256');
// floor tessellation
ctx.save();ctx.beginPath();ctx.rect(60,188,880,317);ctx.clip();for(let y=195;y<510;y+=44){for(let x=45;x<970;x+=88){let off=(Math.floor(y/44)%2)*44;ctx.fillStyle=['#29363e','#2c3941','#25333c'][Math.abs(Math.floor(x*13+y*7))%3];ctx.fillRect(x+off+2,y+2,84,40);line(x+off+4,y+4,x+off+82,y+4,'#8e93751b');if((x+y)%7===0){line(x+off+14,y+11,x+off+28,y+29,'#090f1677');line(x+off+28,y+29,x+off+49,y+33,'#090f1677')}}}ctx.restore();
// embedded astronomical chart
ctx.strokeStyle='#c9a76a20';ctx.lineWidth=1;for(let r of [90,135,150]){ctx.beginPath();ctx.ellipse(505,343,r,r*.62,0,0,7);ctx.stroke()}for(let a=0;a<Math.PI*2;a+=Math.PI/6)line(505+Math.cos(a)*135,343+Math.sin(a)*83,505+Math.cos(a)*150,343+Math.sin(a)*93,'#c9a76a33',2);
for(let x of [65,210,800,935]){poly([[x-17,190],[x+17,190],[x+10,95],[x-10,95]],'#33404a','#6c644e');ctx.fillStyle='#756544';ctx.fillRect(x-22,180,44,10);ctx.fillRect(x-17,97,34,8);line(x-6,110,x-6,174,'#b3a47833',3);ellipse(x-10,186,18,4,'#54695455')}
// broken telescope
ctx.save();ctx.translate(600,135);ctx.rotate(-.25);poly([[-65,-13],[42,-20],[42,20],[-65,13]],'#39464d','#8d7850');ellipse(44,0,8,24,'#aa8a52');ellipse(46,0,4,18,'#0b1924');ctx.restore();line(605,142,580,179,'#74694c',5);line(605,142,635,179,'#74694c',5);
// rail and edge
for(let x=75;x<940;x+=70){line(x,511,x,535,'#776c4e',3);ellipse(x,510,3,3,'#a08a5c')}line(65,525,937,525,'#8b795255',3);line(50,508,950,508,'#b0a37855',3);
// moody shafts of light
poly([[320,0],[370,0],[530,480],[460,480]],'#e3bd7010');poly([[720,0],[735,0],[835,450],[790,450]],'#93c0d00b');
for(let i=0;i<36;i++){let x=(i*97+Math.sin(time*.25+i)*22)%1000,y=180+(i*51+time*(zone===3?-6:3))%320;ellipse(x,y,zone===3?9:1.2,zone===3?3:1.2,zone===3?'#b6d5d10b':'#e9c07836')}
if(zone===2){poly([[450,183],[523,189],[475,242],[512,289],[468,319],[430,280]],'#111c26','#56626a');if(worlds[2].bridge){ctx.fillStyle='#625844';ctx.fillRect(450,217,72,32);for(let x=454;x<520;x+=10)line(x,218,x,248,'#9d8956',2)}}
if(zone===3){for(let i=0;i<7;i++)ellipse(205+i*96,480+Math.sin(i)*9,45,8,'#4e8f9b19')}
}
function drawObject(o){if(o.gone)return;let {x,y,type}=o;ctx.save();ctx.translate(x,y);if(type==='gate'||type==='back'){arch(0,20,66,107,type==='back'||gateReady());text(type==='back'?'RETURN':gateReady()?'ASCEND':'SEALED',0,45,'#bba16a',9)}else if(type==='stone'){halo(0,-15,83,'#e8c66c20');ellipse(0,14,31,12,'#0a151c99');poly([[-24,9],[24,9],[20,-5],[-20,-5]],'#726a52','#c7b676');poly([[-15,-6],[15,-6],[10,-42],[-10,-42]],'#73888a','#e5d395');ellipse(0,-45,12,5,'#eddaa0');halo(0,-43,32,'#ffdb8b50');for(let i=0;i<6;i++){let a=time*.7+i;ellipse(Math.sin(a)*24,-38+Math.cos(a)*17,1.5,1.5,'#f1d78b')}text('SANCTUARY '+o.id,0,40,'#bda981',9)}else if(type==='altar'){halo(0,-20,65,o.level===1?'#fca44319':'#ffe08323');ellipse(0,15,37,14,'#141e26');poly([[-30,8],[30,8],[24,-10],[-24,-10]],'#635c4a','#ae9362');poly([[-20,-9],[20,-9],[14,-35],[-14,-35]],'#38444a','#8e8259');line(0,-24,0,-75,o.used?'#687166':o.level===1?'#ffad60':'#f8dd8a',4);line(-9,-28,9,-28,'#c6a86c',3);if(!o.used)halo(0,-57,33,o.level===1?'#f69f4f45':'#f8e29545');text(o.level===1?'EMBER ALTAR':'SOLAR ALTAR',0,39,'#c2a274',9)}else if(type==='wall'){poly([[-38,14],[37,14],[35,-49],[-30,-56]],'#3f4847','#71684d');poly([[-34,10],[-28,-48],[0,-53],[8,12]],'#46504a');line(-5,-51,2,-33,'#131f26',2);line(2,-33,-9,-15,'#131f26',2);line(-9,-15,7,10,'#131f26',2);line(2,-33,16,-30,'#172126');if(p.equipped.lantern)line(-5,-51,2,-33,'#9cd8e2',2);for(let i=0;i<4;i++)ellipse(-20+i*15,13,7,3,'#55765a66');text('FRACTURED',0,35,p.equipped.lantern?'#a7dae0':'#7f877d',8)}else if(type==='chest'||type==='cache'){ellipse(0,9,23,8,'#10202a');if(type==='chest'){poly([[-21,4],[21,4],[21,-20],[-21,-20]],'#655c47','#b49557');poly([[-21,-20],[21,-20],[14,-30],[-14,-30]],'#827050','#c3a66d');line(-10,-27,-10,4,'#baa36b',3);line(10,-27,10,4,'#baa36b',3);ctx.fillStyle='#e8c672';ctx.fillRect(-3,-10,6,7);halo(0,-15,33,'#ecd48e16')}else{ellipse(0,-4,15,13,'#6c6753');poly([[-13,-6],[4,-18],[15,-3],[9,8],[-8,8]],'#6f7766','#a69a79');line(-5,-16,3,-4,'#25313c',2)}text(type==='chest'?'RELIQUARY':'BROKEN CACHE',0,30,'#8d968e',8)}else if(type==='shop'){halo(0,-17,80,'#a6d3c820');poly([[-31,11],[31,11],[22,-7],[-22,-7]],'#726748','#b7a276');poly([[-15,0],[15,0],[12,-46],[0,-64],[-12,-46]],'#273b44','#a49a6f');ellipse(0,-38,8,8,'#b6d2c2');text('MERCHANT ALTAR',0,40,'#bfba9b',9)}else if(type==='plate'){ellipse(0,0,32,17,o.pressed?'#b19a5c':'#514e3c');ctx.strokeStyle='#b39a5c';ctx.lineWidth=2;ctx.beginPath();ctx.ellipse(0,0,28,13,0,0,7);ctx.stroke();line(-12,0,12,0,'#bfa569');line(0,-8,0,8,'#bfa569');if(o.pressed)halo(0,0,44,'#ffdb7d25');text('PLATE',0,34,'#939988',8)}else if(type==='switch'){let c=o.on?'#f6d78e':'#8bd6e7';line(0,3,0,-37,'#667b83',3);poly([[-17,6],[17,6],[10,-1],[-10,-1]],'#586873','#96865a');halo(0,-46,37,o.on?'#ffdc7533':'#82ddff25');poly([[0,-66],[12,-46],[0,-27],[-12,-46]],c,'#d1e8e5');text(o.on?'KINDLED':'AETHER SWITCH',0,30,c,8)}else if(type==='mirror'){poly([[-22,9],[22,9],[14,-3],[-14,-3]],'#726449');line(0,0,0,-45,'#93815f',4);ctx.save();ctx.translate(0,-46);ctx.rotate((o.turn-1)*.45);ctx.fillStyle='#82bac5';ctx.fillRect(-21,-17,42,34);ctx.strokeStyle='#b9a16d';ctx.lineWidth=4;ctx.strokeRect(-21,-17,42,34);line(-16,-10,16,12,'#c7e4e4',2);ctx.restore();text('TURN MIRROR',0,32,'#a0b9b8',9);if(o.turn===2){ctx.setLineDash([4,6]);line(0,-46,245,-106,p.equipped.lantern?'#a5e9ec99':'#a5e9ec33');ctx.setLineDash([])}}else if(type==='trap'){ellipse(0,0,29,14,'#13212b');for(let i=0;i<5;i++)poly([[-23+i*10,4],[-19+i*10,-(Math.sin(time*2)>.2?18:3)],[-15+i*10,4]],'#898e80','#b4aa8a')}else if(type==='beacon'){halo(0,-65,120,won?'#ffe59470':'#deb46413');poly([[-36,16],[36,16],[26,-2],[-26,-2]],'#776345','#c6a064');for(let i of [-1,1]){poly([[i*18,-1],[i*30,-1],[i*28,-98],[i*17,-114]],'#586168','#bb9f60')}ellipse(0,-68,19,35,won?'#ffdfa1':'#27343b');if(won)halo(0,-68,65,'#ffda94aa');text('CORE BEACON',0,48,'#d7b778',9)}ctx.restore()}
function drawEnemy(e){if(e.dead)return;let {x,y}=e,big=e.type==='boss'?1.7:e.type==='heavy'?1.25:1;ctx.save();ctx.translate(x,y);ctx.scale(big,big);ellipse(0,11,24,9,'#0006');let c=e.hit>0?'#e3af72':e.freeze>0?'#79b4ce':'#647077';if(e.freeze>0)halo(0,-10,45,'#8bcde52a');poly([[-14,3],[-18,-25],[-11,-38],[12,-36],[19,-20],[14,5]],c,'#252f37');poly([[-13,-22],[13,-22],[8,-4],[-7,-4]],'#29323a','#9f8254');poly([[-10,-37],[10,-37],[12,-49],[0,-55],[-12,-49]],c,'#9a8155');line(-7,-43,6,-43,e.freeze?'#94e7ff':'#fb985c',3);line(-10,0,-13,13,'#7b8180',7);line(11,0,15,13,'#7b8180',7);line(19,-21,27,4,'#9c9271',5);line(27,4,31,-31,'#b4aba0',4);if(e.type==='boss'){halo(0,-22,48,worlds[5].shieldDown?'#f9cf5820':'#88cbe822');ctx.strokeStyle=worlds[5].shieldDown?'#e6c66b44':'#97cdeb88';ctx.lineWidth=2;ctx.beginPath();ctx.ellipse(0,-21,32,44,0,0,7);ctx.stroke();poly([[-9,-55],[-15,-65],[-2,-58],[4,-72],[11,-56]],'#ae8c4f')}ctx.fillStyle='#111b24';ctx.fillRect(-22,-65,44,3);ctx.fillStyle=e.freeze?'#86cde6':'#c79764';ctx.fillRect(-22,-65,44*Math.max(0,e.hp/e.max),3);ctx.restore();if(e.type==='boss')text('THE CORE WARDEN',x,y-123,'#cbb48a',11)}
function drawPlayer(){let x=p.x,y=p.y,bob=(keys.w||keys.a||keys.s||keys.d)?Math.sin(time*12)*2:Math.sin(time*2)*.6;ctx.save();ctx.translate(x,y+bob);if(p.inv>0&&Math.floor(time*13)%2===0)ctx.globalAlpha=.5;ellipse(0,14,p.upgrade===2?22:16,7,'#0008');if(p.inv>0)halo(0,-10,44,'#e950472c');if(p.upgrade===2)halo(0,-12,43,'#e6c67316');let stance=p.upgrade===2?12:7;line(-6,2,-stance,15,'#8c928c',5);line(6,2,stance,15,'#8c928c',5);poly([[-9,-26],[9,-26],[14+Math.sin(time*3)*2,8],[-16,8]],'#b2b8a3','#d0c4a0');poly([[-9,-25],[-15,-9],[-11,8],[-3,0],[3,-25]],'#5e7478');if(p.equipped.cloak){poly([[-16,7],[14,7],[10,12],[-18,12]],'#17232e','#6a8390')}if(p.upgrade===2){line(-15,7,13,7,'#d8bd7477',2)}ellipse(0,-34,9,10,'#dad1b4');poly([[-10,-35],[-8,-44],[0,-48],[9,-42],[10,-33],[5,-35]],'#6a8080','#a3ad93');line(-5,-33,5,-33,'#142631',3);let sx=p.face>=0?1:-1;line(sx*8,-22,sx*16,-10,'#aab7a5',5);let bladeColor=['#d4d6be','#ffad60','#ffe49c'][p.upgrade];if(p.upgrade)halo(sx*23,-16,32,p.upgrade===1?'#f7973b38':'#ffe57d44');line(sx*16,-9,sx*31,-38,bladeColor,p.upgrade===2?5:3);line(sx*11,-13,sx*21,-8,'#b49a61',3);if(p.equipped.band)ellipse(sx*15,-11,4,3,'#cfebdb');if(p.shard){line(-sx*9,-20,-sx*20,-12,'#aab7a5',4);halo(-sx*21,-17,20,'#83cde544');poly([[-sx*21,-26],[-sx*15,-18],[-sx*21,-10],[-sx*27,-18]],'#9ce0ed')}if(p.equipped.lantern){ellipse(-7,-22,4,5,'#a1dbeb');halo(-7,-22,40,'#8bd5ec22')}if(p.attack>0){ctx.save();let a=Math.atan2(p.fy,p.face);ctx.rotate(a);ctx.strokeStyle=p.upgrade===2?'#ffe497bb':p.upgrade===1?'#ffa357bb':'#ccd9cd99';ctx.lineWidth=p.upgrade===2?8:4;ctx.beginPath();ctx.arc(0,-10,[60,70,100][p.upgrade],-1.15,1.15);ctx.stroke();ctx.strokeStyle='#fff4c744';ctx.lineWidth=1;ctx.beginPath();ctx.arc(0,-10,[67,78,110][p.upgrade],-1.1,1.1);ctx.stroke();ctx.restore()}ctx.restore();text('LAST WARDEN',x,y+37,'#cfcdad',8)}
function draw(){floor();let all=[...objects.map(o=>({y:o.y,fn:()=>drawObject(o)})),...enemies.map(e=>({y:e.y,fn:()=>drawEnemy(e)})),{y:p.y,fn:drawPlayer}].sort((a,b)=>a.y-b.y);all.forEach(o=>o.fn());for(let b of bolts){halo(b.x,b.y,20,b.evil?'#e3783c44':'#8bdcf74f');line(b.x-b.vx*.022,b.y-b.vy*.022,b.x,b.y,b.evil?'#fa9664':'#bdeffd',3);ellipse(b.x,b.y,4,4,b.evil?'#f9a675':'#e6ffff')}for(let q of particles){ctx.globalAlpha=Math.min(1,q.life*2);ellipse(q.x,q.y,2,2,q.c)}ctx.globalAlpha=1;for(let f of floats){ctx.globalAlpha=Math.min(1,f.t*2);text(f.text,f.x,f.y,f.c,14)}ctx.globalAlpha=1;if(won){halo(860,270,450,'#ffd47b33');for(let i=0;i<18;i++)line(850,260,850+Math.cos(i*.7)*700,260+Math.sin(i*.7)*700,'#ffdb9415',3)}let g=ctx.createRadialGradient(500,340,210,500,340,580);g.addColorStop(0,'transparent');g.addColorStop(1,'#020a1366');ctx.fillStyle=g;ctx.fillRect(0,0,1000,640)}
function resize(){let r=canvas.getBoundingClientRect(),d=devicePixelRatio||1;canvas.width=r.width*d;canvas.height=r.height*d;W=1000;H=640;scale=Math.min(r.width/W,r.height/H);ctx.setTransform(d,0,0,d,0,0)}
function frame(t){let dt=Math.min(.033,(t-last)/1000||0);last=t;if(!paused&&running)update(dt);else time+=dt*.2;let d=devicePixelRatio||1;ctx.setTransform(d,0,0,d,0,0);ctx.clearRect(0,0,canvas.width/d,canvas.height/d); // draw in CSS pixels with a device-scaled transform
ctx.setTransform(d*scale,0,0,d*scale,(canvas.width-W*scale*d)/2,(canvas.height-H*scale*d)/2);
// draw() uses the same world fit, restored through a temporary wrapper
renderWorld();requestAnimationFrame(frame)}
function renderWorld(){draw();}
window.addEventListener('resize',resize);window.addEventListener('keydown',e=>{let k=e.key.length===1?e.key.toLowerCase():e.key;if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' ','Tab'].includes(k))e.preventDefault();if(e.repeat){keys[k]=true;return}if(k==='i'){if(!running)return;if(paused)closePanel();else inventory();return}if(k==='Escape'&&running){if(paused)closePanel();else showPanel('<div class="eyebrow">THE WATCH IS STILL</div><h2>A moment in the quiet.</h2><p>Your progress is secured only when you rest at a Sanctuary Stone.</p><button class="btn" onclick="closePanel()">RESUME</button><button class="btn" onclick="start(true)">NEW WATCH</button>');return}if(paused)return;keys[k]=true;if(k==='e')interact();if(k==='h')useItem('stem');if(k==='b')useItem('seal');if(k===' '&&p.dash<=0&&p.cd<=0){p.dash=.2;p.inv=.3;p.cd=.45;sound('dash');burst(p.x,p.y,'#92b3ba',8)}});window.addEventListener('keyup',e=>{keys[e.key.length===1?e.key.toLowerCase():e.key]=false});window.addEventListener('blur',()=>keys={});canvas.addEventListener('mousemove',e=>{let r=canvas.getBoundingClientRect();mouse={x:(e.clientX-r.left-(r.width-W*scale)/2)/scale,y:(e.clientY-r.top-(r.height-H*scale)/2)/scale}});canvas.addEventListener('mouseleave',()=>mouse=null);canvas.addEventListener('mousedown',e=>{if(paused)return;e.button===2?shoot():attack()});canvas.addEventListener('contextmenu',e=>e.preventDefault());$('invBtn').onclick=()=>running&&(paused?closePanel():inventory());$('soundBtn').onclick=()=>{soundOn=!soundOn;$('soundBtn').textContent=soundOn?'♪ ON':'♪ OFF';if(soundOn)sound('item')};document.querySelectorAll('[data-key]').forEach(b=>{b.addEventListener('pointerdown',e=>{e.preventDefault();b.setPointerCapture(e.pointerId);let k=b.dataset.key;keys[k]=true;if(k==='e')interact();if(k==='j')attack();if(k==='k')shoot()});b.addEventListener('pointerup',()=>keys[b.dataset.key]=false);b.addEventListener('pointercancel',()=>keys[b.dataset.key]=false)});
makeWorld();enemies=worlds[0].enemies;objects=worlds[0].objects;updateUI();resize();let hasSave=false;try{hasSave=!!localStorage.getItem('wardenCorrosionSave')}catch(e){}showPanel('<div class="introMark">AN ACTION ADVENTURE IN SIX DESCENTS</div><h2>The Warden’s Corrosion</h2><p>Aurelion hangs above a valley that has forgotten the sun. Its bronze bones are failing. Its guardians no longer know your name.</p><p>You are the last of the watch. Carry the Ember Blade through the ruined observatory, reclaim its ancient light, and reignite the Core Beacon before the Corrosion takes all.</p><button class="btn" onclick="start(true)">BEGIN THE WATCH →</button>'+(hasSave?'<button class="btn" onclick="start(false)">CONTINUE MEMORY</button>':'')+'<p class="fine">WASD TO MOVE · J TO STRIKE · E TO INTERACT<br>HEADPHONES RECOMMENDED · PROGRESS SAVES AT SANCTUARY STONES</p>');requestAnimationFrame(frame);
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-6.1-sol","provider":"openrouter","persona":"full","sessionId":"34b05084-a9c6-43e8-b1c7-c33b42e0bde7","tokensIn":242343,"tokensOut":17379,"tokensTotal":259722,"cost":0.2611854,"turns":11,"toolCalls":10,"failedToolCalls":0,"timestamp":"2026-09-29T20:03:01.425Z"} -->