Warden
GPT 5.6 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 28,144 bytes · SHA-256 029abb0ef700
<!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>
*{box-sizing:border-box}html,body{margin:0;height:100%;overflow:hidden;background:#07101a;color:#ead9ad;font-family:Georgia,serif}body{display:grid;place-items:center}.wrap{position:relative;width:100vw;height:100vh;background:#09121d}canvas{width:100%;height:100%;display:block}.hud{position:absolute;inset:0;pointer-events:none;padding:18px}.top{display:flex;justify-content:space-between;align-items:flex-start}.brand{letter-spacing:.22em;text-transform:uppercase;font-size:12px;color:#b6a77e}.zone{font-size:22px;margin-top:5px;text-shadow:0 2px 10px #000}.right{text-align:right}.coins{color:#ffc85d;font:700 18px system-ui}.weapon{color:#d4c6a4;font:12px system-ui;margin-top:5px;letter-spacing:.08em}.hearts{display:flex;gap:7px;margin-top:14px}.heart{position:relative;width:25px;height:22px;filter:drop-shadow(0 2px 3px #000)}.heart:before,.heart:after{content:"";position:absolute;top:0;width:13px;height:20px;border-radius:12px 12px 2px 2px;background:#321921;border:1px solid #8e5b58}.heart:before{left:12px;transform:rotate(45deg);transform-origin:0 100%}.heart:after{left:0;transform:rotate(-45deg);transform-origin:100% 100%}.heart.full:before,.heart.full:after{background:#c95742;box-shadow:0 0 9px #e86442}.heart.half:before{background:#c95742}.hint{position:absolute;left:50%;bottom:20px;transform:translateX(-50%);background:#0a111ad9;border:1px solid #745e3d;border-radius:4px;padding:9px 16px;font:13px system-ui;color:#dccca5;opacity:0;transition:.2s;white-space:nowrap}.hint.show{opacity:1}.toast{position:absolute;left:50%;top:19%;transform:translateX(-50%);text-align:center;color:#ffe6a3;text-shadow:0 2px 8px #000;opacity:0;transition:.3s;letter-spacing:.08em}.toast.show{opacity:1}.toast b{display:block;font-size:20px}.toast small{font:12px system-ui;color:#c8bd9e}.controls{position:absolute;left:18px;bottom:17px;font:11px system-ui;color:#807d72;letter-spacing:.05em}.panel,.titleScreen,.ending{pointer-events:auto;position:absolute;inset:0;background:#07101bea;display:none;align-items:center;justify-content:center;backdrop-filter:blur(5px)}.titleScreen{display:flex;background:radial-gradient(circle at 50% 38%,#263044 0,#0b1420 38%,#050a10 75%)}.card{width:min(720px,90vw);border:1px solid #7c6540;background:linear-gradient(145deg,#111d29f2,#090f17f2);box-shadow:0 20px 80px #000;padding:30px}.sigil{width:72px;height:72px;border:2px solid #d69b42;border-radius:50%;margin:auto;display:grid;place-items:center;color:#ffd47b;font-size:35px;box-shadow:0 0 30px #d2872d44}.titleScreen h1,.ending h1{text-align:center;font-weight:400;letter-spacing:.16em;color:#f2d492;margin:18px 0 4px}.subtitle{text-align:center;color:#8795a5;font-style:italic}.intro{line-height:1.6;color:#baaF94;text-align:center;max-width:560px;margin:20px auto}.keys{display:flex;justify-content:center;gap:17px;flex-wrap:wrap;font:12px system-ui;color:#aba48e}.key{border:1px solid #61543c;padding:5px 9px;background:#101820;color:#e0c686}button{display:block;margin:22px auto 0;background:#9b692d;color:#fff1c8;border:1px solid #f2c870;padding:11px 28px;font:bold 12px system-ui;letter-spacing:.16em;text-transform:uppercase;cursor:pointer}button:hover{background:#bd8338;box-shadow:0 0 18px #df9b3d55}.panel .card{max-height:88vh;overflow:auto}.panel h2{font-weight:400;letter-spacing:.13em;color:#e6c27a;margin:0 0 8px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px}.item{border:1px solid #3b4650;padding:12px;background:#101923;min-height:92px;cursor:pointer}.item:hover{border-color:#ae8247}.item b{color:#e7c87e}.item small{display:block;color:#8f9ca4;font:11px system-ui;margin-top:5px;line-height:1.4}.item .count{float:right;color:#6ed0cf}.equipped{box-shadow:inset 3px 0 #e4a640;border-color:#8f723f}.close{float:right;margin:0;padding:7px 12px}.shoprow{display:flex;align-items:center;justify-content:space-between;border-top:1px solid #2b3943;padding:11px 0}.shoprow button{margin:0;padding:7px 14px}.lore{font:12px system-ui;color:#84919a;margin-bottom:18px}.ending{z-index:5}.ending .card{box-shadow:0 0 90px #e9ae4844}.objectives{position:absolute;right:18px;bottom:54px;width:250px;text-align:right;font:11px system-ui;color:#8f978d;line-height:1.5}.objectives b{color:#d6bf87;font-weight:normal;letter-spacing:.1em}.ammo{color:#80d7e8;margin-left:10px}@media(max-width:700px){.controls,.objectives{display:none}.zone{font-size:16px}.hud{padding:10px}.card{padding:20px}.keys{gap:7px}}
</style></head>
<body><div class="wrap"><canvas id="game"></canvas>
<div class="hud"><div class="top"><div><div class="brand">Aurelion's Sunken Observatory</div><div class="zone" id="zoneName"></div><div class="hearts" id="hearts"></div></div><div class="right"><div class="coins">◆ <span id="gold">0</span></div><div class="weapon" id="weapon"></div></div></div><div class="toast" id="toast"></div><div class="hint" id="hint"></div><div class="controls">WASD / ARROWS move · SPACE melee · F shard · E interact · I inventory</div><div class="objectives" id="objectives"></div></div>
<div class="titleScreen" id="title"><div class="card"><div class="sigil">☼</div><h1>THE WARDEN'S CORROSION</h1><div class="subtitle">A melancholic pilgrimage through celestial ruin</div><p class="intro">The Core Beacon is failing. Cross six broken halls, reclaim the Observatory's instruments, and cut the black-iron Corrosion from its heart.</p><div class="keys"><span><i class="key">WASD</i> move</span><span><i class="key">SPACE</i> blade</span><span><i class="key">F</i> shard</span><span><i class="key">E</i> interact</span><span><i class="key">I</i> inventory</span></div><button id="begin">Wake the Last Warden</button></div></div>
<div class="panel" id="inventory"><div class="card"><button class="close" data-close="inventory">×</button><h2>WARDEN'S SATCHEL</h2><p class="lore">Click an accessory to equip it. Only one may be worn. Click a consumable to use it.</p><div class="grid" id="invgrid"></div></div></div>
<div class="panel" id="shop"><div class="card"><button class="close" data-close="shop">×</button><h2>MERCHANT ALTAR</h2><p class="lore">The old mechanism hums beneath your hand. Offer Golden Sigils.</p><div id="shoprows"></div></div></div>
<div class="ending" id="ending"><div class="card"><div class="sigil">✦</div><h1>THE BEACON REMEMBERS</h1><p class="intro">Gold returns to the drowned lenses. Far below, the storm unknots itself. The Observatory will never be whole—but, for this night, it endures.</p><button onclick="localStorage.removeItem('wardenSave');location.reload()">Begin Another Vigil</button></div></div>
</div><script>
const C=document.getElementById('game'),X=C.getContext('2d');let W,H,D=Math.min(devicePixelRatio,2);function resize(){W=C.width=innerWidth*D;H=C.height=innerHeight*D;C.style.width=innerWidth+'px';C.style.height=innerHeight+'px'}addEventListener('resize',resize);resize();
const names=['The Ashen Threshold','The Rusted Archives','The Collapsed Gallery','The Sunken Vault','The Twin Spire','The Core Chamber'];
const desc=['Awaken the sanctuary. Defeat the threshold sentinels.','Survive the archive guard; seek the hidden chest and merchant.','Claim the altar. Break the cracked wall. Depress both plates.','Pierce the hairline wall. Reclaim the Aether Shard.','Ascend with every instrument. Endure the Guardian.','Strike both astral switches, then unmake the Corrosion.'];
let playing=false,keys={},particles=[],shots=[],last=0,shake=0,flash=0,audio=null,near=null;
const defaultState=()=>({zone:0,x:120,y:360,hp:6,maxHp:6,gold:0,weapon:0,shard:false,ammo:8,accessories:{band:false,lantern:false,cloak:false},equipped:null,cons:{stem:0,seal:0},stones:[false,false,false],done:{},shop:{stem:3,seal:2,heart:1}});let S=defaultState();
function load(){try{let q=JSON.parse(localStorage.getItem('wardenSave'));if(q)S=Object.assign(defaultState(),q)}catch(e){}}
const zones=[
{floor:'#1b2630',accent:'#b56e39',enemies:[[500,330,3],[700,430,3]],objects:[['stone',180,300],['door',930,350]]},
{floor:'#20272b',accent:'#9a663e',enemies:[[420,270,4],[610,430,4],[760,300,4]],objects:[['stone',520,190],['chest',230,460],['shop',760,470],['door',930,350]]},
{floor:'#252936',accent:'#c07642',enemies:[[500,270,5],[730,390,6]],objects:[['altar1',340,190],['wall',760,170],['plate',300,480,'p1'],['plate',650,480,'p2'],['door',930,350]]},
{floor:'#152c35',accent:'#4f9aa5',enemies:[[460,280,6],[690,420,6]],objects:[['wallShard',700,185],['switch',350,180,'s1'],['switch',850,430,'s2'],['door',930,350]]},
{floor:'#272b38',accent:'#d19543',enemies:[[380,250,7],[560,430,7],[730,260,9]],objects:[['stone',180,190],['altar2',470,160],['switchHeart',830,170],['treasure',250,470],['jar',650,470],['bonus',820,460],['door',930,350]]},
{floor:'#171b27',accent:'#e3aa4a',enemies:[[470,280,8],[650,420,8],[790,320,22]],objects:[['finalSwitch',290,170,'f1'],['finalSwitch',820,170,'f2'],['beacon',880,400]]}
];
let enemies=[],objects=[];function resetZone(){let z=zones[S.zone];enemies=z.enemies.map((e,i)=>({x:e[0],y:e[1],hp:e[2],max:e[2],id:'z'+S.zone+'e'+i,hit:0,freeze:0,boss:S.zone===5&&i===2})).filter(e=>!S.done[e.id]);objects=z.objects.map((o,i)=>({type:o[0],x:o[1],y:o[2],id:o[3]||o[0]+i,hp:o[0].startsWith('wall')?4:0,active:!!S.done['z'+S.zone+o[3]],dead:!!S.done['z'+S.zone+(o[3]||o[0]+i)]&&o[0].startsWith('wall')}));if(S.zone>0)objects.unshift({type:'back',x:70,y:350,id:'back'});S.x=Math.max(100,Math.min(900,S.x));updateUI()}
function tone(freq=300,dur=.1,type='sine',gain=.05,slide=0){if(!audio)audio=new (window.AudioContext||window.webkitAudioContext)();let o=audio.createOscillator(),g=audio.createGain();o.type=type;o.frequency.setValueAtTime(freq,audio.currentTime);o.frequency.linearRampToValueAtTime(freq+slide,audio.currentTime+dur);g.gain.setValueAtTime(gain,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+dur);o.connect(g).connect(audio.destination);o.start();o.stop(audio.currentTime+dur)}
function sound(n){if(n==='step')tone(S.zone===3?130:90,.04,'square',.018);if(n==='hit'){tone(110,.08,'square',.05);tone(S.weapon?420:280,.06,'sawtooth',.03)}if(n==='swing')tone(S.weapon===2?100:240,.14,'sawtooth',.035,S.weapon===2?-45:130);if(n==='hurt'){tone(90,.18,'square',.08);setTimeout(()=>tone(660,.1,'sine',.03,-250),30)}if(n==='coin')tone(700,.06,'square',.025,180);if(n==='rare'){tone(330,.35,'sine',.05,330);setTimeout(()=>tone(660,.4,'sine',.04,220),120)}if(n==='shot'){tone(720,.12,'triangle',.05,400);tone(180,.1,'sine',.03,120)}if(n==='save'){[220,330,440,660].forEach((f,i)=>setTimeout(()=>tone(f,.35,'sine',.04),i*90))}if(n==='buy'){tone(220,.05,'square',.04);setTimeout(()=>tone(750,.2,'sine',.04,220),100)}}
function toast(t,s=''){let e=document.getElementById('toast');e.innerHTML='<b>'+t+'</b><small>'+s+'</small>';e.classList.add('show');clearTimeout(e.t);e.t=setTimeout(()=>e.classList.remove('show'),2400)}
function burst(x,y,col,n=12,s=80){for(let i=0;i<n;i++)particles.push({x,y,vx:(Math.random()-.5)*s,vy:(Math.random()-.5)*s,life:.5+Math.random()*.5,col,r:1+Math.random()*3})}
function save(){localStorage.setItem('wardenSave',JSON.stringify(S))}
function updateUI(){document.getElementById('zoneName').textContent=(S.zone+1)+' · '+names[S.zone];document.getElementById('gold').textContent=S.gold;document.getElementById('weapon').innerHTML=['EMBER BLADE','IGNITED EDGE','SOLAR FALCHION'][S.weapon]+(S.shard?` <span class="ammo">AETHER ${S.ammo}</span>`:'');let h='';for(let i=0;i<S.maxHp/2;i++)h+=`<i class="heart ${S.hp>=i*2+2?'full':S.hp===i*2+1?'half':''}"></i>`;document.getElementById('hearts').innerHTML=h;document.getElementById('objectives').innerHTML='<b>PILGRIMAGE</b><br>'+desc[S.zone]}
function damage(n){if(S.done.invul||!playing)return;if(S.equipped==='cloak'&&Math.random()<.3)n=1;S.hp=Math.max(0,S.hp-n);flash=.35;shake=8;S.done.invul=true;setTimeout(()=>delete S.done.invul,700);burst(S.x,S.y,'#e44d3f',18);sound('hurt');toast('−'+n+' HP','The Warden reels');updateUI();if(S.hp<=0)setTimeout(respawn,600)}
function respawn(){let stone=S.stones[2]?4:S.stones[1]?1:0;S.zone=stone;S.hp=S.maxHp;S.x=130;S.y=350;toast('THE VIGIL RESUMES','Sanctuary remembers your shape');resetZone();save()}
function attack(){if(!playing||S.done.atk)return;S.done.atk=true;setTimeout(()=>delete S.done.atk,(S.weapon===1?260:S.weapon===2?520:390)*(S.equipped==='band'?.85:1));sound('swing');let range=S.weapon===2?100:72,hit=false;enemies.forEach(e=>{if(Math.hypot(e.x-S.x,e.y-S.y)<range){e.hp-=S.weapon+1;e.hit=.18;hit=true;burst(e.x,e.y,S.weapon===2?'#ffe26f':'#f18a3d',10);sound('hit');if(e.hp<=0)kill(e)}});objects.forEach(o=>{if(o.type.startsWith('wall')&&Math.hypot(o.x-S.x,o.y-S.y)<90){o.hp-=S.weapon===2?3:1;burst(o.x,o.y,'#8c7963',10);if(o.hp<=0){S.done['z'+S.zone+o.id]=true;o.dead=true;sound('rare');toast('CRACKED STONE YIELDS','A hidden recess breathes open')}}});if(S.weapon>0)burst(S.x+45,S.y,S.weapon===2?'#ffd75d':'#f07532',8,120)}
function kill(e){S.done[e.id]=true;let g=5+Math.floor(Math.random()*11);S.gold+=g;burst(e.x,e.y,'#e9b755',20);sound('coin');toast('+'+g+' GOLDEN SIGILS','The construct falls silent');enemies=enemies.filter(a=>a!==e);updateUI();save()}
function shoot(){if(!playing||!S.shard||S.ammo<=0){if(playing&&!S.shard)toast('YOUR OFF-HAND IS EMPTY','A mechanism lies beyond the halfway vault');return}S.ammo--;shots.push({x:S.x,y:S.y,vx:420,life:2});sound('shot');updateUI()}
function acquire(name,sub){sound('rare');toast(name,sub);burst(S.x,S.y,'#fff0ac',30,130);flash=.8;save();updateUI()}
function interact(){if(!near)return;let o=near,tag='z'+S.zone+o.id;if(o.type==='stone'){let ix=S.zone===0?0:S.zone===1?1:2;S.stones[ix]=true;S.hp=S.maxHp;sound('save');toast('SANCTUARY AWAKENED','Progress held · health restored');save();updateUI()}
if(o.type==='door'){if(canExit()){S.done['z'+S.zone+'complete']=true;S.zone++;S.x=100;S.y=350;resetZone();toast(names[S.zone],desc[S.zone]);save()}else toast('THE WAY RESISTS',gateText())}
if(o.type==='back'){S.zone--;S.x=880;S.y=350;resetZone();toast(names[S.zone],'You retrace the Observatory\'s fading paths')}
if(o.type==='chest'&&!S.accessories.band){S.accessories.band=true;S.done[tag]=true;acquire("WARDEN'S BAND",'Accessory · quicker blade recovery')}
if(o.type==='shop'&&enemies.length===0)openShop();
if(o.type==='altar1'&&S.weapon<1){S.weapon=1;S.done[tag]=true;acquire('IGNITED EDGE','Ember damage · faster strikes · burning trail')}
if(o.type==='altar2'&&S.weapon<2){S.weapon=2;S.done[tag]=true;acquire('SOLAR FALCHION','Heavy solar arc · stone-breaking impact')}
if(o.type==='wallShard'&&o.dead&&!S.shard){S.shard=true;S.ammo=12;acquire('AETHER SHARD','Press F · distant switches and crystal bolts')}
if(o.type==='treasure'&&!S.accessories.cloak){S.accessories.cloak=true;S.done[tag]=true;acquire('SHADOW CLOAK','Accessory · dampens trap damage')}
if(o.type==='jar'&&!S.done[tag]){S.cons.seal++;S.done[tag]=true;acquire('BINDING SEAL','Consumable · freezes the nearest construct')}
if(o.type==='bonus'&&!S.done[tag]){S.gold+=120;S.done[tag]=true;acquire('+120 GOLDEN SIGILS','The Spire kept one last treasury')}
if(o.type==='beacon'){if(canExit()){document.getElementById('ending').style.display='flex';playing=false;sound('save')}else toast('THE LENS REMAINS DARK','Awaken both astral switches and end the guardian')}}
function canExit(){if(S.zone===0)return enemies.length===0&&S.stones[0];if(S.zone===1)return enemies.length===0;if(S.zone===2)return S.weapon>=1&&S.done.z2p1&&S.done.z2p2&&S.maxHp>=8;if(S.zone===3)return S.shard&&S.done.z3s1&&S.done.z3s2;if(S.zone===4)return S.weapon===2&&enemies.length===0;if(S.zone===5)return enemies.length===0&&S.done.z5f1&&S.done.z5f2}
function gateText(){return ['Awaken the stone and silence both sentinels.','The archive guard still moves.','Claim the altar, both plates, and the cracked-wall heart.','Find the Shard; strike both crystal switches.','The Solar altar and gauntlet await.','All instruments must sound together.'][S.zone]}
function useObjectTriggers(){objects.forEach(o=>{let tag='z'+S.zone+o.id,d=Math.hypot(o.x-S.x,o.y-S.y);if(o.type==='plate'&&d<45&&!S.done[tag]){S.done[tag]=true;o.active=true;sound('coin');toast('BRONZE PLATE DEPRESSED','A distant lock turns');save()}if(o.type==='wall'&&o.dead&&S.maxHp===6){S.maxHp=8;S.hp=8;acquire('HEART CONTAINER IV','Maximum vitality increased to 8 HP');o.dead=true}})}
function projectileHit(s){for(let o of objects){if(Math.hypot(o.x-s.x,o.y-s.y)<35&&(o.type.includes('Switch')||o.type==='switch')){let tag='z'+S.zone+o.id;if(!S.done[tag]){S.done[tag]=true;o.active=true;burst(o.x,o.y,'#74e4ff',25);sound('rare');toast('CRYSTAL MECHANISM AWAKENS',o.type==='switch'?'A reflected path shines':'The Spire answers the Shard');if(S.zone===3&&S.done.z3s1&&S.done.z3s2&&!S.accessories.lantern){S.accessories.lantern=true;acquire('PHANTOM LANTERN','Accessory · hidden paths gleam blue')}if(o.type==='switchHeart'&&S.maxHp<10){S.maxHp=10;S.hp=10;acquire('HEART CONTAINER V','Maximum vitality increased to 10 HP')}save()}s.life=0;return}}
for(let e of enemies)if(Math.hypot(e.x-s.x,e.y-s.y)<30){e.hp--;e.hit=.15;burst(e.x,e.y,'#76e7ff',9);tone(900,.12,'sine',.03,-200);if(e.hp<=0)kill(e);s.life=0;return}}
function inventory(){let p=document.getElementById('inventory');p.style.display='flex';playing=false;let items=[['band',"Warden's Band",S.accessories.band,'Accessory','Attack cadence quickens'],['lantern','Phantom Lantern',S.accessories.lantern,'Accessory','Hidden cracks glow blue'],['cloak','Shadow Cloak',S.accessories.cloak,'Accessory','Softens environmental harm'],['stem','Vital Stem',S.cons.stem,'Consumable ×'+S.cons.stem,'Restores 4 HP'],['seal','Binding Seal',S.cons.seal,'Consumable ×'+S.cons.seal,'Freezes nearest foe for 3 seconds']];document.getElementById('invgrid').innerHTML=items.filter(a=>a[2]).map(a=>`<div class="item ${S.equipped===a[0]?'equipped':''}" onclick="useItem('${a[0]}')"><b>${a[1]}</b><small>${a[3]}<br>${a[4]}</small></div>`).join('')||'<p>The satchel is empty.</p>'}
window.useItem=function(k){if(['band','lantern','cloak'].includes(k)){S.equipped=S.equipped===k?null:k;toast(S.equipped?'EQUIPPED':'UNEQUIPPED',k.toUpperCase())}else if(k==='stem'&&S.cons.stem){S.cons.stem--;S.hp=Math.min(S.maxHp,S.hp+4);sound('rare');toast('VITAL STEM CONSUMED','Warmth restores 4 HP')}else if(k==='seal'&&S.cons.seal){let e=enemies.sort((a,b)=>Math.hypot(a.x-S.x,a.y-S.y)-Math.hypot(b.x-S.x,b.y-S.y))[0];if(e){S.cons.seal--;e.freeze=3;toast('BINDING SEAL BROKEN','The nearest construct is frozen')}}save();updateUI();inventory()}
function openShop(){document.getElementById('shop').style.display='flex';playing=false;renderShop();tone(80,.5,'sine',.025)}function renderShop(){let arr=[['stem','Vital Stem',50,'Restores 4 HP',S.shop.stem],['seal','Binding Seal',75,'Freezes one enemy for 3 seconds',S.shop.seal],['heart','Heart Container V',300,'Permanent +2 maximum HP',S.shop.heart],['ammo','Aether Charge',10,'One crystal bolt',S.shard&&S.done.z3complete?99:0]];document.getElementById('shoprows').innerHTML=arr.map(a=>`<div class="shoprow"><span><b>${a[1]}</b><small> · ${a[3]} · ${a[4]} left</small></span><button onclick="buy('${a[0]}',${a[2]})" ${!a[4]?'disabled':''}>◆ ${a[2]}</button></div>`).join('')}
window.buy=function(k,p){if(S.gold<p){toast('INSUFFICIENT SIGILS','The altar remains cold');return}S.gold-=p;if(k==='stem'){S.cons.stem++;S.shop.stem--}if(k==='seal'){S.cons.seal++;S.shop.seal--}if(k==='heart'){S.maxHp=10;S.hp=10;S.shop.heart=0;acquire('HEART CONTAINER V','Purchased with deliberate sacrifice')}if(k==='ammo')S.ammo++;sound('buy');save();updateUI();renderShop()}
document.querySelectorAll('.close').forEach(b=>b.onclick=()=>{document.getElementById(b.dataset.close).style.display='none';playing=true});
addEventListener('keydown',e=>{keys[e.key.toLowerCase()]=true;if(e.code==='Space'){e.preventDefault();attack()}if(e.key.toLowerCase()==='f')shoot();if(e.key.toLowerCase()==='e')interact();if(e.key.toLowerCase()==='i'&&playing)inventory()});addEventListener('keyup',e=>keys[e.key.toLowerCase()]=false);
document.getElementById('begin').onclick=()=>{load();document.getElementById('title').style.display='none';playing=true;resetZone();toast(names[S.zone],'The Beacon calls through stone and storm')};
function update(dt){if(!playing)return;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),sp=155*(S.equipped==='band'?1.04:1);if(dx||dy){let l=Math.hypot(dx,dy);S.x+=dx/l*sp*dt;S.y+=dy/l*sp*dt;S.x=Math.max(60,Math.min(940,S.x));S.y=Math.max(125,Math.min(510,S.y));S.done.step=(S.done.step||0)+dt;if(S.done.step>.34){sound('step');S.done.step=0;if(S.weapon)burst(S.x-10,S.y+20,S.weapon===2?'#e9c44f':'#c95c2c',2,20)}}
useObjectTriggers();near=null;objects.forEach(o=>{if(o.dead&&o.type!=='wallShard')return;let d=Math.hypot(o.x-S.x,o.y-S.y);if(d<70&&!['plate','switch','switchHeart','finalSwitch'].includes(o.type))near=o});let hint=document.getElementById('hint');if(near){let labels={stone:'E · Rest at Sanctuary',door:'E · Enter the next hall',back:'E · Return to the previous hall',chest:'E · Open hidden chest',shop:'E · Commune with Merchant Altar',altar1:'E · Rekindle the blade',altar2:'E · Claim the solar edge',wallShard:near.dead?'E · Take the Aether Shard':'Hairline cracks scar the dark wall',treasure:'E · Unwrap dark treasure',jar:'E · Break the sealed jar',bonus:'E · Open the gilded coffer',beacon:'E · Reignite the Core Beacon'};hint.textContent=labels[near.type]||'';hint.classList.add('show')}else hint.classList.remove('show');
enemies.forEach(e=>{e.hit=Math.max(0,e.hit-dt);e.freeze=Math.max(0,e.freeze-dt);if(e.freeze)return;let d=Math.hypot(S.x-e.x,S.y-e.y),vx=(S.x-e.x)/d,vy=(S.y-e.y)/d;if(d<260&&d>38){e.x+=vx*(e.boss?53:42)*dt;e.y+=vy*(e.boss?53:42)*dt}if(d<43&&!S.done.invul)damage(e.boss?2:1)});shots.forEach(s=>{s.x+=s.vx*dt;s.life-=dt;projectileHit(s)});shots=shots.filter(s=>s.life>0&&s.x<1000);particles.forEach(p=>{p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=25*dt;p.life-=dt});particles=particles.filter(p=>p.life>0);flash=Math.max(0,flash-dt);shake=Math.max(0,shake-25*dt)}
function world(){let sx=shake?(Math.random()-.5)*shake:0,sy=shake?(Math.random()-.5)*shake:0;X.save();X.translate(sx,sy);let z=zones[S.zone],sc=Math.min(W/1000,H/600);X.setTransform(sc*D/D,0,0,sc*D/D,(W-1000*sc)/2+sx,(H-600*sc)/2+sy);let g=X.createLinearGradient(0,0,0,600);g.addColorStop(0,'#07101a');g.addColorStop(.65,z.floor);g.addColorStop(1,'#080d12');X.fillStyle=g;X.fillRect(0,0,1000,600);
// architecture
X.strokeStyle=z.accent+'55';X.lineWidth=3;for(let i=0;i<6;i++){X.beginPath();X.arc(500,330,160+i*42,Math.PI,Math.PI*2);X.stroke()}X.fillStyle='#0b1218';X.fillRect(35,100,930,25);X.fillRect(35,510,930,35);for(let x=60;x<960;x+=110){X.fillStyle='#151f26';X.fillRect(x,90,18,430);X.fillStyle=z.accent+'55';X.fillRect(x+4,105,3,390)}
// gears
for(let i=0;i<3;i++){let x=150+i*360,y=170+(i%2)*250;X.strokeStyle=z.accent+'44';X.lineWidth=8;X.beginPath();X.arc(x,y,34,0,7);X.stroke();for(let a=0;a<8;a++){X.save();X.translate(x,y);X.rotate(a*Math.PI/4);X.fillStyle=z.accent+'44';X.fillRect(28,-5,16,10);X.restore()}}
objects.forEach(drawObj);enemies.forEach(drawEnemy);shots.forEach(s=>{X.shadowBlur=16;X.shadowColor='#61eaff';X.fillStyle='#b8fbff';X.beginPath();X.arc(s.x,s.y,6,0,7);X.fill();X.shadowBlur=0});drawPlayer();particles.forEach(p=>{X.globalAlpha=Math.max(0,p.life);X.fillStyle=p.col;X.beginPath();X.arc(p.x,p.y,p.r,0,7);X.fill()});X.globalAlpha=1;X.restore();if(flash){X.fillStyle=`rgba(${S.hp?'255,54,36':'255,255,255'},${flash*.35})`;X.fillRect(0,0,W,H)}if(S.weapon===2&&S.done.atk){let q=X.createRadialGradient(W/2,H/2,50,W/2,H/2,Math.max(W,H)*.7);q.addColorStop(.7,'transparent');q.addColorStop(1,'#ffd85a33');X.fillStyle=q;X.fillRect(0,0,W,H)}}
function drawObj(o){let active=S.done['z'+S.zone+o.id]||o.active;if(o.type==='door'||o.type==='back'){X.fillStyle=o.type==='back'?'#4b5962':canExit()?'#c68b3b':'#27313a';X.fillRect(o.x-25,o.y-65,50,130);X.fillStyle='#080d12';X.fillRect(o.x-15,o.y-52,30,105)}else if(o.type==='stone'){X.fillStyle='#5c6870';X.fillRect(o.x-22,o.y,44,36);X.fillStyle='#73838a';X.beginPath();X.moveTo(o.x-30,o.y);X.lineTo(o.x,o.y-50);X.lineTo(o.x+30,o.y);X.fill();X.shadowBlur=active?25:8;X.shadowColor='#f1bd55';X.fillStyle=active?'#ffe6a0':'#a47843';X.beginPath();X.arc(o.x,o.y-25,8,0,7);X.fill();X.shadowBlur=0}else if(o.type.startsWith('wall')){if(o.dead)return;X.fillStyle='#242b2f';X.fillRect(o.x-35,o.y-65,70,130);X.strokeStyle=S.equipped==='lantern'?'#65d9ee':'#655b50';X.lineWidth=2;X.beginPath();X.moveTo(o.x-20,o.y-40);X.lineTo(o.x+8,o.y-12);X.lineTo(o.x-12,o.y+5);X.lineTo(o.x+25,o.y+38);X.stroke()}else if(o.type.includes('switch')||o.type.includes('Switch')){X.strokeStyle='#69828c';X.lineWidth=5;X.beginPath();X.arc(o.x,o.y,17,0,7);X.stroke();X.fillStyle=active?'#85edff':'#253943';X.shadowBlur=active?18:0;X.shadowColor='#62dff6';X.beginPath();X.arc(o.x,o.y,8,0,7);X.fill();X.shadowBlur=0}else if(o.type==='plate'){X.fillStyle=active?'#ba833f':'#534f46';X.fillRect(o.x-35,o.y-8,70,16)}else if(o.type.startsWith('altar')){X.fillStyle='#493c32';X.fillRect(o.x-40,o.y,80,35);X.fillStyle=active?'#ffc959':'#9e6a3f';X.fillRect(o.x-7,o.y-45,14,50);X.shadowBlur=active?20:5;X.shadowColor='#ffb33c'}else if(o.type==='beacon'){X.strokeStyle='#bf8d43';X.lineWidth=9;X.beginPath();X.arc(o.x,o.y,45,0,7);X.stroke();X.beginPath();X.arc(o.x,o.y,25,0,7);X.stroke()}else{X.fillStyle=o.type==='shop'?'#756038':'#5b4935';X.fillRect(o.x-25,o.y-20,50,35);X.fillStyle=o.type==='jar'?'#56a4ac':'#d0a64e';X.beginPath();X.arc(o.x,o.y-20,9,0,7);X.fill()}X.shadowBlur=0}
function drawEnemy(e){X.save();X.translate(e.x,e.y);if(e.freeze){X.shadowBlur=20;X.shadowColor='#6adfea'}X.fillStyle=e.hit?'#fff0ba':e.boss?'#401d1d':'#342d2b';X.fillRect(-18,-18,36,44);X.fillStyle='#17191b';X.beginPath();X.arc(0,-22,e.boss?22:17,Math.PI,0);X.fill();X.strokeStyle=e.boss?'#de7b39':'#92593c';X.lineWidth=4;X.beginPath();X.moveTo(-28,-5);X.lineTo(-10,5);X.moveTo(28,-5);X.lineTo(10,5);X.stroke();X.fillStyle='#d85b37';X.fillRect(-8,-25,4,3);X.fillRect(4,-25,4,3);X.fillStyle='#14191c';X.fillRect(-20,30,40,5);X.fillStyle='#bd5438';X.fillRect(-20,30,40*(e.hp/e.max),4);X.restore()}
function drawPlayer(){X.save();X.translate(S.x,S.y);let moving=keys.w||keys.a||keys.s||keys.d||keys.arrowup||keys.arrowdown||keys.arrowleft||keys.arrowright;X.fillStyle=S.equipped==='cloak'?'#171827':'#26384a';X.beginPath();X.moveTo(-17,-22);X.lineTo(-26,31);X.lineTo(0,25+(moving?Math.sin(performance.now()/90)*3:0));X.lineTo(26,31);X.lineTo(17,-22);X.fill();if(S.weapon===2){X.strokeStyle='#e8c75c';X.lineWidth=3;X.stroke()}X.fillStyle='#b7a06d';X.beginPath();X.arc(0,-29,10,0,7);X.fill();if(S.equipped==='band'){X.strokeStyle='#e7f4ff';X.lineWidth=3;X.beginPath();X.arc(-17,1,5,0,7);X.stroke()}if(S.equipped==='lantern'){X.shadowBlur=25;X.shadowColor='#55d8ff';X.fillStyle='#8cecff';X.beginPath();X.arc(-20,14,6,0,7);X.fill();X.shadowBlur=0}let ang=S.done.atk?-1.1:.35;X.save();X.rotate(ang);X.strokeStyle=['#aeb4b5','#ff7a31','#ffe06a'][S.weapon];X.shadowBlur=S.weapon?18:0;X.shadowColor=S.weapon===2?'#ffd84c':'#ff6b2d';X.lineWidth=S.weapon===2?8:5;X.beginPath();X.moveTo(12,2);X.lineTo(S.weapon===2?64:48,2);X.stroke();X.restore();if(S.shard){X.shadowBlur=14;X.shadowColor='#6feaff';X.fillStyle='#9cf5ff';X.beginPath();X.moveTo(-24,-5);X.lineTo(-16,-13);X.lineTo(-10,-3);X.lineTo(-17,7);X.fill();X.shadowBlur=0}X.restore()}
function loop(t){let dt=Math.min(.033,(t-last)/1000||0);last=t;update(dt);X.setTransform(1,0,0,1,0,0);X.clearRect(0,0,W,H);world();requestAnimationFrame(loop)}requestAnimationFrame(loop);
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-5.6-sol","provider":"openrouter","persona":"full","sessionId":"b9711d3a-9ce8-4c4d-820e-27f307f667e7","tokensIn":273737,"tokensOut":12746,"tokensTotal":286483,"cost":0.23880170000000003,"turns":15,"toolCalls":17,"failedToolCalls":0,"timestamp":"2026-09-30T21:44:29.229Z"} -->