← Warden results

Warden

muse spark 1.2 · full

Initial view of Warden

Browser check: Artifact runtime error. This is a technical check, not a quality review.

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 58,733 bytes · SHA-256 1b2631c66d0a
<!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>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700;900&family=Cinzel+Decorative:wght@700&family=JetBrains+Mono:wght@500&family=Cormorant+Garamond:ital,wght@0,400;1,400&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--gold:#e8a63a;--gold2:#f5c97a;--ember:#ff6b2b;--slate:#1a2332;--slate2:#24344e;--slate3:#2e4a6b;--paper:#f4e8c8;--rust:#8a4a2a}
body{background:#0d1119;color:var(--paper);font-family:'Cormorant Garamond',serif;overflow:hidden;height:100vh;display:flex;flex-direction:column}
::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-thumb{background:#3a506e;border-radius:99px}
/* HEADER */
header{height:54px;background:linear-gradient(180deg,#1b2538,#0f1828);border-bottom:1px solid #2e4260;display:flex;align-items:center;justify-content:space-between;padding:0 18px;flex-shrink:0}
.brand{display:flex;align-items:center;gap:12px}
.brand-mark{width:34px;height:34px;border:1px solid var(--gold);display:grid;place-items:center;background:radial-gradient(circle at 30% 30%,#2a3a55,#0f1828);font-family:'Cinzel Decorative',serif;color:var(--gold);font-size:14px}
.brand h1{font-family:'Cinzel',serif;font-size:15px;letter-spacing:.18em;font-weight:900;color:var(--gold2)}
.brand p{font-size:11px;letter-spacing:.12em;opacity:.6;font-family:'JetBrains Mono',monospace;text-transform:uppercase}
.hdr-right{display:flex;align-items:center;gap:10px}
.pill{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.12em;border:1px solid #3a506e;padding:6px 10px;background:#1a2a44;color:#8fb0d4}
.pill b{color:var(--gold2)}
.btn-gold{background:var(--gold);color:#1a1205;border:none;padding:7px 14px;font-family:'Cinzel',serif;font-weight:700;letter-spacing:.1em;font-size:11px;cursor:pointer}
.btn-gold:hover{background:var(--gold2)}
.btn-ghost{background:transparent;border:1px solid #3a506e;color:#8fb0d4;padding:6px 12px;font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.1em;cursor:pointer}
.btn-ghost:hover{border-color:var(--gold);color:var(--gold2)}
/* MAIN LAYOUT */
.main{flex:1;display:grid;grid-template-columns:1fr 320px;gap:0;overflow:hidden}
@media(max-width:980px){.main{grid-template-columns:1fr;grid-template-rows:1fr auto}}
.game-wrap{position:relative;background:radial-gradient(900px 500px at 30% 10%,#24344e 0%,#0d1119 70%);display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #1e2f4a}
/* zone nav */
.zone-nav{display:flex;gap:6px;padding:10px 12px;background:rgba(10,16,28,.9);border-bottom:1px solid #1e2f4a;flex-wrap:wrap}
.zbtn{flex:1;min-width:110px;padding:8px 6px;border:1px solid #2a3f5e;background:#142033;color:#7ea0c6;font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.08em;cursor:pointer;text-align:center;position:relative;transition:.15s}
.zbtn.active{background:linear-gradient(180deg,#c98a1e,#8a5a0a);color:#fff;border-color:#e8a63a;box-shadow:0 0 12px rgba(232,166,58,.4)}
.zbtn.locked{opacity:.35;pointer-events:none}
.zbtn small{display:block;font-size:8px;opacity:.7;margin-top:2px}
.zbtn .lock{position:absolute;top:4px;right:6px;font-size:10px}
/* canvas area */
.canvas-box{flex:1;position:relative;display:grid;place-items:center;padding:12px;background:
 radial-gradient(ellipse at 50% 0%,rgba(232,166,58,.08),transparent 60%),
 linear-gradient(180deg,rgba(255,255,255,.02),transparent)}
#game{width:min(860px,100%);height:460px;background:#0f1a2b;border:1px solid #2a3f5e;box-shadow:0 12px 40px rgba(0,0,0,.6), inset 0 0 80px rgba(0,0,0,.5);display:block;image-rendering:pixelated}
.hud-bar{display:flex;align-items:center;gap:12px;padding:8px 12px;background:#0f1828;border-top:1px solid #1e2f4a;font-family:'JetBrains Mono',monospace;font-size:10px;flex-wrap:wrap}
.hearts{display:flex;gap:4px;align-items:center}
.heart{width:22px;height:20px;position:relative;display:grid;place-items:center;font-size:14px;filter:drop-shadow(0 1px 4px rgba(0,0,0,.6))}
.heart .fill{position:absolute;inset:0;display:flex}
.heart .half{flex:1}
.legend{margin-left:auto;display:flex;gap:8px;align-items:center;color:#6d8ab0}
.kbd{border:1px solid #3a506e;border-bottom-width:2px;background:#1a2a44;padding:2px 6px;border-radius:3px;color:#cfe0f5;font-size:9px}
.inv-hint{color:#e8a63a}
/* side panel */
.side{background:linear-gradient(180deg,#111c2e,#0d1526);border-left:1px solid #1e2f4a;display:flex;flex-direction:column;overflow:hidden}
.side-scroll{flex:1;overflow:auto;padding:14px;display:flex;flex-direction:column;gap:14px}
.card{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);padding:12px}
.card h3{font-family:'Cinzel',serif;font-size:11px;letter-spacing:.16em;color:var(--gold2);margin-bottom:8px;display:flex;align-items:center;gap:8px}
.card h3::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,rgba(232,166,58,.4),transparent)}
.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.stat{background:#0f1a2b;border:1px solid #24344e;padding:8px;text-align:center}
.stat b{display:block;font-family:'Cinzel',serif;color:var(--gold2);font-size:16px}
.stat span{font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.1em;color:#6d8ab0}
.equip-row{display:flex;gap:8px;flex-wrap:wrap}
.equip{flex:1;min-width:90px;background:#0f1a2b;border:1px solid #24344e;padding:8px;display:flex;gap:8px;align-items:center}
.equip i{width:28px;height:28px;display:grid;place-items:center;background:#1a2a44;border:1px solid #2e4a6b;font-size:14px}
.equip b{font-family:'JetBrains Mono',monospace;font-size:9px;color:#cfe0f5;display:block}
.equip span{font-size:10px;color:#6d8ab0}
.equip.on{border-color:var(--gold);box-shadow:0 0 10px rgba(232,166,58,.25)}
.badge{font-family:'JetBrains Mono',monospace;font-size:8px;letter-spacing:.1em;padding:2px 6px;background:var(--gold);color:#1a1205}
.inv-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.inv-item{background:#0f1a2b;border:1px solid #24344e;padding:8px;cursor:pointer;position:relative;transition:.15s}
.inv-item:hover{border-color:#3a5a84}
.inv-item.active{border-color:var(--gold);background:rgba(232,166,58,.08)}
.inv-item .ico{width:100%;height:36px;display:grid;place-items:center;font-size:20px;background:radial-gradient(circle at 50% 40%,rgba(232,166,58,.15),transparent 70%)}
.inv-item b{font-family:'JetBrains Mono',monospace;font-size:9px;color:#cfe0f5;display:block;text-align:center;margin-top:4px}
.inv-item span{font-size:9px;color:#6d8ab0;display:block;text-align:center}
.inv-item .qty{position:absolute;top:6px;right:6px;background:#e8a63a;color:#1a1205;font-family:'JetBrains Mono',monospace;font-size:9px;padding:1px 5px;border-radius:99px}
.shop-list{display:flex;flex-direction:column;gap:8px}
.shop-row{display:flex;align-items:center;gap:10px;background:#0f1a2b;border:1px solid #24344e;padding:8px}
.shop-row i{width:30px;height:30px;display:grid;place-items:center;background:#1a2a44;border:1px solid #2e4a6b}
.shop-row b{font-family:'JetBrains Mono',monospace;font-size:10px;color:#cfe0f5}
.shop-row span{font-size:10px;color:#6d8ab0}
.shop-row button{margin-left:auto}
.log{height:110px;overflow:auto;background:#0a1220;border:1px solid #1e2f4a;padding:8px;font-family:'JetBrains Mono',monospace;font-size:10px;line-height:1.6;color:#8fb0d4}
.log b{color:var(--gold2)}
.progress{height:6px;background:#0f1a2b;border:1px solid #24344e;overflow:hidden}
.progress i{display:block;height:100%;background:linear-gradient(90deg,var(--gold),#ff6b2b);width:16%}
.pulse{animation:pulse 1.6s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
.shimmer{position:absolute;inset:0;pointer-events:none;border:2px solid rgba(232,166,58,.0);transition:.3s}
.shimmer.on{border-color:rgba(232,166,58,.9);box-shadow:inset 0 0 40px rgba(232,166,58,.3)}
.damage-flash{position:absolute;inset:0;background:rgba(255,40,40,.18);opacity:0;pointer-events:none;transition:.12s}
.damage-flash.on{opacity:1}
.toast{position:absolute;left:50%;top:14px;transform:translateX(-50%);background:#0f1a2b;border:1px solid var(--gold);color:var(--gold2);font-family:'Cinzel',serif;font-size:11px;letter-spacing:.14em;padding:8px 14px;box-shadow:0 8px 24px rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:.25s}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
.controls{position:absolute;bottom:10px;left:10px;background:rgba(10,16,28,.85);border:1px solid #24344e;padding:8px 10px;font-family:'JetBrains Mono',monospace;font-size:9px;color:#8fb0d4;line-height:1.6;max-width:260px}
.controls b{color:#cfe0f5}
.beacon-glow{position:absolute;inset:0;pointer-events:none;background:radial-gradient(500px 300px at 50% 50%,rgba(232,166,58,.18),transparent 70%);opacity:0;transition:1s}
.beacon-glow.on{opacity:1}
</style>
</head>
<body>
<header>
  <div class="brand">
    <div class="brand-mark">◈</div>
    <div>
      <h1>THE WARDEN'S CORROSION</h1>
      <p>Aurelion's Sunken Observatory — Last Warden Protocol</p>
    </div>
  </div>
  <div class="hdr-right">
    <div class="pill">SIGILS <b id="goldTop">0</b> &nbsp;|&nbsp; ZONE <b id="zoneTop">1 / 6</b></div>
    <button class="btn-ghost" id="btnSave">SAVE</button>
    <button class="btn-gold" id="btnRest">REST AT STONE</button>
  </div>
</header>

<div class="main">
  <div class="game-wrap">
    <div class="zone-nav" id="zoneNav"></div>
    <div class="canvas-box">
      <canvas id="game" width="860" height="460"></canvas>
      <div class="shimmer" id="shimmer"></div>
      <div class="damage-flash" id="dmgFlash"></div>
      <div class="beacon-glow" id="beaconGlow"></div>
      <div class="toast" id="toast"></div>
      <div class="controls">
        <b>WASD</b> move &nbsp; <b>SPACE</b> ember blade &nbsp; <b>F / CLICK</b> aether shard<br>
        <b>E</b> interact &nbsp; <b>1-5</b> use item &nbsp; <b>Q</b> toggle accessory<br>
        <span style="opacity:.7">Break cracked walls, stand on bronze plates, shoot crystal switches.</span>
      </div>
    </div>
    <div class="hud-bar">
      <div class="hearts" id="hearts"></div>
      <div style="display:flex;gap:6px;align-items:center">
        <span style="color:#6d8ab0">BLADE</span>
        <span id="bladeName" style="color:var(--gold2);font-weight:700">Ember Blade</span>
        <span id="rangedBadge" style="display:none;background:#3a6a9a;color:#fff;padding:2px 6px;border-radius:99px;font-size:9px">◈ AETHER SHARD READY</span>
      </div>
      <div class="legend">
        <span><span class="kbd">SPACE</span> swing</span>
        <span><span class="kbd">F</span> shard</span>
        <span class="inv-hint" id="hint">Seek the cracked wall →</span>
      </div>
    </div>
  </div>

  <div class="side">
    <div class="side-scroll">
      <div class="card">
        <h3>WARDEN STATE</h3>
        <div class="stat-grid">
          <div class="stat"><b id="sHp">6 / 6</b><span>HEALTH (HP)</span></div>
          <div class="stat"><b id="sGold">0</b><span>SIGILS</span></div>
          <div class="stat"><b id="sDmg">8</b><span>DMG</span></div>
          <div class="stat"><b id="sAtk">1.0×</b><span>ATK SPEED</span></div>
        </div>
        <div style="margin-top:10px" class="progress"><i id="progBar"></i></div>
        <div style="display:flex;justify-content:space-between;font-family:'JetBrains Mono',monospace;font-size:9px;color:#6d8ab0;margin-top:4px"><span>PROGRESSION</span><span id="progText">16% — Ashen Threshold</span></div>
      </div>

      <div class="card">
        <h3>EQUIPPED</h3>
        <div class="equip-row">
          <div class="equip on" id="eqBlade"><i>⚔</i><div><b id="eqBladeName">Ember Blade</b><span id="eqBladeDesc">Physical</span></div></div>
          <div class="equip" id="eqRanged"><i>◈</i><div><b>Aether Shard</b><span id="eqRangedDesc">Locked — Zone 4</span></div></div>
        </div>
        <div class="equip-row" style="margin-top:8px">
          <div class="equip" id="eqAcc"><i id="eqAccIco">○</i><div><b id="eqAccName">No Accessory</b><span id="eqAccDesc">Find Warden's Band</span></div><span class="badge" style="margin-left:auto;display:none" id="accOn">ON</span></div>
        </div>
      </div>

      <div class="card">
        <h3>INVENTORY — <span style="font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.08em;color:#8fb0d4">1-5 to use • Q to toggle</span></h3>
        <div class="inv-grid" id="invGrid"></div>
      </div>

      <div class="card" id="shopCard" style="display:none">
        <h3>MERCHANT ALTAR — Rusted Archives <span style="margin-left:auto;font-family:'JetBrains Mono',monospace;font-size:9px;background:#1a2a44;border:1px solid #2e4a6b;padding:2px 6px;color:#8fb0d4">ZONE 2 ONLY</span></h3>
        <div class="shop-list" id="shopList"></div>
      </div>

      <div class="card">
        <h3>CHRONICLE</h3>
        <div class="log" id="log"></div>
      </div>
    </div>
  </div>
</div>

<script>
const ZONES = [
  {id:1, name:"The Ashen Threshold", sub:"Prologue — First Steps", color:"#8a5a2a", desc:"Sanctuary Stone at the entrance. Learn movement & blade."},
  {id:2, name:"The Rusted Archives", sub:"Early — Archives & Shop", color:"#6a7a8a", desc:"Shop unlocked. Warden's Band hidden. Vital Stem cache."},
  {id:3, name:"The Collapsed Gallery", sub:"Mid-Early — Ignited Edge", color:"#a05a2a", desc:"Altar of Ignited Edge. Pressure-plate puzzle. Heart #4 behind cracks."},
  {id:4, name:"The Sunken Vault", sub:"Mid — The Turning Point", color:"#1e6a6a", desc:"Aether Shard hidden behind cracked wall. Phantom Lantern puzzle."},
  {id:5, name:"The Twin Spire", sub:"Late — Solar Falchion", color:"#8a7a2a", desc:"Solar Falchion altar. Heart #5 via shard switch or 300g shop. Gauntlet."},
  {id:6, name:"The Core Chamber", sub:"Climax — Beacon", color:"#c9a02a", desc:"All mechanics. Restore the Core Beacon."},
];

const ITEMS = {
  band:{id:'band', name:"Warden's Band", type:'accessory', icon:'◎', desc:'+15% attack speed. Silver ring.', zone:2},
  stem:{id:'stem', name:"Vital Stem", type:'consumable', icon:'🌿', desc:'Restores 4 HP (2 containers).', zone:2, use:'heal'},
  lantern:{id:'lantern', name:"Phantom Lantern", type:'accessory', icon:'🏮', desc:'Reveals cracks & hidden paths. Blue glow.', zone:4},
  cloak:{id:'cloak', name:"Shadow Cloak", type:'accessory', icon:'🧥', desc:'-30% trap damage. Dark trim.', zone:5},
  seal:{id:'seal', name:"Binding Seal", type:'consumable', icon:'❖', desc:'Freezes one enemy 3s.', zone:5, use:'freeze'},
};

let state = {
  zone:1,
  unlocked:1,
  hp:6, maxHp:6, containers:3,
  gold:0,
  weapon:0, //0 base,1 ignited,2 solar
  hasRanged:false, shardCharges:0,
  inventory:{stem:1, band:0, lantern:0, cloak:0, seal:0},
  equippedAcc:null,
  flags:{
    altar1:false, altar2:false,
    wall4broken:false, vaultPuzzle:false,
    heart4:false, heart5:false,
    shopHeartBought:false,
    beacon:false
  },
  kills:0
};

let shopStock = {stem:3, seal:2, heart:1, ammo:99};

const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let particles=[], floats=[], projectiles=[], enemies=[], plates=[], switches=[], chests=[], walls=[], sanctuary=null, altar=null, beacon=null;
let player={x:140,y:220, vx:0, vy:0, dir:1, swinging:false, swingT:0, shootCD:0, invuln:0};
let keys={}, mouse={x:0,y:0};
let lastT=0, shake=0, zoneAlpha=0;

function audio(){
  try{
    const ac = new (window.AudioContext||window.webkitAudioContext)();
    return {
      tone(f, dur, type='sine', gain=0.2, slide){
        const o=ac.createOscillator(), g=ac.createGain();
        o.type=type; o.frequency.value=f;
        if(slide){ o.frequency.exponentialRampToValueAtTime(slide, ac.currentTime+dur); }
        g.gain.value=gain; g.gain.exponentialRampToValueAtTime(0.001, ac.currentTime+dur);
        o.connect(g).connect(ac.destination); o.start(); o.stop(ac.currentTime+dur);
      },
      play(name){
        if(name==='swing'){ this.tone(320,0.12,'square',0.15,180); setTimeout(()=>this.tone(900,0.08,'sine',0.08),40); }
        if(name==='swingFire'){ this.tone(280,0.14,'sawtooth',0.18,160); this.tone(1200,0.18,'triangle',0.12); }
        if(name==='swingSolar'){ this.tone(90,0.35,'sawtooth',0.25,45); this.tone(600,0.25,'sine',0.12); }
        if(name==='shard'){ this.tone(880,0.08,'sine',0.22,1400); this.tone(1400,0.12,'triangle',0.1); }
        if(name==='hit'){ this.tone(180,0.1,'square',0.2,60); }
        if(name==='hurt'){ this.tone(220,0.35,'sawtooth',0.28,140); this.tone(520,0.2,'sine',0.14); }
        if(name==='pick'){ this.tone(540,0.12,'sine',0.18,880); this.tone(880,0.15,'sine',0.12); }
        if(name==='rare'){ this.tone(440,0.2,'sine',0.16,660); setTimeout(()=>this.tone(660,0.2,'sine',0.16,880),120); setTimeout(()=>this.tone(880,0.35,'triangle',0.14),240); }
        if(name==='coin'){ this.tone(900,0.08,'sine',0.15,1200); }
        if(name==='altar'){ this.tone(220,0.6,'triangle',0.2,330); setTimeout(()=>this.tone(330,0.6,'sine',0.16,440),200); }
        if(name==='stone'){ this.tone(180,0.8,'sine',0.18,270); this.tone(540,0.5,'triangle',0.08); }
        if(name==='door'){ this.tone(120,0.4,'square',0.18,80); }
        if(name==='beacon'){ this.tone(110,1.2,'sine',0.22,220); setTimeout(()=>this.tone(440,1.0,'triangle',0.15),300); }
      }
    };
  }catch(e){ return {play(){}} }
}
const SFX = audio();

// LOG
function log(msg, rare=false){
  const el=document.getElementById('log');
  const d=document.createElement('div');
  d.innerHTML = (rare?'<b>◆</b> ':'— ') + msg;
  if(rare) d.style.color='#f5c97a';
  el.appendChild(d); el.scrollTop=el.scrollHeight;
  if(el.children.length>60) el.removeChild(el.firstChild);
}
function toast(msg){
  const t=document.getElementById('toast');
  t.textContent=msg; t.classList.add('show');
  setTimeout(()=>t.classList.remove('show'),1800);
}
function shimmer(){
  const s=document.getElementById('shimmer');
  s.classList.add('on'); setTimeout(()=>s.classList.remove('on'),2000);
}
function dmgFlash(){
  const f=document.getElementById('dmgFlash');
  f.classList.add('on'); setTimeout(()=>f.classList.remove('on'),140);
  shake=10;
}

// PARTICLES
function spawnParticles(x,y, color, n=10, speed=3){
  for(let i=0;i<n;i++){
    particles.push({x,y, vx:(Math.random()-0.5)*speed, vy:(Math.random()-0.5)*speed -1, life:0.5+Math.random()*0.5, max:1, color, size:2+Math.random()*3});
  }
}
function spawnFloat(x,y, text, color='#f5c97a'){
  floats.push({x,y,vx:0,vy:-1.2,life:1, text,color});
}

// ZONE SETUP
function buildZone(n){
  particles=[]; floats=[]; projectiles=[]; enemies=[]; plates=[]; switches=[]; chests=[]; walls=[]; sanctuary=null; altar=null; beacon=null;
  player.x=140; player.y=220; player.vx=0; player.vy=0;
  shake=0;

  // common walls (borders)
  const W=860,H=460;
  // zone specific layouts
  if(n===1){
    walls.push({x:0,y:0,w:W,h:18},{x:0,y:H-18,w:W,h:18},{x:0,y:0,w:18,h:H},{x:W-18,y:0,w:18,h:H});
    // inner pillars
    walls.push({x:300,y:80,w:18,h:120},{x:540,y:260,w:18,h:120});
    // cracked wall hiding? just tutorial breakable
    walls.push({x:680,y:120,w:18,h:90, crack:true, hp:2});
    enemies.push(makeEnemy(420,180, 12, 18), makeEnemy(620,300, 12, 18));
    sanctuary={x:90,y:200,w:54,h:54, active:false};
    chests.push({x:700,y:320,w:30,h:30, type:'gold', amount:25, taken:false});
    log("Ashen Threshold: Sanctuary Stone glows at the entrance. Rest to bind your soul.", true);
  }
  if(n===2){
    walls.push({x:0,y:0,w:W,h:18},{x:0,y:H-18,w:W,h:18},{x:0,y:0,w:18,h:H},{x:W-18,y:0,w:18,h:H});
    walls.push({x:260,y:0,w:18,h:180},{x:260,y:300,w:18,h:160},{x:560,y:100,w:18,h:260});
    walls.push({x:420,y:180,w:100,h:18});
    enemies.push(makeEnemy(380,120, 14, 22), makeEnemy(660,360, 14, 22), makeEnemy(460,340, 12, 20));
    sanctuary={x:120,y:360,w:54,h:54, active:false};
    // Warden's Band chest hidden
    chests.push({x:720,y:80,w:30,h:30, type:'band', taken:false});
    chests.push({x:96,y:96,w:30,h:30, type:'stem', taken:false});
    chests.push({x:620,y:200,w:30,h:30, type:'gold', amount:40, taken:false});
    // shop altar
    altar={x:380,y:250,w:52,h:52, shop:true};
  }
  if(n===3){
    walls.push({x:0,y:0,w:W,h:18},{x:0,y:H-18,w:W,h:18},{x:0,y:0,w:18,h:H},{x:W-18,y:0,w:18,h:H});
    walls.push({x:200,y:0,w:18,h:160},{x:200,y:280,w:18,h:180});
    walls.push({x:500,y:0,w:18,h:200},{x:500,y:320,w:18,h:140});
    // pressure plates puzzle: two plates must be pressed to open door to altar
    plates.push({x:330,y:360,w:44,h:44, pressed:false, timer:0});
    plates.push({x:620,y:100,w:44,h:44, pressed:false, timer:0});
    // door wall
    walls.push({x:490,y:200,w:28,h:120, door:true, id:'door3'});
    // altar Ignited Edge
    altar={x:600,y:220,w:56,h:56, upgrade:1};
    // cracked wall hiding heart container 4
    walls.push({x:120,y:140,w:18,h:90, crack:true, hp:3, heart:true});
    enemies.push(makeEnemy(360,140, 16, 26, true), makeEnemy(680,340, 15, 24));
    sanctuary={x:90,y:320,w:54,h:54, active:false};
    chests.push({x:750,y:320,w:30,h:30, type:'gold', amount:35, taken:false});
  }
  if(n===4){
    walls.push({x:0,y:0,w:W,h:18},{x:0,y:H-18,w:W,h:18},{x:0,y:0,w:18,h:H},{x:W-18,y:0,w:18,h:H});
    walls.push({x:260,y:80,w:18,h:300},{x:580,y:0,w:18,h:180},{x:580,y:280,w:18,h:180});
    // breakable hiding Aether Shard
    walls.push({x:740,y:160,w:18,h:90, crack:true, hp:2, shard:true});
    // lantern puzzle: switch that reveals lantern chest
    switches.push({x:400,y:60,w:22,h:22, hit:false});
    chests.push({x:400,y:340,w:30,h:30, type:'lantern', taken:false, locked:true});
    chests.push({x:120,y:80,w:30,h:30, type:'gold', amount:50, taken:false});
    enemies.push(makeEnemy(460,180, 14, 22), makeEnemy(680,380, 16, 26, true), makeEnemy(340,300, 12, 18));
    sanctuary={x:100,y:360,w:54,h:54, active:false};
    altar={x:660,y:240,w:40,h:40, decor:true};
  }
  if(n===5){
    walls.push({x:0,y:0,w:W,h:18},{x:0,y:H-18,w:W,h:18},{x:0,y:0,w:18,h:H},{x:W-18,y:0,w:18,h:H});
    walls.push({x:220,y:0,w:18,h:200},{x:220,y:280,w:18,h:180},{x:520,y:100,w:18,h:260});
    walls.push({x:360,y:200,w:160,h:18});
    altar={x:600,y:220,w:56,h:56, upgrade:2};
    // distant switch for heart 5 (requires ranged)
    switches.push({x:760,y:70,w:22,h:22, hit:false, heart:true});
    chests.push({x:750,y:340,w:30,h:30, type:'gold', amount:80, taken:false});
    chests.push({x:120,y:300,w:30,h:30, type:'cloak', taken:false});
    chests.push({x:120,y:120,w:30,h:30, type:'seal', taken:false});
    // door to altar initially closed until switch hit or plates? We'll gate heart chest separately
    enemies.push(makeEnemy(360,120, 18, 28, true), makeEnemy(460,340, 16, 26, true), makeEnemy(660,120, 15, 24), makeEnemy(700,280, 14, 22));
    sanctuary={x:90,y:200,w:54,h:54, active:false};
  }
  if(n===6){
    walls.push({x:0,y:0,w:W,h:18},{x:0,y:H-18,w:W,h:18},{x:0,y:0,w:18,h:H},{x:W-18,y:0,w:18,h:H});
    walls.push({x:300,y:0,w:18,h:160},{x:540,y:300,w:18,h:160},{x:360,y:200,w:120,h:18});
    switches.push({x:420,y:60,w:22,h:22, hit:false, beacon:true});
    switches.push({x:620,y:60,w:22,h:22, hit:false, beacon:true});
    beacon={x:410,y:180,w:80,h:80, active:false, lit:false};
    enemies.push(makeEnemy(380,120, 20, 30, true), makeEnemy(620,360, 20, 30, true), makeEnemy(460,260, 18, 28, true));
    sanctuary={x:90,y:360,w:54,h:54, active:false};
  }
}

function makeEnemy(x,y,hp,dmg, elite=false){
  return {x,y,hp,max:hp,dmg,elite, cd:0, vx:0, vy:0, alive:true, freeze:0};
}

// INPUT
window.addEventListener('keydown',e=>{
  keys[e.key.toLowerCase()]=true;
  if(e.code==='Space'){ playerAttack(); e.preventDefault(); }
  if(e.key.toLowerCase()==='f'){ playerShoot(); }
  if(e.key.toLowerCase()==='e'){ interact(); }
  if(e.key.toLowerCase()==='q'){ toggleAccessory(); }
  if(e.key>='1' && e.key<='5'){ useItem(parseInt(e.key)); }
});
window.addEventListener('keyup',e=> keys[e.key.toLowerCase()]=false);
canvas.addEventListener('click',e=>{
  const r=canvas.getBoundingClientRect();
  mouse.x=(e.clientX-r.left)*(canvas.width/r.width);
  mouse.y=(e.clientY-r.top)*(canvas.height/r.height);
  if(state.hasRanged) playerShoot(mouse.x, mouse.y);
});
canvas.addEventListener('mousemove',e=>{
  const r=canvas.getBoundingClientRect();
  mouse.x=(e.clientX-r.left)*(canvas.width/r.width);
  mouse.y=(e.clientY-r.top)*(canvas.height/r.height);
});

// ACTIONS
function playerAttack(){
  if(player.swinging) return;
  player.swinging=true; player.swingT=0;
  const dmg = state.weapon===0?8 : state.weapon===1?12 : 18;
  const range = state.weapon===2? 58 : 42;
  const speed = state.equippedAcc==='band'? 0.85 : 1;
  let hitSomething=false;

  // hit enemies
  enemies.forEach(en=>{
    if(!en.alive) return;
    const d=Math.hypot(en.x - player.x, en.y - player.y);
    // directional cone
    const ang=Math.atan2(en.y - player.y, en.x - player.x);
    const facing = player.dir===1?0:Math.PI;
    let adiff=Math.abs(Math.atan2(Math.sin(ang-facing), Math.cos(ang-facing)));
    if(d < range+26 && adiff < 1.9){
      en.hp -= dmg;
      en.freeze=0.12;
      hitSomething=true;
      spawnParticles(en.x, en.y, state.weapon===1?'#ff7a2a':state.weapon===2?'#ffd86a':'#ff9a3a', 10, 4);
      spawnFloat(en.x, en.y-18, `-${dmg}`, '#ffd86a');
      SFX.play('hit');
      if(en.hp<=0){
        en.alive=false; state.kills++;
        const gold = 5 + Math.floor(Math.random()*11);
        state.gold+=gold; spawnParticles(en.x,en.y,'#f5c97a',14,5); spawnFloat(en.x,en.y-28,`+${gold}g`,'#f5c97a'); SFX.play('coin');
        // chance drops
        if(Math.random()<0.12 && state.inventory.stem<3){ state.inventory.stem++; log("Construct dropped a Vital Stem!"); }
      }
    }
  });
  // hit breakable walls
  walls.forEach(w=>{
    if(!w.crack) return;
    const d=Math.hypot((w.x+w.w/2)-player.x,(w.y+w.h/2)-player.y);
    if(d<60){
      w.hp--; hitSomething=true; spawnParticles(w.x+w.w/2,w.y+w.h/2,'#8a6a3a',8,3);
      SFX.play('hit');
      if(w.hp<=0){
        if(w.heart && !state.flags.heart4){ gainHeart(); state.flags.heart4=true; toast("HEART CONTAINER ACQUIRED — Collapsed Gallery"); log("Cracked wall collapsed — Heart Container #4! You feel sturdier.", true); shimmer(); SFX.play('rare'); }
        if(w.shard && !state.hasRanged){ state.hasRanged=true; state.shardCharges=12; toast("AETHER SHARD DISCOVERED — Ranged weapon unlocked"); log("You found the Aether Shard! Press F or click to fire crystal bolts.", true); shimmer(); SFX.play('rare'); }
        if(!w.heart && !w.shard){ state.gold+=15; spawnFloat(w.x+w.w/2,w.y,'+15g','#f5c97a'); }
        w.crack=false; w.hp=0; // remove crack
        // convert to open
        setTimeout(()=>{ const idx=walls.indexOf(w); if(idx>=0) walls.splice(idx,1); }, 200);
      }
    }
  });

  if(state.weapon===1) SFX.play('swingFire');
  else if(state.weapon===2) SFX.play('swingSolar');
  else SFX.play('swing');

  if(hitSomething && state.weapon===1) spawnParticles(player.x+ player.dir*22, player.y, '#ff6b2b', 6, 2);
  if(hitSomething && state.weapon===2) spawnParticles(player.x, player.y, '#ffd86a', 12, 3);
}

function playerShoot(tx,ty){
  if(!state.hasRanged) { if(state.zone>=4) toast("Find the Aether Shard behind cracked wall in Sunken Vault"); return; }
  if(player.shootCD>0) return;
  if(state.shardCharges<=0){ toast("No shard charges — buy at Merchant (10g)"); return; }
  player.shootCD = state.equippedAcc==='band'?0.32:0.45;
  state.shardCharges--;
  const a = tx!==undefined? Math.atan2(ty - player.y, tx - player.x) : (player.dir===1?0:Math.PI);
  projectiles.push({x:player.x+player.dir*18, y:player.y-6, vx:Math.cos(a)*7.5, vy:Math.sin(a)*7.5, life:1.2});
  SFX.play('shard');
  spawnParticles(player.x+player.dir*18, player.y-6, '#7ad8ff', 5, 2);
}

function interact(){
  // sanctuary
  if(sanctuary && dist(player,sanctuary)<60){
    sanctuary.active=true;
    state.hp=state.maxHp;
    SFX.play('stone'); shimmer(); spawnParticles(sanctuary.x+27,sanctuary.y+18,'#ffd86a',18,3);
    saveGame();
    toast("SANCTUARY STONE — Progress bound. Health restored.");
    log("Sanctuary Stone resonates. Your progress is saved and wounds close.", true);
    return;
  }
  // altar / shop / upgrade
  if(altar){
    if(dist(player,altar)<64){
      if(altar.shop){
        document.getElementById('shopCard').scrollIntoView({behavior:'smooth',block:'nearest'});
        toast("Merchant Altar — spend Sigils wisely");
        return;
      }
      if(altar.upgrade===1 && !state.flags.altar1){
        state.flags.altar1=true; state.weapon=1; SFX.play('altar'); shimmer(); spawnParticles(player.x,player.y,'#ff7a2a',22,4);
        toast("IGNITED EDGE — Blade burns with ember light"); log("Altar of Ignited Edge ignites! Damage fire, speed up, ember trail.", true);
        if(state.unlocked<4) state.unlocked=4; // allow vault
        if(state.zone===3) state.unlocked=Math.max(state.unlocked,4);
      }
      if(altar.upgrade===2 && !state.flags.altar2){
        if(!state.hasRanged){ toast("The altar demands the Aether Shard's resonance..."); return; }
        state.flags.altar2=true; state.weapon=2; SFX.play('altar'); shimmer(); spawnParticles(player.x,player.y,'#ffd86a',26,4);
        toast("SOLAR FALCHION — Golden light floods the blade"); log("Solar Falchion awakened! Wide arc, heavy impact, golden halo.", true);
        if(state.unlocked<6) state.unlocked=6;
      }
      if(altar.decor){ toast("Ancient vault mechanism hums... shoot the crystal switches"); }
    }
  }
  // chests
  chests.forEach(c=>{
    if(c.taken) return;
    if(dist(player,c)<48){
      if(c.locked){ toast("Hidden mechanism — hit the crystal switch with Aether Shard"); return; }
      c.taken=true;
      if(c.type==='gold'){ state.gold+=c.amount; spawnFloat(c.x,c.y,`+${c.amount}g`,'#f5c97a'); SFX.play('coin'); spawnParticles(c.x+15,c.y+15,'#f5c97a',12,3); }
      else if(c.type==='band'){ state.inventory.band=1; SFX.play('rare'); shimmer(); toast("WARDEN'S BAND — Attack speed +15% (press Q to equip)"); log("Found Warden's Band in Rusted Archives!", true); }
      else if(c.type==='stem'){ state.inventory.stem=(state.inventory.stem||0)+1; SFX.play('pick'); toast("VITAL STEM acquired (+4 HP)"); }
      else if(c.type==='lantern'){ state.inventory.lantern=1; SFX.play('rare'); shimmer(); toast("PHANTOM LANTERN — Cracks now glow blue"); log("Phantom Lantern reveals hidden passages in Vault.", true); }
      else if(c.type==='cloak'){ state.inventory.cloak=1; SFX.play('rare'); shimmer(); toast("SHADOW CLOAK — Trap damage reduced"); }
      else if(c.type==='seal'){ state.inventory.seal=(state.inventory.seal||0)+1; SFX.play('pick'); toast("BINDING SEAL ×1 — Freeze enemy"); }
      if(c.type==='gold') spawnParticles(c.x+15,c.y+8,'#f5c97a',10,3);
      else spawnParticles(c.x+15,c.y+8,'#ffd86a',14,3);
    }
  });
  // beacon final
  if(beacon && dist(player,beacon)<60){
    const litSwitches = switches.filter(s=>s.hit && s.beacon).length;
    if(litSwitches>=2){
      beacon.lit=true; state.flags.beacon=true; SFX.play('beacon'); document.getElementById('beaconGlow').classList.add('on'); shimmer();
      toast("CORE BEACON REIGNITED — Aurelion Endures"); log("Beacon blazes! The Corrosion recedes. You are the Warden renewed.", true);
      state.unlocked=6;
      // fireworks
      for(let i=0;i<40;i++) setTimeout(()=>spawnParticles(420+Math.random()*80, 220+Math.random()*40, ['#ffd86a','#ff6b2b','#7ad8ff'][i%3], 6, 5), i*40);
    } else {
      toast("Beacon dormant — strike both crystal switches with Aether Shard");
    }
  }
}

function toggleAccessory(){
  const avail = [];
  if(state.inventory.band) avail.push('band');
  if(state.inventory.lantern) avail.push('lantern');
  if(state.inventory.cloak) avail.push('cloak');
  if(avail.length===0){ toast("No accessory owned yet"); return; }
  let idx = avail.indexOf(state.equippedAcc);
  state.equippedAcc = avail[(idx+1)%avail.length];
  SFX.play('pick'); toast(`Equipped: ${ITEMS[state.equippedAcc]?.name||state.equippedAcc}`);
}
function useItem(slot){
  const map=['stem','band','lantern','cloak','seal'];
  const id = map[slot-1];
  if(!id) return;
  if(id==='stem'){
    if((state.inventory.stem||0)<=0){ toast("No Vital Stem"); return; }
    if(state.hp>=state.maxHp){ toast("Health already full"); return; }
    state.inventory.stem--; state.hp=Math.min(state.maxHp, state.hp+4);
    SFX.play('pick'); spawnParticles(player.x,player.y,'#5cffa0',14,3); toast("Vital Stem used — +4 HP");
  }
  if(id==='seal'){
    if((state.inventory.seal||0)<=0){ toast("No Binding Seal"); return; }
    let target=null, best=999;
    enemies.forEach(en=>{ if(!en.alive) return; const d=Math.hypot(en.x-player.x,en.y-player.y); if(d<best){best=d; target=en;} });
    if(!target || best>220){ toast("No enemy in range"); return; }
    state.inventory.seal--; target.freeze=3; SFX.play('pick'); spawnParticles(target.x,target.y,'#7ad8ff',16,4); toast("Binding Seal — Enemy frozen 3s");
  }
  if(id==='band' || id==='lantern' || id==='cloak'){
    if(!state.inventory[id]){ toast("Not owned"); return; }
    state.equippedAcc = state.equippedAcc===id? null : id;
    SFX.play('pick'); toast(state.equippedAcc? `Equipped ${ITEMS[id].name}`: `Unequipped ${ITEMS[id].name}`);
  }
}

function gainHeart(){
  state.containers++; state.maxHp=state.containers*2; state.hp=state.maxHp;
  spawnParticles(player.x,player.y,'#ffd86a',18,4); shimmer();
}

function dist(a,b){ return Math.hypot((a.x+a.w? a.x+a.w/2:a.x)-(b.x+b.w? b.x+b.w/2:b.x), (a.y+a.h? a.y+a.h/2:a.y)-(b.y+b.h? b.y+b.h/2:b.y)); }

// SHOP
function renderShop(){
  const shopList=document.getElementById('shopList');
  const card=document.getElementById('shopCard');
  if(state.zone===2) card.style.display='block'; else card.style.display='none';
  shopList.innerHTML='';
  const rows=[
    {id:'stem', name:'Vital Stem', desc:'Heal 4 HP', price:50, stock:shopStock.stem, icon:'🌿'},
    {id:'seal', name:'Binding Seal', desc:'Freeze foe 3s', price:75, stock:shopStock.seal, icon:'❖'},
    {id:'heart', name:'Heart Container #5', desc:'+2 max HP (max 5)', price:300, stock:shopStock.heart, icon:'♥'},
    {id:'ammo', name:'Shard Charges ×5', desc: state.hasRanged?'10g per 5':'Locked until Vault', price:10, stock: shopStock.ammo>0? '∞':'0', icon:'◈', disabled:!state.hasRanged},
  ];
  rows.forEach(r=>{
    const div=document.createElement('div'); div.className='shop-row';
    div.innerHTML=`<i>${r.icon}</i><div><b>${r.name}</b><span>${r.desc} — ${r.price}g • Stock: ${r.stock}</span></div>`;
    const b=document.createElement('button');
    b.className='btn-gold'; b.style.padding='6px 10px'; b.style.fontSize='10px';
    b.textContent=r.stock===0||r.stock==='0' ? 'SOLD OUT' : 'BUY';
    b.disabled=(r.stock===0||r.stock==='0'||r.disabled);
    if(r.disabled) b.style.opacity=.4;
    b.onclick=()=>{
      if(state.gold < r.price){ toast("Not enough Sigils"); SFX.play('hit'); return; }
      state.gold-=r.price;
      if(r.id==='stem'){ shopStock.stem--; state.inventory.stem=(state.inventory.stem||0)+1; SFX.play('coin'); toast("Purchased Vital Stem"); }
      if(r.id==='seal'){ shopStock.seal--; state.inventory.seal=(state.inventory.seal||0)+1; SFX.play('coin'); toast("Purchased Binding Seal"); }
      if(r.id==='heart'){ if(state.flags.shopHeartBought||state.flags.heart5){ toast("Already have 5 hearts"); state.gold+=r.price; return;} shopStock.heart=0; state.flags.shopHeartBought=true; gainHeart(); state.flags.heart5=true; SFX.play('rare'); shimmer(); toast("Heart Container #5 — Max HP 10!"); log("Merchant's ultimate treasure — Heart Container #5!", true); }
      if(r.id==='ammo'){ state.shardCharges+=5; SFX.play('coin'); toast("+5 Shard charges"); }
      renderShop(); updateUI();
    };
    div.appendChild(b); shopList.appendChild(div);
  });
}

// SAVE
function saveGame(){
  localStorage.setItem('warden_save', JSON.stringify(state));
  log("Progress bound to Sanctuary Stone.");
}
function loadGame(){
  const s=localStorage.getItem('warden_save');
  if(s){ try{ Object.assign(state, JSON.parse(s)); }catch(e){} }
}

// UI
function renderZones(){
  const nav=document.getElementById('zoneNav');
  nav.innerHTML='';
  ZONES.forEach(z=>{
    const b=document.createElement('button');
    b.className='zbtn'+(state.zone===z.id?' active':'')+(z.id>state.unlocked?' locked':'');
    b.innerHTML=`${z.name}<small>${z.sub}</small>${z.id>state.unlocked?'<span class="lock">🔒</span>':''}`;
    b.onclick=()=>{ if(z.id>state.unlocked) return; state.zone=z.id; buildZone(z.id); renderZones(); renderShop(); };
    nav.appendChild(b);
  });
  document.getElementById('zoneTop').textContent= state.zone+' / 6';
  document.getElementById('progBar').style.width= (state.zone/6*100)+'%';
  document.getElementById('progText').textContent = Math.round(state.zone/6*100)+'% — '+ZONES[state.zone-1].name;
}

function updateUI(){
  document.getElementById('goldTop').textContent=state.gold;
  document.getElementById('sGold').textContent=state.gold;
  document.getElementById('sHp').textContent= state.hp+' / '+state.maxHp;
  const dmg = state.weapon===0?8:state.weapon===1?12:18;
  document.getElementById('sDmg').textContent=dmg;
  document.getElementById('sAtk').textContent= (state.equippedAcc==='band'?'1.15×':'1.0×');
  document.getElementById('bladeName').textContent= state.weapon===0?'Ember Blade':state.weapon===1?'Ignited Edge':'Solar Falchion';
  document.getElementById('eqBladeName').textContent=document.getElementById('bladeName').textContent;
  document.getElementById('eqBladeDesc').textContent= state.weapon===0?'Physical':state.weapon===1?'Fire • Ember trail':'Solar • Wide arc';
  const rb=document.getElementById('rangedBadge');
  rb.style.display= state.hasRanged? 'inline-block':'none';
  document.getElementById('eqRangedDesc').textContent= state.hasRanged? `${state.shardCharges} charges • F to fire`:'Locked — Zone 4';
  document.getElementById('eqRanged').classList.toggle('on', state.hasRanged);
  document.getElementById('eqBlade').classList.toggle('on', true);
  // accessory
  const accName=document.getElementById('eqAccName'), accDesc=document.getElementById('eqAccDesc'), accIco=document.getElementById('eqAccIco'), accOn=document.getElementById('accOn');
  if(state.equippedAcc){
    const it=ITEMS[state.equippedAcc];
    accName.textContent=it.name; accDesc.textContent=it.desc; accIco.textContent=it.icon; accOn.style.display='block'; document.getElementById('eqAcc').classList.add('on');
  } else { accName.textContent='No Accessory'; accDesc.textContent='Press Q to equip'; accIco.textContent='○'; accOn.style.display='none'; document.getElementById('eqAcc').classList.remove('on'); }

  // hearts
  const hearts=document.getElementById('hearts'); hearts.innerHTML='';
  for(let i=0;i<state.containers;i++){
    const hpLeft = state.hp - i*2;
    let fill=0; if(hpLeft>=2) fill=2; else if(hpLeft===1) fill=1;
    const h=document.createElement('div'); h.className='heart';
    h.innerHTML= fill===2? '❤' : fill===1? '💔':'♡';
    h.style.color= fill===0? '#2a3f5e' : fill===1? '#ff6b6b' : '#ff3b3b';
    h.title=`Container ${i+1}: ${fill*1} /2 HP`;
    hearts.appendChild(h);
  }
  // inventory grid
  const invGrid=document.getElementById('invGrid'); invGrid.innerHTML='';
  const order=['stem','band','lantern','cloak','seal'];
  order.forEach((id,idx)=>{
    const owned = state.inventory[id]||0;
    const it=ITEMS[id];
    const div=document.createElement('div'); div.className='inv-item'+(state.equippedAcc===id?' active':'');
    div.innerHTML=`<div class="ico">${it.icon}</div><b>${it.name}</b><span>${it.type==='accessory'?(owned?'Owned':'Locked'):`×${owned}`}</span>${owned>1?`<span class="qty">×${owned}</span>`:''}<div style="text-align:center;margin-top:4px;font-family:'JetBrains Mono',monospace;font-size:8px;color:#6d8ab0">${idx+1}</div>`;
    if(it.type==='accessory' && !owned) div.style.opacity=.45;
    div.onclick=()=> useItem(idx+1);
    invGrid.appendChild(div);
  });

  // hint
  let hint='Explore →';
  if(state.zone===3 && !state.flags.altar1) hint='Altar beyond timed door — stand on both plates!';
  else if(state.zone===3 && !state.flags.heart4) hint='Cracked wall left side hides Heart #4';
  else if(state.zone===4 && !state.hasRanged) hint='Hairline cracks on right wall — break it!';
  else if(state.zone===5 && !state.flags.altar2) hint='Solar altar needs Aether Shard';
  else if(state.zone===6 && !state.flags.beacon) hint='Hit both crystal switches, then ignite Beacon';
  document.getElementById('hint').textContent=hint;
}

// COLLISION
function rectCollide(ax,ay,aw,ah,bx,by,bw,bh){ return ax<bx+bw && ax+aw>bx && ay<by+bh && ay+ah>by; }

// UPDATE LOOP
function update(dt){
  // player movement
  let mx=0,my=0;
  if(keys['w']||keys['arrowup']) my=-1;
  if(keys['s']||keys['arrowdown']) my=1;
  if(keys['a']||keys['arrowleft']) {mx=-1; player.dir=-1;}
  if(keys['d']||keys['arrowright']) {mx=1; player.dir=1;}
  if(mx!==0 && my!==0){ mx*=0.71; my*=0.71; }
  const speed = 2.6 * (state.equippedAcc==='band'?1.15:1);
  let nx = player.x + mx*speed;
  let ny = player.y + my*speed;

  // simple wall collision (AABB with player 18x18)
  const pw=18, ph=18;
  let canX=true, canY=true;
  walls.forEach(w=>{
    if(w.door && plates.filter(p=>p.pressed).length>=2) return; // door open when both plates
    if(rectCollide(nx, player.y - ph/2, pw, ph, w.x,w.y,w.w,w.h)) canX=false;
    if(rectCollide(player.x - pw/2, ny - ph/2, pw, ph, w.x,w.y,w.w,w.h)) canY=false;
  });
  if(canX) player.x=nx;
  if(canY) player.y=ny;
  // clamp
  player.x=Math.max(32,Math.min(828,player.x)); player.y=Math.max(36,Math.min(424,player.y));

  // swing timer
  if(player.swinging){
    player.swingT+=dt;
    const dur = state.weapon===2?0.34 : state.weapon===1?0.22:0.2;
    if(player.swingT>dur) player.swinging=false;
  }
  if(player.shootCD>0) player.shootCD-=dt;
  if(player.invuln>0) player.invuln-=dt;

  // plates
  plates.forEach(p=>{
    const on = rectCollide(player.x-pw/2, player.y-ph/2, pw,ph, p.x,p.y,p.w,p.h) || enemies.some(en=>en.alive && rectCollide(en.x-12,en.y-12,24,24,p.x,p.y,p.w,p.h));
    p.pressed=on;
    if(on) p.timer=Math.min(1,p.timer+dt*3); else p.timer=Math.max(0,p.timer-dt*2);
  });
  // open door visual
  // projectiles
  projectiles.forEach(pr=>{
    pr.x+=pr.vx; pr.y+=pr.vy; pr.life-=dt;
    // wall hit
    walls.forEach(w=>{
      if(rectCollide(pr.x-4,pr.y-4,8,8,w.x,w.y,w.w,w.h)){ pr.life=0; spawnParticles(pr.x,pr.y,'#7ad8ff',6,2); }
    });
    // switch hit
    switches.forEach(sw=>{
      if(!sw.hit && rectCollide(pr.x-4,pr.y-4,8,8, sw.x,sw.y,sw.w,sw.h)){
        sw.hit=true; pr.life=0; spawnParticles(sw.x+11,sw.y+11,'#7ad8ff',14,4); SFX.play('pick'); shimmer();
        if(sw.heart && !state.flags.heart5){
          gainHeart(); state.flags.heart5=true; toast("HEART #5 — Distant switch!"); log("Ranged switch shattered — Heart Container #5 revealed!", true);
        }
        if(sw.beacon){
          // check both
          const lit = switches.filter(s=>s.beacon && s.hit).length;
          if(lit>=1) toast(`Beacon switch ${lit}/2 lit`);
        }
        if(!sw.heart && !sw.beacon){
          // unlock lantern chest
          const chest = chests.find(c=>c.type==='lantern'); if(chest) chest.locked=false;
          toast("Crystal switch struck — hidden cache revealed");
        }
      }
    });
    // enemy hit
    enemies.forEach(en=>{
      if(!en.alive) return;
      if(rectCollide(pr.x-4,pr.y-4,8,8, en.x-14,en.y-14,28,28)){
        en.hp-=10; pr.life=0; spawnParticles(en.x,en.y,'#7ad8ff',10,3); spawnFloat(en.x,en.y-16,'-10','#7ad8ff');
        SFX.play('hit');
        if(en.hp<=0){ en.alive=false; state.kills++; state.gold+=8; spawnFloat(en.x,en.y-26,'+8g','#f5c97a'); }
      }
    });
  });
  projectiles=projectiles.filter(p=>p.life>0 && p.x>0 && p.x<860 && p.y>0 && p.y<460);

  // enemies AI
  enemies.forEach(en=>{
    if(!en.alive) return;
    if(en.freeze>0){ en.freeze-=dt; return; }
    const d=Math.hypot(en.x-player.x, en.y-player.y);
    if(d<260){
      const ang=Math.atan2(player.y-en.y, player.x-en.x);
      const sp = en.elite?1.35:0.95;
      let ex = en.x + Math.cos(ang)*sp;
      let ey = en.y + Math.sin(ang)*sp*0.9;
      // wall collide for enemy
      let blocked=false;
      walls.forEach(w=>{ if(w.door && plates.filter(p=>p.pressed).length>=2) return; if(rectCollide(ex-12,ey-12,24,24,w.x,w.y,w.w,w.h)) blocked=true; });
      if(!blocked){ en.x=ex; en.y=ey; }
      // attack if close
      if(d<28 && en.cd<=0 && player.invuln<=0){
        let dmg = en.dmg - (state.equippedAcc==='cloak'? Math.floor(en.dmg*0.3):0);
        state.hp-= dmg>0?dmg:1;
        en.cd=1.0; player.invuln=0.8;
        spawnFloat(player.x, player.y-22, `-${dmg}`, '#ff4a4a');
        spawnParticles(player.x,player.y,'#ff3b3b',10,4);
        SFX.play('hurt'); dmgFlash();
        if(state.hp<=0){
          state.hp=0; toast("FALLEN — Returned to last Sanctuary");
          log("You fell to Corrosion. Sigils lost...", true);
          state.gold=Math.max(0, state.gold-10);
          // respawn at sanctuary
          if(sanctuary){ player.x=sanctuary.x+27; player.y=sanctuary.y+10; state.hp=Math.min(state.maxHp,2); }
          else { player.x=140; player.y=220; state.hp=state.maxHp; }
        }
      }
    }
    if(en.cd>0) en.cd-=dt;
  });

  // auto unlock next zone when altar achieved or simple kill threshold
  if(state.zone===1 && state.kills>=2) state.unlocked=Math.max(state.unlocked,2);
  if(state.zone===2 && state.kills>=4) state.unlocked=Math.max(state.unlocked,3);
  if(state.zone===3 && state.flags.altar1) state.unlocked=Math.max(state.unlocked,4);
  if(state.zone===4 && state.hasRanged) state.unlocked=Math.max(state.unlocked,5);
  if(state.zone===5 && state.flags.altar2) state.unlocked=Math.max(state.unlocked,6);

  // particles update
  particles.forEach(p=>{ p.x+=p.vx; p.y+=p.vy; p.vy+=0.12; p.life-=dt; });
  particles=particles.filter(p=>p.life>0);
  floats.forEach(f=>{ f.y+=f.vy; f.life-=dt; });
  floats=floats.filter(f=>f.life>0);
}

function draw(){
  ctx.clearRect(0,0,canvas.width,canvas.height);
  // background zones tinted
  const zc=ZONES[state.zone-1].color;
  // floor texture
  ctx.fillStyle='#0e1a2e'; ctx.fillRect(0,0,860,460);
  // subtle grid + color tint
  ctx.fillStyle= zc+'14'; ctx.fillRect(0,0,860,460);
  ctx.strokeStyle='rgba(255,255,255,0.04)'; ctx.lineWidth=1;
  for(let x=0;x<860;x+=40){ ctx.beginPath(); ctx.moveTo(x,18); ctx.lineTo(x,442); ctx.stroke(); }
  for(let y=18;y<460;y+=40){ ctx.beginPath(); ctx.moveTo(18,y); ctx.lineTo(842,y); ctx.stroke(); }

  // mist particles for vault
  if(state.zone===4){
    ctx.fillStyle='rgba(120,180,200,0.06)';
    for(let i=0;i<3;i++){ const ox=(Date.now()*0.02+i*120)%860; ctx.beginPath(); ctx.ellipse(ox, 360+i*18, 80, 18,0,0,Math.PI*2); ctx.fill(); }
  }

  // plates
  plates.forEach(p=>{
    ctx.fillStyle= p.pressed? '#e8a63a' : '#3a4a5a';
    ctx.strokeStyle='#1a2332'; ctx.lineWidth=2;
    ctx.fillRect(p.x,p.y,p.w,p.h);
    ctx.strokeRect(p.x,p.y,p.w,p.h);
    if(p.pressed){ ctx.fillStyle='rgba(232,166,58,0.25)'; ctx.fillRect(p.x-4,p.y-4,p.w+8,p.h+8); }
    ctx.fillStyle='#8fb0d4'; ctx.font='9px JetBrains Mono'; ctx.fillText('◉', p.x+16,p.y+26);
  });

  // walls
  walls.forEach(w=>{
    if(w.door){
      const open = plates.filter(p=>p.pressed).length>=2;
      if(open){ ctx.fillStyle='rgba(232,166,58,0.15)'; ctx.fillRect(w.x,w.y,w.w,w.h); ctx.fillStyle='#e8a63a'; ctx.font='10px JetBrains Mono'; ctx.fillText('OPEN', w.x-4,w.y+66); return; }
      ctx.fillStyle='#2a3a4a'; ctx.fillRect(w.x,w.y,w.w,w.h);
      ctx.fillStyle='#3a506e'; for(let i=0;i<w.h;i+=10) ctx.fillRect(w.x+4,w.y+i+4, w.w-8,4);
      ctx.fillStyle='#e8a63a'; ctx.font='9px JetBrains Mono'; ctx.fillText('DOOR', w.x-2,w.y+10);
    } else if(w.crack){
      ctx.fillStyle='#1e2a3e'; ctx.fillRect(w.x,w.y,w.w,w.h);
      ctx.strokeStyle= state.inventory.lantern && state.equippedAcc==='lantern' ? '#7ad8ff' : '#5a3a2a';
      ctx.lineWidth= state.inventory.lantern && state.equippedAcc==='lantern' ? 2:1.4;
      ctx.strokeRect(w.x,w.y,w.w,w.h);
      // crack lines
      ctx.strokeStyle= state.inventory.lantern && state.equippedAcc==='lantern' ? '#7ad8ff' : '#8a5a2a';
      ctx.beginPath(); ctx.moveTo(w.x+4,w.y+16); ctx.lineTo(w.x+w.w-4,w.y+28); ctx.moveTo(w.x+6,w.y+44); ctx.lineTo(w.x+w.w-6,w.y+62); ctx.stroke();
      if(state.equippedAcc==='lantern') { ctx.fillStyle='rgba(122,216,255,0.25)'; ctx.fillRect(w.x-6,w.y-6,w.w+12,w.h+12); ctx.fillStyle='#7ad8ff'; ctx.font='8px JetBrains Mono'; ctx.fillText('CRACK', w.x-6,w.y-8); }
      else { ctx.fillStyle='#e8a63a'; ctx.font='7px JetBrains Mono'; ctx.fillText('✧', w.x+4,w.y+12); }
    } else {
      ctx.fillStyle='#1a2a44'; ctx.fillRect(w.x,w.y,w.w,w.h);
      ctx.fillStyle='#24344e'; ctx.fillRect(w.x,w.y,w.w,4); ctx.fillRect(w.x,w.y,4,w.h);
    }
  });

  // switches
  switches.forEach(sw=>{
    ctx.fillStyle= sw.hit? '#7ad8ff':'#2a3a5a';
    ctx.strokeStyle= sw.hit? '#7ad8ff':'#3a506e'; ctx.lineWidth=2;
    ctx.fillRect(sw.x,sw.y,sw.w,sw.h); ctx.strokeRect(sw.x,sw.y,sw.w,sw.h);
    ctx.fillStyle= sw.hit? '#0a1220':'#7ad8ff'; ctx.font='12px monospace'; ctx.textAlign='center'; ctx.fillText('◈', sw.x+11, sw.y+15); ctx.textAlign='left';
    if(sw.hit){ ctx.fillStyle='rgba(122,216,255,0.3)'; ctx.beginPath(); ctx.arc(sw.x+11,sw.y+11,18,0,Math.PI*2); ctx.fill(); }
  });

  // chests
  chests.forEach(c=>{
    if(c.taken) return;
    const pulse = Math.sin(Date.now()*0.004 + c.x)*0.15+0.85;
    ctx.globalAlpha=pulse;
    ctx.fillStyle= c.locked? '#2a2a3a' : '#3a2a12';
    ctx.fillRect(c.x,c.y,c.w,c.h);
    ctx.strokeStyle= c.locked? '#4a4a5a' : '#e8a63a'; ctx.lineWidth=1.5; ctx.strokeRect(c.x,c.y,c.w,c.h);
    ctx.fillStyle= c.locked? '#6a6a7a' : '#f5c97a'; ctx.font='14px monospace'; ctx.fillText(c.type==='gold'?'¢': c.type==='band'?'◎':c.type==='stem'?'🌿':c.type==='lantern'?'🏮':c.type==='cloak'?'🧥':'❖', c.x+7,c.y+19);
    ctx.globalAlpha=1;
    if(!c.locked){ ctx.fillStyle='rgba(245,201,122,0.18)'; ctx.beginPath(); ctx.arc(c.x+15,c.y+15,22,0,Math.PI*2); ctx.fill(); }
  });

  // sanctuary
  if(sanctuary){
    const g = ctx.createRadialGradient(sanctuary.x+27,sanctuary.y+27,6, sanctuary.x+27,sanctuary.y+27,40);
    g.addColorStop(0,'rgba(232,166,58,0.55)'); g.addColorStop(1,'rgba(232,166,58,0)');
    ctx.fillStyle=g; ctx.beginPath(); ctx.arc(sanctuary.x+27,sanctuary.y+27,40,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#1a2a44'; ctx.strokeStyle='#e8a63a'; ctx.lineWidth=2; ctx.fillRect(sanctuary.x,sanctuary.y,sanctuary.w,sanctuary.h); ctx.strokeRect(sanctuary.x,sanctuary.y,sanctuary.w,sanctuary.h);
    ctx.fillStyle='#e8a63a'; ctx.font='10px Cinzel'; ctx.fillText('STONE', sanctuary.x+8, sanctuary.y+30);
    ctx.fillStyle='rgba(232,166,58,0.9)'; ctx.beginPath(); ctx.arc(sanctuary.x+27,sanctuary.y+12,6,0,Math.PI*2); ctx.fill();
    // floating motes
    ctx.fillStyle='rgba(245,201,122,0.5)'; for(let i=0;i<4;i++){ const yy = sanctuary.y+8 + Math.sin(Date.now()*0.001+i)*8; ctx.beginPath(); ctx.arc(sanctuary.x+10+i*12, yy, 1.5,0,Math.PI*2); ctx.fill(); }
  }

  // altar
  if(altar){
    const ax=altar.x, ay=altar.y;
    ctx.fillStyle= altar.shop? '#1a2a44' : '#2a1a0a';
    ctx.strokeStyle= altar.shop? '#7ad8ff' : '#e8a63a'; ctx.lineWidth=2;
    ctx.fillRect(ax,ay,altar.w,altar.h); ctx.strokeRect(ax,ay,altar.w,altar.h);
    ctx.fillStyle= altar.shop? '#7ad8ff' : '#ffd86a'; ctx.font='10px Cinzel'; ctx.textAlign='center';
    ctx.fillText(altar.shop?'SHOP': altar.upgrade===1?'IGNITE': altar.upgrade===2?'SOLAR':'VAULT', ax+altar.w/2, ay+30);
    ctx.textAlign='left';
    if(altar.upgrade && ((altar.upgrade===1 && !state.flags.altar1)||(altar.upgrade===2 && !state.flags.altar2))){
      ctx.fillStyle='rgba(232,166,58,0.3)'; ctx.beginPath(); ctx.arc(ax+altar.w/2, ay+altar.h/2, 38,0,Math.PI*2); ctx.fill();
    }
  }

  // beacon
  if(beacon){
    ctx.fillStyle= beacon.lit? '#ffd86a':'#1a2a44'; ctx.strokeStyle= beacon.lit? '#ffd86a':'#3a506e'; ctx.lineWidth= beacon.lit?3:2;
    ctx.fillRect(beacon.x,beacon.y,beacon.w,beacon.h); ctx.strokeRect(beacon.x,beacon.y,beacon.w,beacon.h);
    if(beacon.lit){
      ctx.fillStyle='rgba(255,216,106,0.45)'; ctx.beginPath(); ctx.arc(beacon.x+40,beacon.y+10,80,0,Math.PI*2); ctx.fill();
      ctx.fillStyle='#fff'; ctx.font='12px Cinzel'; ctx.textAlign='center'; ctx.fillText('BEACON LIT', beacon.x+40, beacon.y+46); ctx.textAlign='left';
    } else {
      ctx.fillStyle='#6d8ab0'; ctx.font='9px JetBrains Mono'; ctx.textAlign='center'; ctx.fillText('BEACON', beacon.x+40, beacon.y+46); ctx.textAlign='left';
    }
  }

  // enemies
  enemies.forEach(en=>{
    if(!en.alive) return;
    // shadow
    ctx.fillStyle='rgba(0,0,0,0.35)'; ctx.beginPath(); ctx.ellipse(en.x,en.y+16,18,7,0,0,Math.PI*2); ctx.fill();
    // body
    const frozen = en.freeze>0;
    ctx.fillStyle= frozen? '#7ad8ff' : en.elite? '#3a1a1a':'#2a2a3a';
    ctx.strokeStyle= frozen? '#7ad8ff': en.elite? '#ff3b3b':'#5a4a3a'; ctx.lineWidth=1.5;
    ctx.fillRect(en.x-14,en.y-14,28,28); ctx.strokeRect(en.x-14,en.y-14,28,28);
    // eyes
    ctx.fillStyle= frozen? '#0a1220' : '#ff3b3b'; ctx.beginPath(); ctx.arc(en.x-6,en.y-4,3,0,Math.PI*2); ctx.arc(en.x+6,en.y-4,3,0,Math.PI*2); ctx.fill();
    // hp bar
    ctx.fillStyle='#0a1220'; ctx.fillRect(en.x-14,en.y-20,28,4);
    ctx.fillStyle= en.elite? '#ff6b2b':'#e8a63a'; ctx.fillRect(en.x-14,en.y-20,28*(en.hp/en.max),4);
    if(frozen){ ctx.fillStyle='rgba(122,216,255,0.25)'; ctx.fillRect(en.x-14,en.y-14,28,28); ctx.fillStyle='#7ad8ff'; ctx.font='8px JetBrains Mono'; ctx.fillText('FROZEN', en.x-16, en.y-24); }
  });

  // projectiles
  projectiles.forEach(pr=>{
    ctx.fillStyle='#7ad8ff'; ctx.shadowColor='#7ad8ff'; ctx.shadowBlur=10;
    ctx.beginPath(); ctx.arc(pr.x,pr.y,5,0,Math.PI*2); ctx.fill(); ctx.shadowBlur=0;
    ctx.fillStyle='rgba(122,216,255,0.25)'; ctx.beginPath(); ctx.arc(pr.x,pr.y,10,0,Math.PI*2); ctx.fill();
  });

  // player
  const flicker = player.invuln>0 && Math.floor(Date.now()*0.02)%2===0;
  if(!flicker){
    // cloak
    ctx.fillStyle= state.equippedAcc==='cloak'? '#1a1a2a':'#2a1a0a';
    ctx.globalAlpha=0.95;
    ctx.beginPath(); ctx.moveTo(player.x-12, player.y-8); ctx.lineTo(player.x+12, player.y-8); ctx.lineTo(player.x+9, player.y+12); ctx.lineTo(player.x-9, player.y+12); ctx.closePath(); ctx.fill();
    // body
    ctx.fillStyle='#3a2a12'; ctx.fillRect(player.x-8, player.y-14,16,20);
    // head
    ctx.fillStyle='#f4e8c8'; ctx.beginPath(); ctx.arc(player.x, player.y-14,8,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#1a1205'; ctx.beginPath(); ctx.arc(player.x-3, player.y-15,1.6,0,Math.PI*2); ctx.arc(player.x+3, player.y-15,1.6,0,Math.PI*2); ctx.fill();
    // ring token
    if(state.equippedAcc==='band'){ ctx.strokeStyle='#c0d8ff'; ctx.lineWidth=1.5; ctx.beginPath(); ctx.arc(player.x+8, player.y+2,3,0,Math.PI*2); ctx.stroke(); }
    if(state.equippedAcc==='lantern'){ ctx.fillStyle='rgba(122,216,255,0.7)'; ctx.beginPath(); ctx.arc(player.x-10, player.y-6,4,0,Math.PI*2); ctx.fill(); }
    // blade
    const bladeLen = state.weapon===2? 30:24;
    const bladeW = state.weapon===2? 6:4;
    const off = player.swinging? Math.sin(player.swingT*18)*0.9 : 0;
    const ang = player.dir===1? 0+off : Math.PI -off;
    ctx.save(); ctx.translate(player.x, player.y-4); ctx.rotate(ang);
    // glow
    if(state.weapon===1){ ctx.fillStyle='rgba(255,107,43,0.45)'; ctx.fillRect(8,-3, bladeLen+6, bladeW+2); }
    if(state.weapon===2){ ctx.fillStyle='rgba(255,216,106,0.45)'; ctx.fillRect(8,-4, bladeLen+8, bladeW+4); }
    // blade rect
    ctx.fillStyle= state.weapon===2? '#ffd86a' : state.weapon===1? '#ff7a2a' : '#cfe0f5';
    ctx.fillRect(8, -bladeW/2, bladeLen, bladeW);
    ctx.fillStyle='#1a1205'; ctx.fillRect(0,-3,10,6);
    if(player.swinging){
      ctx.strokeStyle= state.weapon===2? '#ffd86a': state.weapon===1? '#ff6b2b':'#cfe0f5'; ctx.lineWidth=2; ctx.globalAlpha=0.6;
      ctx.beginPath(); ctx.arc(10,0,22, -0.9, 0.9); ctx.stroke();
    }
    ctx.restore();
    // offhand shard
    if(state.hasRanged){
      ctx.fillStyle='#7ad8ff'; ctx.shadowColor='#7ad8ff'; ctx.shadowBlur=8;
      ctx.beginPath(); ctx.arc(player.x - player.dir*10, player.y-6, 5,0,Math.PI*2); ctx.fill(); ctx.shadowBlur=0;
    }
    // ember trail for upgrade 1
    if(state.weapon===1){
      ctx.fillStyle='rgba(255,107,43,0.35)'; ctx.beginPath(); ctx.arc(player.x - player.dir*12, player.y+6, 3,0,Math.PI*2); ctx.fill();
    }
  }

  // particles
  particles.forEach(p=>{
    ctx.globalAlpha=Math.max(0,p.life);
    ctx.fillStyle=p.color; ctx.beginPath(); ctx.arc(p.x,p.y,p.size,0,Math.PI*2); ctx.fill();
  }); ctx.globalAlpha=1;
  // floats
  floats.forEach(f=>{
    ctx.globalAlpha=Math.max(0,f.life);
    ctx.fillStyle=f.color; ctx.font='bold 11px JetBrains Mono'; ctx.fillText(f.text, f.x-12, f.y);
  }); ctx.globalAlpha=1;

  // zone title
  ctx.fillStyle='rgba(10,16,28,0.75)'; ctx.fillRect(18,18, 300,22);
  ctx.fillStyle='#f5c97a'; ctx.font='10px Cinzel'; ctx.fillText(`${ZONES[state.zone-1].name.toUpperCase()}`, 26,33);
  // charges
  if(state.hasRanged){
    ctx.fillStyle='rgba(10,16,28,0.75)'; ctx.fillRect(680,18,160,22);
    ctx.fillStyle='#7ad8ff'; ctx.font='10px JetBrains Mono'; ctx.fillText(`◈ ${state.shardCharges} CHARGES`, 690,33);
  }

  // shake
  if(shake>0){ canvas.style.transform=`translate(${ (Math.random()-0.5)*shake}px, ${(Math.random()-0.5)*shake}px)`; shake*=0.88; if(shake<0.3) shake=0; } else canvas.style.transform='';
}

function gameLoop(t){
  const dt = Math.min(0.033, (t-lastT)/1000 || 0); lastT=t;
  update(dt); draw(); updateUI(); requestAnimationFrame(gameLoop);
}

// INIT
loadGame();
buildZone(state.zone);
renderZones(); renderShop();
if(!state.flags.heart4 && state.zone===3) log("Hint: The western wall shows darker cracks — the blade can break it.", false);
log("WASD to move. SPACE to swing. Find the Sanctuary Stone and bind your progress.", false);
requestAnimationFrame(gameLoop);
setInterval(()=>{ if(state.hp>0) saveGame(); }, 4000);

document.getElementById('btnSave').onclick=()=>{ saveGame(); toast("Progress saved"); SFX.play('stone'); };
document.getElementById('btnRest').onclick=()=>{
  if(sanctuary && dist(player,sanctuary)<80){ interact(); }
  else { toast("Reach a Sanctuary Stone to rest"); }
};
window.addEventListener('keydown',e=>{ if(e.key==='r' && e.ctrlKey){ localStorage.clear(); location.reload(); }});
</script>
</body>
</html>