← Warden results

Warden

Gemini 3.7 Flash · full

Initial view of Warden

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 112,765 bytes · SHA-256 89c5711674ff
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>The Warden's Corrosion — Aurelion's Sunken Observatory</title>
<style>
  :root {
    --gold: #f4c542;
    --gold-glow: rgba(244, 197, 66, 0.4);
    --ember: #e65c00;
    --solar: #ffe066;
    --slate: #1a2332;
    --slate-deep: #0d131d;
    --corrosion: #090a0f;
    --iron: #3a4756;
    --aether: #4ee0e6;
    --aether-glow: rgba(78, 224, 230, 0.5);
    --heart-red: #e63946;
    --bg-overlay: rgba(10, 14, 22, 0.88);
  }

  * {
    box-sizing: border-box;
    user-select: none;
    -webkit-user-select: none;
    margin: 0;
    padding: 0;
  }

  body, html {
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color: var(--corrosion);
    font-family: 'Cinzel', 'Palatino Linotype', 'Book Antiqua', Palatino, serif;
    color: #e2e8f0;
  }

  #game-container {
    position: relative;
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: radial-gradient(circle at center, #151d2a 0%, #080a0f 100%);
  }

  canvas#gameCanvas {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
  }

  /* HUD */
  #hud {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    padding: 16px 24px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    box-sizing: border-box;
    z-index: 10;
  }

  .hud-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    width: 100%;
  }

  .hud-stats {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }

  .hearts-container {
    display: flex;
    gap: 8px;
    align-items: center;
  }

  .heart-box {
    width: 28px;
    height: 28px;
    position: relative;
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.8));
    transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  }

  .heart-box.pulse {
    transform: scale(1.3);
  }

  .gold-display {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 18px;
    font-weight: bold;
    color: var(--gold);
    text-shadow: 0 0 10px var(--gold-glow), 0 2px 4px #000;
    background: rgba(13, 19, 29, 0.7);
    padding: 4px 12px;
    border-radius: 20px;
    border: 1px solid rgba(244, 197, 66, 0.3);
    width: fit-content;
  }

  .gold-icon {
    width: 18px;
    height: 18px;
    background: radial-gradient(circle, #ffe277 30%, #d49c12 80%);
    border-radius: 50%;
    display: inline-block;
    border: 1px solid #fff399;
    box-shadow: 0 0 6px var(--gold);
  }

  .zone-title-banner {
    text-align: right;
    pointer-events: none;
  }

  .zone-name {
    font-size: 20px;
    font-weight: 700;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: #e0e6ed;
    text-shadow: 0 2px 8px rgba(0,0,0,0.9), 0 0 12px rgba(244, 197, 66, 0.3);
  }

  .zone-sub {
    font-size: 12px;
    letter-spacing: 1px;
    color: #94a3b8;
    text-transform: uppercase;
  }

  /* Hotbar / Active Item HUD */
  .hud-bottom {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    width: 100%;
  }

  .quick-slots {
    display: flex;
    gap: 12px;
  }

  .slot-box {
    background: rgba(13, 19, 29, 0.85);
    border: 2px solid var(--iron);
    border-radius: 8px;
    padding: 6px 12px;
    display: flex;
    align-items: center;
    gap: 10px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.6);
    color: #f1f5f9;
  }

  .slot-box.active {
    border-color: var(--gold);
    box-shadow: 0 0 12px var(--gold-glow);
  }

  .slot-key {
    background: #202b3c;
    border: 1px solid #475569;
    border-radius: 4px;
    padding: 2px 6px;
    font-size: 11px;
    font-weight: bold;
    color: #cbd5e1;
  }

  .slot-label {
    font-size: 13px;
    font-weight: 600;
  }

  .slot-count {
    color: var(--gold);
    font-weight: bold;
  }

  /* Screen overlay flash */
  #screen-flash {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 8;
    transition: background 0.15s ease-out;
  }

  /* Vignette & Shimmer */
  #shimmer-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    box-shadow: inset 0 0 100px rgba(0,0,0,0.8);
    z-index: 5;
    transition: box-shadow 0.5s ease;
  }

  /* Notification Banner / Toast */
  #notification-toast {
    position: absolute;
    top: 80px;
    left: 50%;
    transform: translateX(-50%) translateY(-20px);
    background: rgba(13, 20, 32, 0.95);
    border: 2px solid var(--gold);
    padding: 12px 30px;
    border-radius: 8px;
    color: #fff;
    font-size: 16px;
    font-weight: bold;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    box-shadow: 0 0 25px var(--gold-glow), 0 8px 30px rgba(0,0,0,0.8);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease, transform 0.3s ease;
    z-index: 20;
    text-align: center;
  }

  #notification-toast.show {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }

  /* Modal Windows (Inventory, Shop, Dialogues, Start Screen, Death, Victory) */
  .modal-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: var(--bg-overlay);
    backdrop-filter: blur(6px);
    display: none;
    justify-content: center;
    align-items: center;
    z-index: 30;
    pointer-events: auto;
  }

  .modal-box {
    background: #0f1623;
    border: 2px solid #334155;
    border-radius: 12px;
    padding: 28px;
    width: 90%;
    max-width: 580px;
    box-shadow: 0 0 40px rgba(0,0,0,0.9), inset 0 0 20px rgba(244, 197, 66, 0.05);
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 18px;
  }

  .modal-title {
    font-size: 22px;
    font-weight: 700;
    color: var(--gold);
    letter-spacing: 2px;
    text-transform: uppercase;
    text-align: center;
    border-bottom: 1px solid rgba(244, 197, 66, 0.25);
    padding-bottom: 10px;
  }

  .btn {
    background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
    border: 1px solid var(--gold);
    color: #f8fafc;
    padding: 10px 20px;
    font-size: 15px;
    font-family: inherit;
    font-weight: 600;
    letter-spacing: 1px;
    cursor: pointer;
    border-radius: 6px;
    transition: all 0.2s ease;
    text-transform: uppercase;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
  }

  .btn:hover {
    background: var(--gold);
    color: #0b0f19;
    box-shadow: 0 0 15px var(--gold-glow);
    transform: translateY(-2px);
  }

  .btn:disabled {
    border-color: #475569;
    color: #64748b;
    background: #1e293b;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
  }

  /* Inventory & Shop Grid */
  .grid-list {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
    max-height: 360px;
    overflow-y: auto;
    padding-right: 6px;
  }

  .item-card {
    background: #182233;
    border: 1px solid #334155;
    border-radius: 8px;
    padding: 12px 16px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    transition: border-color 0.2s;
  }

  .item-card.equipped {
    border-color: var(--gold);
    background: #1e2c44;
  }

  .item-info {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }

  .item-name {
    font-weight: 700;
    font-size: 15px;
    color: #f1f5f9;
  }

  .item-desc {
    font-size: 12px;
    color: #94a3b8;
    line-height: 1.3;
  }

  .item-badge {
    font-size: 11px;
    padding: 2px 6px;
    border-radius: 4px;
    background: #334155;
    color: #cbd5e1;
    width: fit-content;
  }

  /* Interaction Prompt Prompt overlay in world */
  #interaction-prompt {
    position: absolute;
    bottom: 90px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(13, 20, 32, 0.9);
    border: 1px solid var(--gold);
    padding: 6px 16px;
    border-radius: 20px;
    font-size: 14px;
    font-weight: 600;
    color: #f8fafc;
    pointer-events: none;
    display: none;
    box-shadow: 0 0 15px var(--gold-glow);
    z-index: 15;
  }

  /* Touch controls for mobile/tablets */
  #touch-controls {
    position: absolute;
    bottom: 20px;
    left: 20px;
    right: 20px;
    display: none;
    justify-content: space-between;
    pointer-events: none;
    z-index: 25;
  }

  .touch-btn {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background: rgba(26, 35, 50, 0.7);
    border: 2px solid var(--gold);
    color: #fff;
    font-size: 14px;
    font-weight: bold;
    display: flex;
    justify-content: center;
    align-items: center;
    pointer-events: auto;
    touch-action: manipulation;
  }

  /* Lore/Dialogue Box */
  #dialogue-modal {
    position: absolute;
    bottom: 40px;
    left: 50%;
    transform: translateX(-50%);
    width: 90%;
    max-width: 680px;
    background: rgba(10, 15, 25, 0.95);
    border: 2px solid var(--gold);
    border-radius: 8px;
    padding: 20px 24px;
    color: #e2e8f0;
    z-index: 35;
    display: none;
    box-shadow: 0 0 30px rgba(0,0,0,0.9);
  }

  #dialogue-title {
    font-size: 16px;
    color: var(--gold);
    font-weight: bold;
    margin-bottom: 6px;
    letter-spacing: 1px;
    text-transform: uppercase;
  }

  #dialogue-body {
    font-size: 14px;
    line-height: 1.5;
    color: #cbd5e1;
    margin-bottom: 12px;
  }

  #dialogue-close-hint {
    font-size: 12px;
    color: #64748b;
    text-align: right;
  }
</style>
</head>
<body>

<div id="game-container">
  <canvas id="gameCanvas" width="960" height="540"></canvas>
  <div id="screen-flash"></div>
  <div id="shimmer-overlay"></div>

  <!-- HUD -->
  <div id="hud">
    <div class="hud-top">
      <div class="hud-stats">
        <div class="hearts-container" id="hearts-container"></div>
        <div class="gold-display">
          <span class="gold-icon"></span>
          <span id="gold-amount">0</span>
        </div>
      </div>
      <div class="zone-title-banner">
        <div class="zone-name" id="zone-name">The Ashen Threshold</div>
        <div class="zone-sub" id="zone-sub">Zone I — Outer Observatory</div>
      </div>
    </div>

    <div class="hud-bottom">
      <div class="quick-slots">
        <div class="slot-box" id="slot-weapon">
          <span class="slot-key">LMB / J</span>
          <span class="slot-label" id="weapon-name">Ember Blade</span>
        </div>
        <div class="slot-box" id="slot-ranged" style="display:none;">
          <span class="slot-key">RMB / K</span>
          <span class="slot-label">Aether Shard</span>
          <span class="slot-count" id="ammo-count">∞</span>
        </div>
        <div class="slot-box" id="slot-item1" style="cursor:pointer; pointer-events:auto;" onclick="game.useQuickItem('vitalStem')">
          <span class="slot-key">1</span>
          <span class="slot-label">Vital Stem: <b id="vital-stem-count">0</b></span>
        </div>
        <div class="slot-box" id="slot-item2" style="cursor:pointer; pointer-events:auto;" onclick="game.useQuickItem('bindingSeal')">
          <span class="slot-key">2</span>
          <span class="slot-label">Binding Seal: <b id="binding-seal-count">0</b></span>
        </div>
      </div>
      <div style="pointer-events: auto;">
        <button class="btn" style="padding: 6px 14px; font-size: 12px;" onclick="game.toggleInventory()">[I] Inventory</button>
      </div>
    </div>
  </div>

  <div id="interaction-prompt">[E] Interact</div>
  <div id="notification-toast"></div>

  <!-- Dialogue Modal -->
  <div id="dialogue-modal" onclick="game.closeDialogue()">
    <div id="dialogue-title">Altar of Ancient Knowledge</div>
    <div id="dialogue-body">Text goes here...</div>
    <div id="dialogue-close-hint">Press [E] or Click to dismiss</div>
  </div>

  <!-- Inventory Modal -->
  <div class="modal-overlay" id="inventory-modal">
    <div class="modal-box">
      <div class="modal-title">Warden's Satchel & Equipment</div>
      <div class="grid-list" id="inventory-list"></div>
      <div style="display:flex; justify-content:space-between; align-items:center;">
        <span style="font-size:12px; color:#94a3b8;">Click an accessory to equip/unequip, or consumable to use.</span>
        <button class="btn" onclick="game.toggleInventory()">Close</button>
      </div>
    </div>
  </div>

  <!-- Shop Modal -->
  <div class="modal-overlay" id="shop-modal">
    <div class="modal-box">
      <div class="modal-title">Observatory Altar Merchant</div>
      <div class="grid-list" id="shop-list"></div>
      <div style="display:flex; justify-content:space-between; align-items:center;">
        <div class="gold-display">
          <span class="gold-icon"></span>
          <span id="shop-gold-amount">0</span>
        </div>
        <button class="btn" onclick="game.closeShop()">Leave Altar</button>
      </div>
    </div>
  </div>

  <!-- Start / Title Screen -->
  <div class="modal-overlay" id="title-screen" style="display: flex;">
    <div class="modal-box" style="text-align: center; max-width: 640px;">
      <h1 style="color: var(--gold); font-size: 32px; letter-spacing: 4px; margin-bottom: 4px; text-shadow: 0 0 15px var(--gold-glow);">THE WARDEN'S CORROSION</h1>
      <p style="color: #94a3b8; font-size: 14px; margin-bottom: 20px; font-style: italic;">Aurelion's Sunken Observatory</p>
      
      <p style="font-size: 14px; line-height: 1.6; color: #cbd5e1; margin-bottom: 24px; text-align: left; background: rgba(0,0,0,0.3); padding: 14px; border-radius: 6px; border-left: 3px solid var(--gold);">
        The black-iron Corrosion eats through the floating celestial archive. As the <b>Last Warden</b>, you must delve through six crumbling zones, recover forgotten weapon altars, uncover the mid-game <b>Aether Shard</b>, expand your life essence, and reignite the <b>Core Beacon</b>.
      </p>

      <div style="display:grid; grid-template-columns: 1fr 1fr; gap: 12px; text-align: left; font-size: 13px; color: #94a3b8; margin-bottom: 24px;">
        <div><b>WASD / Arrows:</b> Move</div>
        <div><b>LMB / J / Space:</b> Attack</div>
        <div><b>RMB / K:</b> Fire Aether Shard</div>
        <div><b>E:</b> Interact / Save / Shop</div>
        <div><b>1 / 2:</b> Quick Consumable</div>
        <div><b>I:</b> Inventory / Equip Gear</div>
      </div>

      <button class="btn" style="font-size: 18px; padding: 12px 36px;" onclick="game.startGame()">Awaken Warden</button>
    </div>
  </div>

  <!-- Game Over Screen -->
  <div class="modal-overlay" id="game-over-screen">
    <div class="modal-box" style="text-align: center;">
      <div class="modal-title" style="color: var(--heart-red);">The Warden Has Fallen</div>
      <p style="color: #94a3b8; font-size: 14px; margin: 15px 0 25px;">The black corrosion has overcome your celestial essence. Return to your last activated Sanctuary Stone to rekindle the flame.</p>
      <button class="btn" onclick="game.respawnAtSanctuary()">Return to Sanctuary Stone</button>
    </div>
  </div>

  <!-- Victory Screen -->
  <div class="modal-overlay" id="victory-screen">
    <div class="modal-box" style="text-align: center; max-width: 620px;">
      <div class="modal-title" style="color: var(--solar); font-size: 28px;">Core Beacon Reignited!</div>
      <p style="color: #cbd5e1; font-size: 15px; line-height: 1.6; margin: 18px 0;">
        The golden fire surges through the bronze conduits of Aurelion's Sunken Observatory. The creeping iron corrosion shatters into stardust, and the ancient celestial gears turn once more in eternal balance.
      </p>
      <div id="victory-stats" style="background: rgba(0,0,0,0.4); padding: 14px; border-radius: 8px; margin-bottom: 20px; font-size: 14px; color: var(--gold);"></div>
      <button class="btn" onclick="game.restartGame()">Explore Anew</button>
    </div>
  </div>

  <!-- Touch Controls (Mobile Friendly) -->
  <div id="touch-controls">
    <div style="display:flex; gap:10px;">
      <div class="touch-btn" id="touch-left">←</div>
      <div class="touch-btn" id="touch-up">↑</div>
      <div class="touch-btn" id="touch-down">↓</div>
      <div class="touch-btn" id="touch-right">→</div>
    </div>
    <div style="display:flex; gap:10px;">
      <div class="touch-btn" id="touch-atk" style="background:rgba(230,92,0,0.6);">ATK</div>
      <div class="touch-btn" id="touch-ranged" style="background:rgba(78,224,230,0.6);">FIRE</div>
      <div class="touch-btn" id="touch-use" style="background:rgba(244,197,66,0.6);">USE</div>
    </div>
  </div>
</div>

<script>
/**
 * THE WARDEN'S CORROSION
 * Audio Synthesis Engine using Web Audio API
 */
class SoundEngine {
  constructor() {
    this.ctx = null;
    this.initialized = false;
  }

  init() {
    if (this.initialized) return;
    try {
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      this.ctx = new AudioContext();
      this.initialized = true;
    } catch (e) {
      console.warn("AudioContext not supported", e);
    }
  }

  // Soft stone / bronze footstep
  playFootstep(zoneType = 1) {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    
    // Zone acoustics
    let freq = 80 + Math.random() * 40;
    if (zoneType === 2) freq = 120 + Math.random() * 60; // metallic grate
    if (zoneType === 4) freq = 60 + Math.random() * 30; // damp sunken echo

    osc.type = zoneType === 2 ? 'triangle' : 'sine';
    osc.frequency.setValueAtTime(freq, t);
    osc.frequency.exponentialRampToValueAtTime(30, t + 0.08);

    gain.gain.setValueAtTime(0.04, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.08);

    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.08);
  }

  // Base Ember Blade attack
  playAttackBase() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    
    // Whoosh
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    osc.type = 'sine';
    osc.frequency.setValueAtTime(450, t);
    osc.frequency.exponentialRampToValueAtTime(120, t + 0.12);

    gain.gain.setValueAtTime(0.12, t);
    gain.gain.linearRampToValueAtTime(0.01, t + 0.12);

    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.12);
  }

  // Upgrade 1: Ignited Edge attack (flame crackle + deeper resonant thud)
  playAttackIgnited() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;

    const osc1 = this.ctx.createOscillator();
    const osc2 = this.ctx.createOscillator();
    const gain = this.ctx.createGain();

    osc1.type = 'sawtooth';
    osc1.frequency.setValueAtTime(380, t);
    osc1.frequency.exponentialRampToValueAtTime(90, t + 0.18);

    osc2.type = 'triangle';
    osc2.frequency.setValueAtTime(600, t);
    osc2.frequency.linearRampToValueAtTime(200, t + 0.15);

    gain.gain.setValueAtTime(0.16, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.2);

    osc1.connect(gain);
    osc2.connect(gain);
    gain.connect(this.ctx.destination);

    osc1.start(t);
    osc2.start(t);
    osc1.stop(t + 0.2);
    osc2.stop(t + 0.2);
  }

  // Upgrade 2: Solar Falchion (heavy wind-up + resonant clang + low bass rumble)
  playAttackSolar() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;

    const osc = this.ctx.createOscillator();
    const bass = this.ctx.createOscillator();
    const gain = this.ctx.createGain();

    osc.type = 'square';
    osc.frequency.setValueAtTime(260, t);
    osc.frequency.exponentialRampToValueAtTime(70, t + 0.28);

    bass.type = 'sine';
    bass.frequency.setValueAtTime(90, t);
    bass.frequency.exponentialRampToValueAtTime(35, t + 0.35);

    gain.gain.setValueAtTime(0.22, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.35);

    osc.connect(gain);
    bass.connect(gain);
    gain.connect(this.ctx.destination);

    osc.start(t);
    bass.start(t);
    osc.stop(t + 0.35);
    bass.stop(t + 0.35);
  }

  // Ranged Weapon: Aether Shard crystal twang + impact chime
  playAetherShoot() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();

    osc.type = 'sine';
    osc.frequency.setValueAtTime(1100, t);
    osc.frequency.exponentialRampToValueAtTime(520, t + 0.16);

    gain.gain.setValueAtTime(0.18, t);
    gain.gain.exponentialRampToValueAtTime(0.01, t + 0.16);

    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.16);
  }

  playAetherHit() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();

    osc.type = 'sine';
    osc.frequency.setValueAtTime(1600, t);
    osc.frequency.exponentialRampToValueAtTime(800, t + 0.25);

    gain.gain.setValueAtTime(0.15, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.25);

    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.25);
  }

  // Enemy hit impact
  playHitEnemy() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();

    osc.type = 'sawtooth';
    osc.frequency.setValueAtTime(180, t);
    osc.frequency.exponentialRampToValueAtTime(50, t + 0.12);

    gain.gain.setValueAtTime(0.16, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.12);

    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.12);
  }

  // Damage taken (dull body-thud + brief high-pitch dissonant chime)
  playDamagePlayer() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;

    const thud = this.ctx.createOscillator();
    const dissonance = this.ctx.createOscillator();
    const gain = this.ctx.createGain();

    thud.type = 'triangle';
    thud.frequency.setValueAtTime(120, t);
    thud.frequency.exponentialRampToValueAtTime(30, t + 0.2);

    dissonance.type = 'sawtooth';
    dissonance.frequency.setValueAtTime(870, t);
    dissonance.frequency.exponentialRampToValueAtTime(420, t + 0.25);

    gain.gain.setValueAtTime(0.25, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.25);

    thud.connect(gain);
    dissonance.connect(gain);
    gain.connect(this.ctx.destination);

    thud.start(t);
    dissonance.start(t);
    thud.stop(t + 0.25);
    dissonance.stop(t + 0.25);
  }

  // Gold / currency pickup (metallic clink)
  playGoldPickup() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();

    osc.type = 'sine';
    osc.frequency.setValueAtTime(987.77, t); // B5
    osc.frequency.setValueAtTime(1318.51, t + 0.05); // E6

    gain.gain.setValueAtTime(0.12, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.15);

    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.15);
  }

  // Item pickup (ascending melodic chime)
  playItemPickup() {
    if (!this.ctx) return;
    const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6
    notes.forEach((freq, idx) => {
      const t = this.ctx.currentTime + idx * 0.06;
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();

      osc.type = 'sine';
      osc.frequency.setValueAtTime(freq, t);

      gain.gain.setValueAtTime(0.15, t);
      gain.gain.exponentialRampToValueAtTime(0.001, t + 0.18);

      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start(t);
      osc.stop(t + 0.18);
    });
  }

  // Rare / Accessory or Heart Container (resonant chord)
  playMajorPickup() {
    if (!this.ctx) return;
    const freqs = [392.00, 493.88, 587.33, 783.99, 1174.66]; // G chord
    freqs.forEach((f, i) => {
      const t = this.ctx.currentTime + (i * 0.04);
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();

      osc.type = 'triangle';
      osc.frequency.setValueAtTime(f, t);

      gain.gain.setValueAtTime(0.15, t);
      gain.gain.exponentialRampToValueAtTime(0.001, t + 0.8);

      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start(t);
      osc.stop(t + 0.8);
    });
  }

  // Sanctuary Stone save chord
  playSaveSound() {
    if (!this.ctx) return;
    const freqs = [220, 277.18, 329.63, 440, 554.37];
    freqs.forEach(f => {
      const t = this.ctx.currentTime;
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();

      osc.type = 'sine';
      osc.frequency.setValueAtTime(f, t);

      gain.gain.setValueAtTime(0.12, t);
      gain.gain.exponentialRampToValueAtTime(0.001, t + 1.2);

      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start(t);
      osc.stop(t + 1.2);
    });
  }

  // Shop purchase confirmation chime
  playShopPurchase() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    [659.25, 880, 1318.51].forEach((freq, idx) => {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = 'sine';
      osc.frequency.setValueAtTime(freq, t + idx * 0.06);

      gain.gain.setValueAtTime(0.14, t + idx * 0.06);
      gain.gain.exponentialRampToValueAtTime(0.001, t + idx * 0.06 + 0.3);

      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start(t + idx * 0.06);
      osc.stop(t + idx * 0.06 + 0.3);
    });
  }

  // Switch / Pressure plate click
  playSwitch() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    osc.type = 'triangle';
    osc.frequency.setValueAtTime(300, t);
    osc.frequency.exponentialRampToValueAtTime(100, t + 0.1);
    gain.gain.setValueAtTime(0.2, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.1);
    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.1);
  }

  // Break wall sound
  playBreakWall() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    osc.type = 'sawtooth';
    osc.frequency.setValueAtTime(140, t);
    osc.frequency.exponentialRampToValueAtTime(30, t + 0.3);
    gain.gain.setValueAtTime(0.3, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.3);
  }

  // Enemy Freeze sound (Binding Seal)
  playFreeze() {
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    osc.type = 'sine';
    osc.frequency.setValueAtTime(800, t);
    osc.frequency.exponentialRampToValueAtTime(1800, t + 0.25);
    gain.gain.setValueAtTime(0.18, t);
    gain.gain.exponentialRampToValueAtTime(0.001, t + 0.35);
    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(t);
    osc.stop(t + 0.35);
  }
}
</script>

<script>
/**
 * DATA & STATE DEFINITIONS
 */
const ZONES = [
  {
    id: 1,
    name: "The Ashen Threshold",
    sub: "Zone I — Outer Observatory Gate",
    bgFloor: "#1a2332",
    bgWall: "#0f1622",
    accent: "#3a4756",
    width: 1600,
    height: 900
  },
  {
    id: 2,
    name: "The Rusted Archives",
    sub: "Zone II — Mechanical Library & Altar Market",
    bgFloor: "#24201c",
    bgWall: "#151210",
    accent: "#6b4c30",
    width: 1800,
    height: 1000
  },
  {
    id: 3,
    name: "The Collapsed Gallery",
    sub: "Zone III — Weapon Altar & Plate Sanctum",
    bgFloor: "#2b1e1a",
    bgWall: "#170f0d",
    accent: "#8c3b1e",
    width: 2000,
    height: 1000
  },
  {
    id: 4,
    name: "The Sunken Vault",
    sub: "Zone IV — Crystalline Depths (Mid-Game Turning Point)",
    bgFloor: "#12252a",
    bgWall: "#0a1417",
    accent: "#1f5f6b",
    width: 2000,
    height: 1100
  },
  {
    id: 5,
    name: "The Twin Spire",
    sub: "Zone V — Ascent of the Sun & Solar Altar",
    bgFloor: "#2b2518",
    bgWall: "#17140a",
    accent: "#8c7224",
    width: 2200,
    height: 1100
  },
  {
    id: 6,
    name: "The Core Chamber",
    sub: "Zone VI — Aurelion's Heart & Beacon Climax",
    bgFloor: "#1a1e2b",
    bgWall: "#0d101a",
    accent: "#433a75",
    width: 1800,
    height: 1200
  }
];

class Particle {
  constructor(x, y, vx, vy, color, size, life, shape = 'circle', gravity = 0) {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.color = color;
    this.size = size;
    this.maxLife = life;
    this.life = life;
    this.shape = shape;
    this.gravity = gravity;
  }

  update(dt) {
    this.x += this.vx * dt;
    this.y += this.vy * dt;
    this.vy += this.gravity * dt;
    this.life -= dt;
  }

  draw(ctx) {
    const alpha = Math.max(0, this.life / this.maxLife);
    ctx.save();
    ctx.globalAlpha = alpha;
    ctx.fillStyle = this.color;
    ctx.strokeStyle = this.color;

    if (this.shape === 'spark') {
      ctx.lineWidth = this.size;
      ctx.beginPath();
      ctx.moveTo(this.x, this.y);
      ctx.lineTo(this.x - this.vx * 0.08, this.y - this.vy * 0.08);
      ctx.stroke();
    } else if (this.shape === 'ember') {
      ctx.shadowBlur = 8;
      ctx.shadowColor = this.color;
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
      ctx.fill();
    } else {
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.restore();
  }
}

class Projectile {
  constructor(x, y, vx, vy, isPlayer, damage, color = '#4ee0e6') {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.isPlayer = isPlayer;
    this.damage = damage;
    this.color = color;
    this.radius = 7;
    this.life = 2.5; // seconds
    this.trailTimer = 0;
  }

  update(dt, particles) {
    this.x += this.vx * dt;
    this.y += this.vy * dt;
    this.life -= dt;

    this.trailTimer += dt;
    if (this.trailTimer > 0.03) {
      this.trailTimer = 0;
      particles.push(new Particle(
        this.x + (Math.random() - 0.5) * 4,
        this.y + (Math.random() - 0.5) * 4,
        (Math.random() - 0.5) * 15,
        (Math.random() - 0.5) * 15,
        this.color,
        3,
        0.3,
        'ember'
      ));
    }
  }

  draw(ctx) {
    ctx.save();
    ctx.shadowBlur = 12;
    ctx.shadowColor = this.color;
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fill();

    ctx.fillStyle = this.color;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius * 0.65, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }
}

class FloatingText {
  constructor(x, y, text, color = '#f4c542', size = 16) {
    this.x = x;
    this.y = y;
    this.text = text;
    this.color = color;
    this.size = size;
    this.life = 0.9;
    this.maxLife = 0.9;
  }

  update(dt) {
    this.y -= 28 * dt;
    this.life -= dt;
  }

  draw(ctx) {
    const alpha = Math.max(0, this.life / this.maxLife);
    ctx.save();
    ctx.globalAlpha = alpha;
    ctx.font = `bold ${this.size}px 'Cinzel', serif`;
    ctx.fillStyle = this.color;
    ctx.shadowColor = '#000';
    ctx.shadowBlur = 4;
    ctx.textAlign = 'center';
    ctx.fillText(this.text, this.x, this.y);
    ctx.restore();
  }
}
</script>

<script>
/**
 * ENTITIES & WORLD OBJECTS
 */

class CorruptedConstruct {
  constructor(x, y, type = 'sentinel') {
    this.x = x;
    this.y = y;
    this.type = type; // 'crawler', 'sentinel', 'knight', 'boss'
    this.radius = 18;
    this.vx = 0;
    this.vy = 0;
    this.angle = 0;
    this.state = 'idle'; // idle, chase, attack, hurt, frozen
    this.freezeTimer = 0;
    this.hitTimer = 0;
    this.attackCooldown = 0;
    this.isDead = false;

    // Type specific stats
    if (type === 'crawler') {
      this.maxHp = 4;
      this.hp = 4;
      this.speed = 110;
      this.damage = 1;
      this.goldDrop = [6, 12];
      this.color = '#7d8a99';
      this.coreColor = '#e65c00';
      this.radius = 14;
    } else if (type === 'sentinel') {
      this.maxHp = 8;
      this.hp = 8;
      this.speed = 80;
      this.damage = 2;
      this.goldDrop = [10, 18];
      this.color = '#475569';
      this.coreColor = '#f43f5e';
      this.radius = 18;
    } else if (type === 'knight') {
      this.maxHp = 16;
      this.hp = 16;
      this.speed = 65;
      this.damage = 2;
      this.goldDrop = [18, 30];
      this.color = '#334155';
      this.coreColor = '#8b5cf6';
      this.radius = 24;
    } else if (type === 'boss') {
      this.maxHp = 65;
      this.hp = 65;
      this.speed = 55;
      this.damage = 2;
      this.goldDrop = [60, 100];
      this.color = '#1e293b';
      this.coreColor = '#ec4899';
      this.radius = 36;
    }
  }

  update(dt, player, walls, soundEngine, particles, floatingTexts) {
    if (this.isDead) return;

    if (this.freezeTimer > 0) {
      this.freezeTimer -= dt;
      return;
    }

    if (this.hitTimer > 0) {
      this.hitTimer -= dt;
    }

    if (this.attackCooldown > 0) {
      this.attackCooldown -= dt;
    }

    const distToPlayer = Math.hypot(player.x - this.x, player.y - this.y);
    const angleToPlayer = Math.atan2(player.y - this.y, player.x - this.x);
    this.angle = angleToPlayer;

    // AI logic
    const aggroRange = this.type === 'boss' ? 700 : 380;
    if (distToPlayer < aggroRange && !player.isDead) {
      this.state = 'chase';
      
      // Move towards player
      let targetVx = Math.cos(angleToPlayer) * this.speed;
      let targetVy = Math.sin(angleToPlayer) * this.speed;

      let newX = this.x + targetVx * dt;
      let newY = this.y + targetVy * dt;

      // Check wall collision
      let canMoveX = true;
      let canMoveY = true;
      for (const w of walls) {
        if (w.broken) continue;
        if (circleRectCollide(newX, this.y, this.radius, w.x, w.y, w.w, w.h)) canMoveX = false;
        if (circleRectCollide(this.x, newY, this.radius, w.x, w.y, w.w, w.h)) canMoveY = false;
      }

      if (canMoveX) this.x = newX;
      if (canMoveY) this.y = newY;

      // Melee attack contact
      if (distToPlayer < this.radius + player.radius + 6) {
        if (this.attackCooldown <= 0) {
          player.takeDamage(this.damage, soundEngine, particles, floatingTexts);
          this.attackCooldown = 1.0;
        }
      }

      // Boss special projectile attack
      if (this.type === 'boss' && distToPlayer > 120 && this.attackCooldown <= 0) {
        if (Math.random() < 0.03) {
          this.attackCooldown = 2.0;
          // Spawn 3 spreading corrupted bolts
          for (let offset of [-0.25, 0, 0.25]) {
            const bAngle = angleToPlayer + offset;
            game.projectiles.push(new Projectile(
              this.x + Math.cos(bAngle) * 30,
              this.y + Math.sin(bAngle) * 30,
              Math.cos(bAngle) * 220,
              Math.sin(bAngle) * 220,
              false,
              2,
              '#e11d48'
            ));
          }
        }
      }
    } else {
      this.state = 'idle';
    }
  }

  takeDamage(dmg, soundEngine, particles, floatingTexts, damageType = 'physical') {
    if (this.isDead) return;
    this.hp -= dmg;
    this.hitTimer = 0.15;

    soundEngine.playHitEnemy();

    // Floating text
    let txtColor = '#f8fafc';
    if (damageType === 'ember') txtColor = '#fb923c';
    if (damageType === 'solar') txtColor = '#fde047';
    if (damageType === 'aether') txtColor = '#38bdf8';

    floatingTexts.push(new FloatingText(this.x, this.y - 20, `-${dmg}`, txtColor, 18));

    // Particle effect
    const sparkColor = damageType === 'ember' ? '#ff6600' : (damageType === 'solar' ? '#ffe066' : '#94a3b8');
    for (let i = 0; i < 6; i++) {
      particles.push(new Particle(
        this.x,
        this.y,
        (Math.random() - 0.5) * 120,
        (Math.random() - 0.5) * 120,
        sparkColor,
        2.5,
        0.3,
        'spark'
      ));
    }

    if (this.hp <= 0) {
      this.isDead = true;
      // Death explosion particles
      for (let i = 0; i < 14; i++) {
        particles.push(new Particle(
          this.x,
          this.y,
          (Math.random() - 0.5) * 160,
          (Math.random() - 0.5) * 160,
          this.coreColor,
          3.5,
          0.5,
          'ember',
          30
        ));
      }

      // Drop gold
      const goldAmt = Math.floor(this.goldDrop[0] + Math.random() * (this.goldDrop[1] - this.goldDrop[0] + 1));
      game.spawnGoldLoot(this.x, this.y, goldAmt);
    }
  }

  draw(ctx) {
    if (this.isDead) return;

    ctx.save();
    ctx.translate(this.x, this.y);
    ctx.rotate(this.angle);

    // Flash white/red when hit
    if (this.hitTimer > 0) {
      ctx.fillStyle = '#ffffff';
      ctx.beginPath();
      ctx.arc(0, 0, this.radius + 3, 0, Math.PI * 2);
      ctx.fill();
    }

    // Frozen visual (blue ice overlay)
    if (this.freezeTimer > 0) {
      ctx.strokeStyle = '#38bdf8';
      ctx.lineWidth = 3;
      ctx.shadowBlur = 10;
      ctx.shadowColor = '#38bdf8';
      ctx.beginPath();
      ctx.arc(0, 0, this.radius + 6, 0, Math.PI * 2);
      ctx.stroke();
    }

    // Construct Body (Rusted armor shape)
    ctx.fillStyle = this.color;
    ctx.strokeStyle = '#1e293b';
    ctx.lineWidth = 2;

    if (this.type === 'crawler') {
      // Spider / mechanical bug shape
      ctx.beginPath();
      ctx.ellipse(0, 0, this.radius, this.radius * 0.7, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.stroke();

      // Mandibles / spikes
      ctx.fillStyle = '#94a3b8';
      ctx.fillRect(8, -6, 8, 3);
      ctx.fillRect(8, 3, 8, 3);
    } else if (this.type === 'boss') {
      // Large mechanical core with orbital bronze gear teeth
      ctx.beginPath();
      ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
      ctx.fill();
      ctx.stroke();

      // Gear teeth outer
      ctx.fillStyle = '#d97706';
      for (let i = 0; i < 8; i++) {
        const a = (i / 8) * Math.PI * 2;
        ctx.fillRect(Math.cos(a) * 32 - 4, Math.sin(a) * 32 - 4, 8, 8);
      }
    } else {
      // Sentinel / Knight armor plate
      ctx.beginPath();
      ctx.rect(-this.radius, -this.radius, this.radius * 2, this.radius * 2);
      ctx.fill();
      ctx.stroke();

      // Pauldrons
      ctx.fillStyle = '#64748b';
      ctx.fillRect(-this.radius - 3, -this.radius * 0.8, 4, this.radius * 1.6);
    }

    // Corrupted glowing core eye
    ctx.shadowBlur = 12;
    ctx.shadowColor = this.coreColor;
    ctx.fillStyle = this.coreColor;
    ctx.beginPath();
    ctx.arc(this.type === 'boss' ? 0 : 4, 0, this.radius * 0.4, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();

    // Health Bar overhead for larger enemies / boss
    if (this.hp < this.maxHp) {
      const barW = this.radius * 2;
      const barH = 5;
      const barX = this.x - barW / 2;
      const barY = this.y - this.radius - 12;

      ctx.fillStyle = 'rgba(0,0,0,0.7)';
      ctx.fillRect(barX, barY, barW, barH);
      ctx.fillStyle = this.type === 'boss' ? '#ec4899' : '#e63946';
      ctx.fillRect(barX, barY, barW * (Math.max(0, this.hp) / this.maxHp), barH);
      ctx.strokeStyle = '#000';
      ctx.strokeRect(barX, barY, barW, barH);
    }
  }
}
</script>

<script>
/**
 * PLAYER CLASS & VISUAL PROGRESSION
 */
class Player {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.radius = 16;
    this.vx = 0;
    this.vy = 0;
    this.baseSpeed = 190;
    this.speed = 190;
    this.angle = 0;
    this.isDead = false;

    // Health Progression: starts at 3 containers (6 HP total), up to 5 containers (10 HP max)
    this.maxContainers = 3; 
    this.hp = 6; // each container is 2 HP
    this.invulnerableTimer = 0;
    this.attackTimer = 0;
    this.attackCooldown = 0.28; // base
    this.isAttacking = false;
    this.slashArc = 0;

    // Weapon & Upgrade State:
    // weaponLevel: 0 = Ember Blade (base), 1 = Ignited Edge (Zone 3), 2 = Solar Falchion (Zone 5)
    this.weaponLevel = 0;
    this.hasAetherShard = false; // Zone 4 discovery
    this.aetherCooldown = 0;

    // Inventory Accessories equipped:
    this.equipped = {
      wardensBand: false,  // wrist ring, +attack speed
      phantomLantern: false, // blue aura, reveals secrets
      shadowCloak: false   // cloak hem, reduces trap damage
    };

    // Consumable counts
    this.vitalStems = 1;
    this.bindingSeals = 1;

    // Footstep timer
    this.footstepTimer = 0;
    this.emberTrailTimer = 0;
  }

  get maxHp() {
    return this.maxContainers * 2;
  }

  update(dt, keys, mouseWorldPos, walls, soundEngine, particles, floatingTexts, zoneId) {
    if (this.isDead) return;

    if (this.invulnerableTimer > 0) {
      this.invulnerableTimer -= dt;
    }

    if (this.aetherCooldown > 0) {
      this.aetherCooldown -= dt;
    }

    // Determine target speed (Warden's Band slight speed buff)
    this.speed = this.baseSpeed;
    if (this.equipped.wardensBand) {
      this.attackCooldown = this.weaponLevel === 2 ? 0.28 : (this.weaponLevel === 1 ? 0.20 : 0.24);
    } else {
      this.attackCooldown = this.weaponLevel === 2 ? 0.36 : (this.weaponLevel === 1 ? 0.25 : 0.30);
    }

    // Input Movement
    let dx = 0;
    let dy = 0;
    if (keys['KeyW'] || keys['ArrowUp']) dy -= 1;
    if (keys['KeyS'] || keys['ArrowDown']) dy += 1;
    if (keys['KeyA'] || keys['ArrowLeft']) dx -= 1;
    if (keys['KeyD'] || keys['ArrowRight']) dx += 1;

    if (dx !== 0 && dy !== 0) {
      const invLen = 1 / Math.SQRT2;
      dx *= invLen;
      dy *= invLen;
    }

    const moveX = dx * this.speed * dt;
    const moveY = dy * this.speed * dt;

    let targetX = this.x + moveX;
    let targetY = this.y + moveY;

    // Wall collision checking
    let canMoveX = true;
    let canMoveY = true;
    for (const w of walls) {
      if (w.broken) continue;
      if (circleRectCollide(targetX, this.y, this.radius, w.x, w.y, w.w, w.h)) canMoveX = false;
      if (circleRectCollide(this.x, targetY, this.radius, w.x, w.y, w.w, w.h)) canMoveY = false;
    }

    if (canMoveX) this.x = targetX;
    if (canMoveY) this.y = targetY;

    // Aim angle towards mouse
    this.angle = Math.atan2(mouseWorldPos.y - this.y, mouseWorldPos.x - this.x);

    // Footsteps
    if (dx !== 0 || dy !== 0) {
      this.footstepTimer += dt;
      if (this.footstepTimer > 0.32) {
        this.footstepTimer = 0;
        soundEngine.playFootstep(zoneId);
      }

      // Ember trail for Upgrade 1 (Ignited Edge) and Upgrade 2 (Solar Falchion)
      if (this.weaponLevel >= 1) {
        this.emberTrailTimer += dt;
        if (this.emberTrailTimer > 0.08) {
          this.emberTrailTimer = 0;
          const trailColor = this.weaponLevel === 2 ? '#fde047' : '#ea580c';
          particles.push(new Particle(
            this.x + (Math.random() - 0.5) * 10,
            this.y + (Math.random() - 0.5) * 10,
            (Math.random() - 0.5) * 10,
            (Math.random() - 0.5) * 10,
            trailColor,
            Math.random() * 2.5 + 1,
            0.4,
            'ember'
          ));
        }
      }
    }

    // Attack swing animation update
    if (this.isAttacking) {
      this.attackTimer += dt;
      if (this.attackTimer >= this.attackCooldown) {
        this.isAttacking = false;
      }
    }
  }

  performMeleeAttack(soundEngine, particles, enemies, breakables) {
    if (this.isAttacking) return;
    this.isAttacking = true;
    this.attackTimer = 0;

    let damage = 2; // base
    let range = 52;
    let arc = Math.PI * 0.55; // base arc
    let dmgType = 'physical';

    if (this.weaponLevel === 1) {
      // Ignited Edge (Zone 3): Fire / Ember damage, faster attack
      damage = 3;
      range = 58;
      dmgType = 'ember';
      soundEngine.playAttackIgnited();
    } else if (this.weaponLevel === 2) {
      // Solar Falchion (Zone 5): Light / Solar damage, wider arc sweep & screen shimmer
      damage = 5;
      range = 72;
      arc = Math.PI * 0.85;
      dmgType = 'solar';
      soundEngine.playAttackSolar();
      game.triggerShimmer();
    } else {
      soundEngine.playAttackBase();
    }

    // Attack slash arc particles
    const steps = 10;
    for (let i = 0; i < steps; i++) {
      const a = this.angle - arc / 2 + (arc * (i / steps));
      const px = this.x + Math.cos(a) * (range * 0.85);
      const py = this.y + Math.sin(a) * (range * 0.85);
      const slashColor = this.weaponLevel === 2 ? '#fef08a' : (this.weaponLevel === 1 ? '#ff7700' : '#e2e8f0');
      particles.push(new Particle(
        px,
        py,
        Math.cos(a) * 40,
        Math.sin(a) * 40,
        slashColor,
        this.weaponLevel === 2 ? 4 : 2.5,
        0.2,
        'spark'
      ));
    }

    // Check hit on Corrupted Constructs
    for (const enemy of enemies) {
      if (enemy.isDead) continue;
      const d = Math.hypot(enemy.x - this.x, enemy.y - this.y);
      if (d <= range + enemy.radius) {
        const enemyAngle = Math.atan2(enemy.y - this.y, enemy.x - this.x);
        let diff = Math.abs(normalizeAngle(enemyAngle - this.angle));
        if (diff <= arc / 2 + 0.3) {
          enemy.takeDamage(damage, soundEngine, particles, game.floatingTexts, dmgType);
        }
      }
    }

    // Check hit on Breakable Walls / Caches
    for (const b of breakables) {
      if (b.broken) continue;
      // Solar Falchion breaks walls in 1 hit, base blade in 2-3 hits
      const d = Math.hypot((b.x + b.w / 2) - this.x, (b.y + b.h / 2) - this.y);
      if (d <= range + 35) {
        const breakDmg = this.weaponLevel === 2 ? 3 : (this.weaponLevel === 1 ? 2 : 1);
        b.hit(breakDmg, soundEngine, particles);
      }
    }
  }

  performRangedAttack(mouseWorldPos, soundEngine, particles) {
    if (!this.hasAetherShard) return;
    if (this.aetherCooldown > 0) return;

    this.aetherCooldown = 0.35;
    soundEngine.playAetherShoot();

    const shootAngle = Math.atan2(mouseWorldPos.y - this.y, mouseWorldPos.x - this.x);
    const speed = 440;

    game.projectiles.push(new Projectile(
      this.x + Math.cos(shootAngle) * 20,
      this.y + Math.sin(shootAngle) * 20,
      Math.cos(shootAngle) * speed,
      Math.sin(shootAngle) * speed,
      true,
      3,
      '#4ee0e6'
    ));

    // Recoil spark
    for (let i = 0; i < 4; i++) {
      particles.push(new Particle(
        this.x + Math.cos(shootAngle) * 16,
        this.y + Math.sin(shootAngle) * 16,
        (Math.random() - 0.5) * 50,
        (Math.random() - 0.5) * 50,
        '#4ee0e6',
        2.5,
        0.2,
        'spark'
      ));
    }
  }

  takeDamage(dmg, soundEngine, particles, floatingTexts) {
    if (this.isDead || this.invulnerableTimer > 0) return;

    // Shadow cloak mitigates 1 damage if hit
    if (this.equipped.shadowCloak && dmg > 1 && Math.random() < 0.4) {
      dmg -= 1;
      floatingTexts.push(new FloatingText(this.x, this.y - 34, "SHADOW WARD", '#a855f7', 12));
    }

    this.hp -= dmg;
    this.invulnerableTimer = 0.7; // I-frames

    soundEngine.playDamagePlayer();
    game.triggerScreenFlash('rgba(230, 57, 70, 0.45)');

    floatingTexts.push(new FloatingText(this.x, this.y - 25, `-${dmg} HP`, '#ef4444', 20));

    // Blood / Aether burst
    for (let i = 0; i < 10; i++) {
      particles.push(new Particle(
        this.x,
        this.y,
        (Math.random() - 0.5) * 120,
        (Math.random() - 0.5) * 120,
        '#ef4444',
        3,
        0.4,
        'ember'
      ));
    }

    game.updateHUD();

    if (this.hp <= 0) {
      this.hp = 0;
      this.isDead = true;
      game.onPlayerDeath();
    }
  }

  heal(amount, particles, floatingTexts) {
    const oldHp = this.hp;
    this.hp = Math.min(this.maxHp, this.hp + amount);
    const gained = this.hp - oldHp;

    if (gained > 0) {
      floatingTexts.push(new FloatingText(this.x, this.y - 25, `+${gained} HP`, '#22c55e', 20));
      for (let i = 0; i < 12; i++) {
        particles.push(new Particle(
          this.x + (Math.random() - 0.5) * 20,
          this.y + (Math.random() - 0.5) * 20,
          (Math.random() - 0.5) * 30,
          -40 - Math.random() * 30,
          '#4ade80',
          3,
          0.6,
          'ember'
        ));
      }
    }
    game.updateHUD();
  }

  draw(ctx) {
    if (this.isDead) return;

    ctx.save();
    ctx.translate(this.x, this.y);

    // I-frame blinking
    if (this.invulnerableTimer > 0 && Math.floor(Date.now() / 60) % 2 === 0) {
      ctx.globalAlpha = 0.4;
    }

    // Phantom Lantern light halo if equipped
    if (this.equipped.phantomLantern) {
      ctx.save();
      const grad = ctx.createRadialGradient(0, 0, 10, 0, 0, 140);
      grad.addColorStop(0, 'rgba(78, 224, 230, 0.25)');
      grad.addColorStop(1, 'rgba(78, 224, 230, 0)');
      ctx.fillStyle = grad;
      ctx.beginPath();
      ctx.arc(0, 0, 140, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }

    ctx.rotate(this.angle);

    // 1. Cloak / Armor Visuals
    // Base cloak: Deep slate blue #1e293b
    // Shadow Cloak token: Dark cloth trim
    ctx.fillStyle = this.equipped.shadowCloak ? '#0b0f19' : '#1e293b';
    ctx.strokeStyle = '#0f172a';
    ctx.lineWidth = 2;

    // Cloak base body
    ctx.beginPath();
    ctx.ellipse(0, 0, this.radius, this.radius * 0.85, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();

    if (this.equipped.shadowCloak) {
      // Dark cloth trim outline
      ctx.strokeStyle = '#8b5cf6';
      ctx.lineWidth = 1.5;
      ctx.stroke();
    }

    // Cloak shimmering hem for Upgrade 2 (Solar Falchion)
    if (this.weaponLevel === 2) {
      ctx.strokeStyle = '#fde047';
      ctx.lineWidth = 2;
      ctx.shadowBlur = 8;
      ctx.shadowColor = '#fde047';
      ctx.beginPath();
      ctx.arc(0, 0, this.radius + 1, 0, Math.PI * 2);
      ctx.stroke();
      ctx.shadowBlur = 0;
    }

    // 2. Head / Hood
    ctx.fillStyle = '#334155';
    ctx.beginPath();
    ctx.arc(0, 0, this.radius * 0.55, 0, Math.PI * 2);
    ctx.fill();

    // Golden warden crest on forehead
    ctx.fillStyle = '#f4c542';
    ctx.fillRect(2, -2, 5, 4);

    // 3. Off-hand / Dual-ready stance if Aether Shard acquired
    if (this.hasAetherShard) {
      // Off-hand crystal orb
      ctx.save();
      ctx.translate(6, -14);
      ctx.fillStyle = '#4ee0e6';
      ctx.shadowBlur = 10;
      ctx.shadowColor = '#4ee0e6';
      ctx.beginPath();
      ctx.arc(0, 0, 4.5, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }

    // 4. Warden's Band accessory token on right wrist
    if (this.equipped.wardensBand) {
      ctx.fillStyle = '#e2e8f0';
      ctx.shadowBlur = 6;
      ctx.shadowColor = '#ffffff';
      ctx.beginPath();
      ctx.arc(6, 12, 3, 0, Math.PI * 2);
      ctx.fill();
      ctx.shadowBlur = 0;
    }

    // 5. Main Hand Weapon (Ember Blade / Ignited Edge / Solar Falchion)
    ctx.save();
    ctx.translate(8, 10); // Main hand position

    // If attacking, rotate weapon swing
    if (this.isAttacking) {
      const progress = this.attackTimer / this.attackCooldown;
      ctx.rotate(-0.8 + progress * 2.2);
    }

    if (this.weaponLevel === 0) {
      // Base Ember Blade: Steel blade with bronze hilt
      ctx.fillStyle = '#94a3b8';
      ctx.fillRect(0, -2, 22, 4);
      ctx.fillStyle = '#d97706';
      ctx.fillRect(-3, -4, 4, 8);
    } else if (this.weaponLevel === 1) {
      // Upgrade 1: Ignited Edge (glowing orange flame blade)
      ctx.fillStyle = '#ea580c';
      ctx.shadowBlur = 12;
      ctx.shadowColor = '#f97316';
      ctx.fillRect(0, -2.5, 26, 5);
      // Flame tip
      ctx.fillStyle = '#fde047';
      ctx.fillRect(22, -1.5, 6, 3);
      // Bronze hilt
      ctx.fillStyle = '#b45309';
      ctx.fillRect(-3, -5, 4, 10);
    } else if (this.weaponLevel === 2) {
      // Upgrade 2: Solar Falchion (wider golden curved blade + intense solar halo)
      ctx.fillStyle = '#facc15';
      ctx.shadowBlur = 18;
      ctx.shadowColor = '#fef08a';
      ctx.beginPath();
      ctx.moveTo(0, -4);
      ctx.lineTo(28, -6);
      ctx.lineTo(36, 0);
      ctx.lineTo(28, 6);
      ctx.lineTo(0, 4);
      ctx.closePath();
      ctx.fill();
      // Solar core inlay
      ctx.fillStyle = '#ffffff';
      ctx.fillRect(4, -1, 20, 2);
      // Gold ornate guard
      ctx.fillStyle = '#eab308';
      ctx.fillRect(-4, -6, 5, 12);
    }

    ctx.restore();
    ctx.restore();
  }
}
</script>

<script>
/**
 * ENVIRONMENT ELEMENTS: Breakables, Sanctuary Stones, Pressure Plates, Ranged Switches, Gates, Altars
 */

class Wall {
  constructor(x, y, w, h, isCrack = false, hp = 3, dropsItem = null) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.isCrack = isCrack; // hairline cracks breakable wall
    this.hp = hp;
    this.maxHp = hp;
    this.broken = false;
    this.dropsItem = dropsItem;
  }

  hit(dmg, soundEngine, particles) {
    if (!this.isCrack || this.broken) return;
    this.hp -= dmg;
    soundEngine.playBreakWall();

    for (let i = 0; i < 8; i++) {
      particles.push(new Particle(
        this.x + this.w / 2 + (Math.random() - 0.5) * this.w,
        this.y + this.h / 2 + (Math.random() - 0.5) * this.h,
        (Math.random() - 0.5) * 80,
        (Math.random() - 0.5) * 80,
        '#64748b',
        3,
        0.3,
        'spark'
      ));
    }

    if (this.hp <= 0) {
      this.broken = true;
      soundEngine.playBreakWall();
      // Wall collapse burst
      for (let i = 0; i < 18; i++) {
        particles.push(new Particle(
          this.x + this.w / 2,
          this.y + this.h / 2,
          (Math.random() - 0.5) * 160,
          (Math.random() - 0.5) * 160,
          '#475569',
          4,
          0.6,
          'ember',
          50
        ));
      }

      // Drop item or container if configured
      if (this.dropsItem) {
        if (this.dropsItem === 'container4') {
          game.spawnHealthContainer(this.x + this.w / 2, this.y + this.h / 2, 4);
        } else if (this.dropsItem === 'aetherShard') {
          game.spawnWorldItem(this.x + this.w / 2, this.y + this.h / 2, 'aetherShard', "Aether Shard", "Ancient crystalline ranged projectile focus!");
        } else if (this.dropsItem === 'wardensBand') {
          game.spawnWorldItem(this.x + this.w / 2, this.y + this.h / 2, 'wardensBand', "Warden's Band", "Accessory: Boosts melee attack speed!");
        }
      }
    }
  }

  draw(ctx, phantomLanternActive) {
    if (this.broken) return;

    if (this.isCrack) {
      // Breakable wall with hairline cracks
      ctx.fillStyle = '#1e293b';
      ctx.fillRect(this.x, this.y, this.w, this.h);

      // Crack lines
      ctx.strokeStyle = (phantomLanternActive ? '#38bdf8' : '#64748b');
      ctx.lineWidth = phantomLanternActive ? 2 : 1.5;
      if (phantomLanternActive) {
        ctx.shadowBlur = 8;
        ctx.shadowColor = '#38bdf8';
      }

      ctx.beginPath();
      ctx.moveTo(this.x + 4, this.y + 4);
      ctx.lineTo(this.x + this.w * 0.4, this.y + this.h * 0.6);
      ctx.lineTo(this.x + this.w * 0.7, this.y + this.h * 0.3);
      ctx.lineTo(this.x + this.w - 4, this.y + this.h - 4);
      ctx.stroke();

      ctx.shadowBlur = 0;
      ctx.strokeStyle = '#0f172a';
      ctx.strokeRect(this.x, this.y, this.w, this.h);
    } else {
      // Solid Observatory Stone
      ctx.fillStyle = '#0f172a';
      ctx.fillRect(this.x, this.y, this.w, this.h);

      // Bronze trim edge
      ctx.strokeStyle = '#334155';
      ctx.lineWidth = 2;
      ctx.strokeRect(this.x, this.y, this.w, this.h);
    }
  }
}

class SanctuaryStone {
  constructor(x, y, stoneId, zoneId) {
    this.x = x;
    this.y = y;
    this.stoneId = stoneId;
    this.zoneId = zoneId;
    this.activated = false;
    this.radius = 24;
    this.sparkleTimer = 0;
  }

  update(dt, particles) {
    this.sparkleTimer += dt;
    if (this.sparkleTimer > 0.15) {
      this.sparkleTimer = 0;
      const color = this.activated ? '#fde047' : '#94a3b8';
      particles.push(new Particle(
        this.x + (Math.random() - 0.5) * 24,
        this.y + (Math.random() - 0.5) * 24,
        (Math.random() - 0.5) * 15,
        -15 - Math.random() * 20,
        color,
        2,
        0.7,
        'ember'
      ));
    }
  }

  activate(player, soundEngine, particles, floatingTexts) {
    this.activated = true;
    player.hp = player.maxHp; // Full restore upon rest
    game.lastSanctuaryZone = this.zoneId;
    game.lastSanctuaryPos = { x: this.x, y: this.y + 40 };

    soundEngine.playSaveSound();
    game.triggerScreenFlash('rgba(244, 197, 66, 0.35)');
    game.showToast("Sanctuary Stone Rekindled — Progress & Vitality Restored");

    floatingTexts.push(new FloatingText(this.x, this.y - 40, "SANCTUARY PRESERVED", '#f4c542', 16));

    // Save burst
    for (let i = 0; i < 25; i++) {
      particles.push(new Particle(
        this.x,
        this.y,
        (Math.random() - 0.5) * 140,
        (Math.random() - 0.5) * 140,
        '#facc15',
        3.5,
        0.8,
        'ember'
      ));
    }

    game.updateHUD();
  }

  draw(ctx) {
    ctx.save();
    ctx.translate(this.x, this.y);

    // Stone Pedestal base
    ctx.fillStyle = '#1e293b';
    ctx.strokeStyle = '#475569';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();

    // Glowing Central Monolith
    const stoneColor = this.activated ? '#facc15' : '#64748b';
    ctx.fillStyle = stoneColor;
    if (this.activated) {
      ctx.shadowBlur = 18;
      ctx.shadowColor = '#fde047';
    }

    ctx.beginPath();
    ctx.moveTo(0, -18);
    ctx.lineTo(12, 0);
    ctx.lineTo(0, 18);
    ctx.lineTo(-12, 0);
    ctx.closePath();
    ctx.fill();

    // Floating celestial ring
    ctx.strokeStyle = this.activated ? '#fef08a' : '#475569';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.ellipse(0, 0, 20, 8, 0, 0, Math.PI * 2);
    ctx.stroke();

    ctx.restore();
  }
}

class PressurePlate {
  constructor(x, y, targetDoorId) {
    this.x = x;
    this.y = y;
    this.w = 40;
    this.h = 40;
    this.targetDoorId = targetDoorId;
    this.isPressed = false;
  }

  update(player, enemies, soundEngine) {
    let wasPressed = this.isPressed;
    let nowPressed = false;

    // Player stepping on plate
    if (pointInRect(player.x, player.y, this.x, this.y, this.w, this.h)) {
      nowPressed = true;
    }

    // Enemies stepping on plate
    for (const e of enemies) {
      if (!e.isDead && pointInRect(e.x, e.y, this.x, this.y, this.w, this.h)) {
        nowPressed = true;
        break;
      }
    }

    this.isPressed = nowPressed;

    if (!wasPressed && this.isPressed) {
      soundEngine.playSwitch();
      game.setDoorState(this.targetDoorId, true);
    } else if (wasPressed && !this.isPressed) {
      // Optional toggle or latch
      // Keep open if latch or close if hold
    }
  }

  draw(ctx) {
    ctx.fillStyle = this.isPressed ? '#d97706' : '#78350f';
    ctx.strokeStyle = '#b45309';
    ctx.lineWidth = 2;
    ctx.fillRect(this.x, this.y, this.w, this.h);
    ctx.strokeRect(this.x, this.y, this.w, this.h);

    // Inner bronze depression
    ctx.fillStyle = this.isPressed ? '#f59e0b' : '#451a03';
    ctx.fillRect(this.x + 6, this.y + 6, this.w - 12, this.h - 12);
  }
}

class RangedSwitch {
  constructor(x, y, targetDoorId) {
    this.x = x;
    this.y = y;
    this.radius = 16;
    this.targetDoorId = targetDoorId;
    this.activated = false;
  }

  hit(soundEngine, particles, floatingTexts) {
    if (this.activated) return;
    this.activated = true;
    soundEngine.playAetherHit();
    soundEngine.playSwitch();

    game.setDoorState(this.targetDoorId, true);
    floatingTexts.push(new FloatingText(this.x, this.y - 20, "SWITCH ENGAGED", '#4ee0e6', 15));

    for (let i = 0; i < 15; i++) {
      particles.push(new Particle(
        this.x,
        this.y,
        (Math.random() - 0.5) * 120,
        (Math.random() - 0.5) * 120,
        '#4ee0e6',
        3,
        0.5,
        'spark'
      ));
    }
  }

  draw(ctx) {
    ctx.save();
    ctx.translate(this.x, this.y);

    ctx.fillStyle = '#0f172a';
    ctx.strokeStyle = '#334155';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();

    // Crystal Core
    const color = this.activated ? '#4ee0e6' : '#0284c7';
    ctx.fillStyle = color;
    if (this.activated) {
      ctx.shadowBlur = 15;
      ctx.shadowColor = '#4ee0e6';
    }

    ctx.beginPath();
    ctx.moveTo(0, -10);
    ctx.lineTo(8, 0);
    ctx.lineTo(0, 10);
    ctx.lineTo(-8, 0);
    ctx.closePath();
    ctx.fill();

    ctx.restore();
  }
}

class GateDoor {
  constructor(id, x, y, w, h, isOpen = false) {
    this.id = id;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.isOpen = isOpen;
  }

  draw(ctx) {
    if (this.isOpen) {
      // Open glowing gateway frame
      ctx.strokeStyle = '#22c55e';
      ctx.lineWidth = 2;
      ctx.strokeRect(this.x, this.y, this.w, this.h);
      return;
    }

    // Heavy iron/bronze portcullis gate
    ctx.fillStyle = '#1e293b';
    ctx.fillRect(this.x, this.y, this.w, this.h);
    ctx.strokeStyle = '#f59e0b';
    ctx.lineWidth = 3;
    ctx.strokeRect(this.x, this.y, this.w, this.h);

    // Bars
    ctx.fillStyle = '#475569';
    if (this.w > this.h) {
      for (let bx = this.x + 8; bx < this.x + this.w; bx += 14) {
        ctx.fillRect(bx, this.y, 4, this.h);
      }
    } else {
      for (let by = this.y + 8; by < this.y + this.h; by += 14) {
        ctx.fillRect(this.x, by, this.w, 4);
      }
    }
  }
}

class WeaponAltar {
  constructor(x, y, upgradeLevel, name, description) {
    this.x = x;
    this.y = y;
    this.upgradeLevel = upgradeLevel; // 1 = Ignited Edge (Zone 3), 2 = Solar Falchion (Zone 5)
    this.name = name;
    this.description = description;
    this.activated = false;
    this.radius = 28;
  }

  activate(player, soundEngine, particles, floatingTexts) {
    if (this.activated) return;
    this.activated = true;
    player.weaponLevel = this.upgradeLevel;

    soundEngine.playMajorPickup();
    game.triggerScreenFlash(this.upgradeLevel === 2 ? 'rgba(255, 224, 102, 0.45)' : 'rgba(230, 92, 0, 0.45)');
    game.triggerShimmer(2500);

    const title = this.upgradeLevel === 1 ? "Weapon Forged: Ignited Edge!" : "Weapon Forged: Solar Falchion!";
    game.showToast(title);
    game.openDialogue(this.name, this.description);

    floatingTexts.push(new FloatingText(this.x, this.y - 45, "WEAPON UPGRADED", '#fde047', 18));

    // Giant eruption particles
    for (let i = 0; i < 40; i++) {
      particles.push(new Particle(
        this.x,
        this.y,
        (Math.random() - 0.5) * 180,
        (Math.random() - 0.5) * 180,
        this.upgradeLevel === 2 ? '#facc15' : '#ea580c',
        4,
        0.9,
        'ember'
      ));
    }

    game.updateHUD();
  }

  draw(ctx) {
    ctx.save();
    ctx.translate(this.x, this.y);

    // Altar Stone Base
    ctx.fillStyle = '#1e293b';
    ctx.strokeStyle = this.upgradeLevel === 2 ? '#eab308' : '#ea580c';
    ctx.lineWidth = 3;
    ctx.fillRect(-this.radius, -this.radius, this.radius * 2, this.radius * 2);
    ctx.strokeRect(-this.radius, -this.radius, this.radius * 2, this.radius * 2);

    // Altar Center Weapon Projection
    if (!this.activated) {
      const glowColor = this.upgradeLevel === 2 ? '#fef08a' : '#fb923c';
      ctx.fillStyle = glowColor;
      ctx.shadowBlur = 18;
      ctx.shadowColor = glowColor;
      
      // Floating blade icon
      ctx.beginPath();
      ctx.moveTo(0, -18);
      ctx.lineTo(6, 12);
      ctx.lineTo(0, 16);
      ctx.lineTo(-6, 12);
      ctx.closePath();
      ctx.fill();
    } else {
      // Activated remnant
      ctx.fillStyle = '#64748b';
      ctx.beginPath();
      ctx.arc(0, 0, 8, 0, Math.PI * 2);
      ctx.fill();
    }

    ctx.restore();
  }
}
</script>

<script>
/**
 * COLLECTIBLES & LOOT: Gold, Heart Containers, Accessories, Consumables
 */

class GoldLoot {
  constructor(x, y, amount) {
    this.x = x;
    this.y = y;
    this.amount = amount;
    this.radius = 10;
    this.collected = false;
    this.sparkleTimer = 0;
  }

  update(dt, player, soundEngine, particles, floatingTexts) {
    if (this.collected) return;
    this.sparkleTimer += dt;

    const dist = Math.hypot(player.x - this.x, player.y - this.y);
    // Magnet pull towards player if close
    if (dist < 80) {
      this.x += (player.x - this.x) * 6 * dt;
      this.y += (player.y - this.y) * 6 * dt;
    }

    if (dist < player.radius + this.radius) {
      this.collected = true;
      game.gold += this.amount;
      soundEngine.playGoldPickup();
      floatingTexts.push(new FloatingText(this.x, this.y - 18, `+${this.amount} Sigils`, '#facc15', 13));

      // Gold swirl particles
      for (let i = 0; i < 6; i++) {
        particles.push(new Particle(
          this.x,
          this.y,
          (Math.random() - 0.5) * 60,
          (Math.random() - 0.5) * 60,
          '#fde047',
          2,
          0.3,
          'spark'
        ));
      }

      game.updateHUD();
    }
  }

  draw(ctx) {
    if (this.collected) return;
    ctx.save();
    ctx.translate(this.x, this.y);
    ctx.fillStyle = '#f59e0b';
    ctx.shadowBlur = 8;
    ctx.shadowColor = '#fde047';
    ctx.beginPath();
    ctx.arc(0, 0, this.radius * 0.75, 0, Math.PI * 2);
    ctx.fill();

    ctx.fillStyle = '#fef08a';
    ctx.beginPath();
    ctx.arc(0, 0, this.radius * 0.4, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }
}

class HealthContainerLoot {
  constructor(x, y, containerIndex) {
    this.x = x;
    this.y = y;
    this.containerIndex = containerIndex; // 4 or 5
    this.radius = 16;
    this.collected = false;
    this.floatOffset = 0;
  }

  update(dt, player, soundEngine, particles, floatingTexts) {
    if (this.collected) return;
    this.floatOffset += dt * 3;

    const dist = Math.hypot(player.x - this.x, player.y - this.y);
    if (dist < player.radius + this.radius) {
      this.collected = true;
      player.maxContainers = Math.min(5, player.maxContainers + 1);
      player.hp = player.maxHp; // Heal to full on expansion

      soundEngine.playMajorPickup();
      game.triggerScreenFlash('rgba(230, 57, 70, 0.4)');
      game.triggerShimmer(2000);
      game.showToast(`Vital Essence Expanded! Container #${player.maxContainers} Acquired!`);

      floatingTexts.push(new FloatingText(this.x, this.y - 30, "+1 HEALTH CONTAINER", '#e63946', 18));

      // Golden aura particles around player
      for (let i = 0; i < 35; i++) {
        particles.push(new Particle(
          player.x,
          player.y,
          (Math.random() - 0.5) * 150,
          (Math.random() - 0.5) * 150,
          '#f43f5e',
          3.5,
          0.8,
          'ember'
        ));
      }

      game.updateHUD();
    }
  }

  draw(ctx) {
    if (this.collected) return;
    const dy = Math.sin(this.floatOffset) * 5;
    ctx.save();
    ctx.translate(this.x, this.y + dy);

    ctx.shadowBlur = 16;
    ctx.shadowColor = '#e63946';

    // Draw Ornate Heart Shape
    ctx.fillStyle = '#e63946';
    ctx.beginPath();
    ctx.moveTo(0, 5);
    ctx.bezierCurveTo(-14, -10, -14, -22, 0, -12);
    ctx.bezierCurveTo(14, -22, 14, -10, 0, 5);
    ctx.fill();

    ctx.fillStyle = '#fca5a5';
    ctx.beginPath();
    ctx.arc(-4, -12, 3, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
  }
}

class WorldItemLoot {
  constructor(x, y, itemKey, itemName, itemDesc) {
    this.x = x;
    this.y = y;
    this.itemKey = itemKey;
    this.itemName = itemName;
    this.itemDesc = itemDesc;
    this.radius = 16;
    this.collected = false;
    this.floatOffset = 0;
  }

  update(dt, player, soundEngine, particles, floatingTexts) {
    if (this.collected) return;
    this.floatOffset += dt * 3.5;

    const dist = Math.hypot(player.x - this.x, player.y - this.y);
    if (dist < player.radius + this.radius) {
      this.collected = true;

      if (this.itemKey === 'aetherShard') {
        player.hasAetherShard = true;
        document.getElementById('slot-ranged').style.display = 'flex';
        soundEngine.playMajorPickup();
        game.triggerScreenFlash('rgba(78, 224, 230, 0.45)');
        game.showToast("Discovered Ranged Weapon: Aether Shard!");
        game.openDialogue("The Aether Shard Recovered", "In the crystalline depths of the Sunken Vault, you unearthed the Aether Shard. Press [RMB] or [K] to fire long-range radiant bolts to trigger distant mechanisms and pierce tough foes from afar.");
      } else if (this.itemKey === 'vitalStem') {
        player.vitalStems += 1;
        soundEngine.playItemPickup();
        game.showToast("Acquired Vital Stem (+4 HP Consumable)");
      } else if (this.itemKey === 'bindingSeal') {
        player.bindingSeals += 1;
        soundEngine.playItemPickup();
        game.showToast("Acquired Binding Seal (Freeze Consumable)");
      } else {
        // Accessories: wardensBand, phantomLantern, shadowCloak
        game.inventory.push(this.itemKey);
        player.equipped[this.itemKey] = true; // Auto-equip first find
        soundEngine.playMajorPickup();
        game.showToast(`Equipped Accessory: ${this.itemName}`);
        game.openDialogue(this.itemName, this.itemDesc);
      }

      floatingTexts.push(new FloatingText(this.x, this.y - 25, `ACQUIRED: ${this.itemName}`, '#facc15', 14));

      for (let i = 0; i < 20; i++) {
        particles.push(new Particle(
          this.x,
          this.y,
          (Math.random() - 0.5) * 120,
          (Math.random() - 0.5) * 120,
          '#fde047',
          3,
          0.6,
          'ember'
        ));
      }

      game.updateHUD();
    }
  }

  draw(ctx) {
    if (this.collected) return;
    const dy = Math.sin(this.floatOffset) * 4;
    ctx.save();
    ctx.translate(this.x, this.y + dy);

    let color = '#facc15';
    if (this.itemKey === 'aetherShard' || this.itemKey === 'phantomLantern') color = '#38bdf8';
    if (this.itemKey === 'vitalStem') color = '#4ade80';
    if (this.itemKey === 'shadowCloak') color = '#a855f7';

    ctx.shadowBlur = 14;
    ctx.shadowColor = color;
    ctx.fillStyle = color;

    // Diamond / Orb item frame
    ctx.beginPath();
    ctx.moveTo(0, -12);
    ctx.lineTo(12, 0);
    ctx.lineTo(0, 12);
    ctx.lineTo(-12, 0);
    ctx.closePath();
    ctx.fill();

    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(0, 0, 4, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
  }
}

class BeaconAltar {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.radius = 45;
    this.activated = false;
    this.spinAngle = 0;
  }

  update(dt) {
    this.spinAngle += dt * 1.5;
  }

  activate(player, soundEngine, particles) {
    if (this.activated) return;
    this.activated = true;

    soundEngine.playSaveSound();
    soundEngine.playMajorPickup();
    game.triggerScreenFlash('rgba(255, 224, 102, 0.7)');
    game.triggerShimmer(5000);

    for (let i = 0; i < 80; i++) {
      particles.push(new Particle(
        this.x,
        this.y,
        (Math.random() - 0.5) * 260,
        (Math.random() - 0.5) * 260,
        '#facc15',
        5,
        1.5,
        'ember'
      ));
    }

    setTimeout(() => {
      game.onVictory();
    }, 1800);
  }

  draw(ctx) {
    ctx.save();
    ctx.translate(this.x, this.y);

    // Large Bronze Pedestal
    ctx.fillStyle = '#1e293b';
    ctx.strokeStyle = '#d97706';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();

    // Rotating celestial gear teeth
    ctx.save();
    ctx.rotate(this.spinAngle);
    ctx.fillStyle = '#b45309';
    for (let i = 0; i < 12; i++) {
      const a = (i / 12) * Math.PI * 2;
      ctx.fillRect(Math.cos(a) * 40 - 4, Math.sin(a) * 40 - 4, 8, 8);
    }
    ctx.restore();

    // Core Beacon flame
    const beaconColor = this.activated ? '#ffffff' : '#f59e0b';
    ctx.fillStyle = beaconColor;
    ctx.shadowBlur = this.activated ? 40 : 20;
    ctx.shadowColor = '#fde047';
    ctx.beginPath();
    ctx.arc(0, 0, 18, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
  }
}
</script>

<script>
/**
 * MAIN GAME ENGINE & ZONE MAP BUILDERS
 */

// Math / Utility helpers
function pointInRect(px, py, rx, ry, rw, rh) {
  return px >= rx && px <= rx + rw && py >= ry && py <= ry + rh;
}

function circleRectCollide(cx, cy, cr, rx, ry, rw, rh) {
  const testX = Math.max(rx, Math.min(cx, rx + rw));
  const testY = Math.max(ry, Math.min(cy, ry + rh));
  const distX = cx - testX;
  const distY = cy - testY;
  return (distX * distX + distY * distY) < (cr * cr);
}

function normalizeAngle(a) {
  while (a < -Math.PI) a += Math.PI * 2;
  while (a > Math.PI) a -= Math.PI * 2;
  return a;
}

class GameEngine {
  constructor() {
    this.canvas = document.getElementById('gameCanvas');
    this.ctx = this.canvas.getContext('2d');
    this.sound = new SoundEngine();

    this.currentZoneId = 1;
    this.player = new Player(200, 450);
    this.gold = 0;
    this.inventory = []; // list of item keys acquired

    // Sanctuary Stone tracking
    this.lastSanctuaryZone = 1;
    this.lastSanctuaryPos = { x: 200, y: 450 };

    // World entities for current zone
    this.walls = [];
    this.enemies = [];
    this.sanctuaryStones = [];
    this.pressurePlates = [];
    this.rangedSwitches = [];
    this.doors = [];
    this.weaponAltars = [];
    this.goldLoots = [];
    this.healthContainerLoots = [];
    this.worldItems = [];
    this.beaconAltar = null;

    // Ephemeral visual arrays
    this.projectiles = [];
    this.particles = [];
    this.floatingTexts = [];

    // Camera
    this.camX = 0;
    this.camY = 0;

    // Controls state
    this.keys = {};
    this.mouseScreenPos = { x: 480, y: 270 };
    this.mouseWorldPos = { x: 480, y: 270 };

    // Game state loop
    this.isRunning = false;
    this.isPaused = false;
    this.lastTime = 0;

    // Shop stock counts
    this.shopVitalStems = 3;
    this.shopBindingSeals = 2;
    this.shopContainer5Available = true;

    // Shop proximity
    this.nearShop = false;

    // Bind events
    this.bindEvents();
    this.updateHUD();
  }

  bindEvents() {
    window.addEventListener('keydown', (e) => {
      this.sound.init();
      this.keys[e.code] = true;

      if (e.code === 'KeyI') {
        this.toggleInventory();
      }
      if (e.code === 'KeyE') {
        this.handleInteract();
      }
      if (e.code === 'Digit1') {
        this.useQuickItem('vitalStem');
      }
      if (e.code === 'Digit2') {
        this.useQuickItem('bindingSeal');
      }
      if (e.code === 'KeyJ' || e.code === 'Space') {
        this.player.performMeleeAttack(this.sound, this.particles, this.enemies, this.walls);
      }
      if (e.code === 'KeyK') {
        this.player.performRangedAttack(this.mouseWorldPos, this.sound, this.particles);
      }
    });

    window.addEventListener('keyup', (e) => {
      this.keys[e.code] = false;
    });

    this.canvas.addEventListener('mousemove', (e) => {
      const rect = this.canvas.getBoundingClientRect();
      const scaleX = this.canvas.width / rect.width;
      const scaleY = this.canvas.height / rect.height;
      this.mouseScreenPos.x = (e.clientX - rect.left) * scaleX;
      this.mouseScreenPos.y = (e.clientY - rect.top) * scaleY;
    });

    this.canvas.addEventListener('mousedown', (e) => {
      this.sound.init();
      if (e.button === 0) {
        // Left click: melee attack
        this.player.performMeleeAttack(this.sound, this.particles, this.enemies, this.walls);
      } else if (e.button === 2) {
        // Right click: ranged attack
        e.preventDefault();
        this.player.performRangedAttack(this.mouseWorldPos, this.sound, this.particles);
      }
    });

    this.canvas.addEventListener('contextmenu', (e) => e.preventDefault());

    // Touch controls binding
    const setupTouchBtn = (id, onDown, onUp) => {
      const el = document.getElementById(id);
      if (!el) return;
      el.addEventListener('touchstart', (e) => { e.preventDefault(); this.sound.init(); onDown(); });
      el.addEventListener('touchend', (e) => { e.preventDefault(); onUp(); });
    };

    setupTouchBtn('touch-left', () => this.keys['KeyA'] = true, () => this.keys['KeyA'] = false);
    setupTouchBtn('touch-right', () => this.keys['KeyD'] = true, () => this.keys['KeyD'] = false);
    setupTouchBtn('touch-up', () => this.keys['KeyW'] = true, () => this.keys['KeyW'] = false);
    setupTouchBtn('touch-down', () => this.keys['KeyS'] = true, () => this.keys['KeyS'] = false);
    setupTouchBtn('touch-atk', () => this.player.performMeleeAttack(this.sound, this.particles, this.enemies, this.walls), () => {});
    setupTouchBtn('touch-ranged', () => this.player.performRangedAttack(this.mouseWorldPos, this.sound, this.particles), () => {});
    setupTouchBtn('touch-use', () => this.handleInteract(), () => {});
  }

  startGame() {
    this.sound.init();
    document.getElementById('title-screen').style.display = 'none';
    this.loadZone(1);
    this.isRunning = true;
    this.lastTime = performance.now();
    requestAnimationFrame((t) => this.gameLoop(t));
  }

  loadZone(zoneId, spawnPos = null) {
    this.currentZoneId = zoneId;
    const zoneDef = ZONES.find(z => z.id === zoneId);

    // Clear world arrays
    this.walls = [];
    this.enemies = [];
    this.sanctuaryStones = [];
    this.pressurePlates = [];
    this.rangedSwitches = [];
    this.doors = [];
    this.weaponAltars = [];
    this.goldLoots = [];
    this.healthContainerLoots = [];
    this.worldItems = [];
    this.projectiles = [];
    this.particles = [];
    this.floatingTexts = [];
    this.beaconAltar = null;

    // Update HUD
    document.getElementById('zone-name').innerText = zoneDef.name;
    document.getElementById('zone-sub').innerText = zoneDef.sub;
    this.showToast(`Entering: ${zoneDef.name}`);

    // Build zone level layout
    this.buildZoneGeometry(zoneId, zoneDef);

    // Position player
    if (spawnPos) {
      this.player.x = spawnPos.x;
      this.player.y = spawnPos.y;
    } else {
      this.player.x = 120;
      this.player.y = zoneDef.height / 2;
    }

    this.updateHUD();
  }

  buildZoneGeometry(zoneId, zoneDef) {
    const W = zoneDef.width;
    const H = zoneDef.height;

    // Outer perimeter walls
    this.walls.push(new Wall(0, 0, W, 40));
    this.walls.push(new Wall(0, H - 40, W, 40));
    this.walls.push(new Wall(0, 0, 40, H)); // West outer wall

    // Exit portal / gateway to next zone on East wall
    if (zoneId < 6) {
      this.walls.push(new Wall(W - 40, 0, 40, H / 2 - 80));
      this.walls.push(new Wall(W - 40, H / 2 + 80, 40, H / 2 - 80));
      // Zone exit trigger is gap at W-40, H/2 - 80 to H/2 + 80
    } else {
      this.walls.push(new Wall(W - 40, 0, 40, H)); // Zone 6 is sealed climax arena
    }

    // ==========================================
    // ZONE 1: THE ASHEN THRESHOLD (Prologue)
    // Sanctuary Stone 1, basic crawler constructs, initial movement & combat training
    // ==========================================
    if (zoneId === 1) {
      // Sanctuary Stone 1 near entrance
      this.sanctuaryStones.push(new SanctuaryStone(200, H / 2, 1, 1));

      // Pillar obstacles
      this.walls.push(new Wall(480, 160, 60, 220));
      this.walls.push(new Wall(480, H - 380, 60, 220));
      this.walls.push(new Wall(880, 260, 70, 380));

      // Initial breakable cache wall
      this.walls.push(new Wall(600, 40, 100, 40, true, 2, 'vitalStem'));

      // Enemies (Crawlers)
      this.enemies.push(new CorruptedConstruct(650, 280, 'crawler'));
      this.enemies.push(new CorruptedConstruct(650, H - 280, 'crawler'));
      this.enemies.push(new CorruptedConstruct(1100, 340, 'crawler'));
      this.enemies.push(new CorruptedConstruct(1250, H - 340, 'crawler'));
      this.enemies.push(new CorruptedConstruct(1380, H / 2, 'sentinel'));
    }

    // ==========================================
    // ZONE 2: THE RUSTED ARCHIVES (Early)
    // Sanctuary Stone 2, Shop Altar, Warden's Band hidden chest, money farming
    // ==========================================
    else if (zoneId === 2) {
      // Sanctuary Stone 2
      this.sanctuaryStones.push(new SanctuaryStone(700, H / 2, 2, 2));

      // Internal library shelves and partitions
      this.walls.push(new Wall(360, 100, 40, 320));
      this.walls.push(new Wall(360, H - 420, 40, 320));
      
      this.walls.push(new Wall(1020, 140, 40, 300));
      this.walls.push(new Wall(1020, H - 440, 40, 300));

      // Secret breakable wall hiding Warden's Band accessory
      this.walls.push(new Wall(360, 420, 40, 160, true, 3, 'wardensBand'));

      // Enemies
      this.enemies.push(new CorruptedConstruct(520, 220, 'crawler'));
      this.enemies.push(new CorruptedConstruct(520, H - 220, 'crawler'));
      this.enemies.push(new CorruptedConstruct(880, 260, 'sentinel'));
      this.enemies.push(new CorruptedConstruct(880, H - 260, 'sentinel'));
      this.enemies.push(new CorruptedConstruct(1350, 320, 'sentinel'));
      this.enemies.push(new CorruptedConstruct(1450, H - 320, 'knight'));

      // Extra gold caches
      this.goldLoots.push(new GoldLoot(280, 140, 15));
      this.goldLoots.push(new GoldLoot(280, H - 140, 15));
    }

    // ==========================================
    // ZONE 3: THE COLLAPSED GALLERY (Mid-Early)
    // Weapon Upgrade 1 Altar (Ignited Edge), Pressure Plate puzzle, Crack-marked wall hiding Container #4
    // ==========================================
    else if (zoneId === 3) {
      // Pressure plate puzzle and gate
      this.pressurePlates.push(new PressurePlate(580, 220, 'doorGallery'));
      this.doors.push(new GateDoor('doorGallery', 820, 380, 40, 240, false));

      this.walls.push(new Wall(820, 40, 40, 340));
      this.walls.push(new Wall(820, 620, 40, H - 660));

      // Weapon Upgrade 1 Altar: Ignited Edge
      this.weaponAltars.push(new WeaponAltar(
        1350,
        H / 2,
        1,
        "Altar of the Ignited Edge",
        "The ancient flame awakens! Your Ember Blade is infused with celestial fire—higher attack speed and searing ember damage."
      ));

      // Hidden crack wall hiding Container 4 (4th Health Container)
      this.walls.push(new Wall(1550, 40, 120, 40, true, 3, 'container4'));

      // Enemies & Mini-boss sentinel gauntlet
      this.enemies.push(new CorruptedConstruct(450, 300, 'crawler'));
      this.enemies.push(new CorruptedConstruct(450, H - 300, 'sentinel'));
      this.enemies.push(new CorruptedConstruct(1050, 280, 'knight'));
      this.enemies.push(new CorruptedConstruct(1050, H - 280, 'knight'));
      this.enemies.push(new CorruptedConstruct(1650, H / 2, 'knight'));
    }

    // ==========================================
    // ZONE 4: THE SUNKEN VAULT (Mid-Game Turning Point)
    // Ranged Weapon Discovery: Aether Shard hidden behind breakable wall,
    // Phantom Lantern light puzzle, crystal switches
    // ==========================================
    else if (zoneId === 4) {
      // Internal submerged ruins
      this.walls.push(new Wall(420, 120, 40, 400));
      this.walls.push(new Wall(420, H - 520, 40, 400));

      // Hidden wall hiding the AETHER SHARD
      this.walls.push(new Wall(420, 520, 40, 100, true, 3, 'aetherShard'));

      // Distant crystal switch requiring Aether Shard to open doorVault
      this.rangedSwitches.push(new RangedSwitch(1250, 180, 'doorVault'));
      this.doors.push(new GateDoor('doorVault', 1400, 420, 40, 260, false));

      this.walls.push(new Wall(1400, 40, 40, 380));
      this.walls.push(new Wall(1400, 680, 40, H - 720));

      // Light-puzzle reward: Phantom Lantern
      this.worldItems.push(new WorldItemLoot(
        950,
        H / 2,
        'phantomLantern',
        "Phantom Lantern",
        "Accessory: Casts a radiant ethereal halo that exposes secret cracked passages and illuminates the darkest vaults."
      ));

      // Enemies
      this.enemies.push(new CorruptedConstruct(650, 260, 'sentinel'));
      this.enemies.push(new CorruptedConstruct(650, H - 260, 'sentinel'));
      this.enemies.push(new CorruptedConstruct(1100, 320, 'knight'));
      this.enemies.push(new CorruptedConstruct(1100, H - 320, 'knight'));
      this.enemies.push(new CorruptedConstruct(1680, H / 2, 'knight'));
    }

    // ==========================================
    // ZONE 5: THE TWIN SPIRE (Late Game)
    // Sanctuary Stone 3, Weapon Upgrade 2 (Solar Falchion),
    // Shadow Cloak accessory, Health Container #5 ranged switch room
    // ==========================================
    else if (zoneId === 5) {
      // Sanctuary Stone 3 at Spire entrance
      this.sanctuaryStones.push(new SanctuaryStone(220, H / 2, 3, 5));

      // Spire layout & Gauntlet
      this.walls.push(new Wall(520, 80, 50, 400));
      this.walls.push(new Wall(520, H - 480, 50, 400));

      // Weapon Upgrade 2: Solar Falchion Altar
      this.weaponAltars.push(new WeaponAltar(
        1100,
        H / 2,
        2,
        "Altar of the Solar Falchion",
        "The purest solar essence floods your blade! Devastating wide sweep, deep resonance, and radiant starlight power."
      ));

      // Ranged switch for Health Container 5 secret chamber (if not bought from shop)
      this.rangedSwitches.push(new RangedSwitch(1450, 160, 'doorSpireSecret'));
      this.doors.push(new GateDoor('doorSpireSecret', 1600, 120, 30, 180, false));
      this.walls.push(new Wall(1600, 40, 30, 80));
      this.walls.push(new Wall(1600, 300, 30, 200));

      // Spawn container 5 if player doesn't have it yet
      if (this.player.maxContainers < 5) {
        this.healthContainerLoots.push(new HealthContainerLoot(1800, 200, 5));
      }

      // Shadow Cloak reward in south wing
      this.worldItems.push(new WorldItemLoot(
        1800,
        H - 200,
        'shadowCloak',
        "Shadow Cloak",
        "Accessory: Weaves dark celestial fabric into your garment, greatly dampening trap impact and incoming enemy blows."
      ));

      // Tough combat gauntlet
      this.enemies.push(new CorruptedConstruct(720, 240, 'knight'));
      this.enemies.push(new CorruptedConstruct(720, H - 240, 'knight'));
      this.enemies.push(new CorruptedConstruct(1380, 320, 'knight'));
      this.enemies.push(new CorruptedConstruct(1380, H - 320, 'knight'));
      this.enemies.push(new CorruptedConstruct(1500, H / 2, 'knight'));
    }

    // ==========================================
    // ZONE 6: THE CORE CHAMBER (Climax)
    // Corrupted Titan Boss + Core Beacon Reignite Altar
    // ==========================================
    else if (zoneId === 6) {
      // Arena boundary pillars
      this.walls.push(new Wall(450, 180, 80, 80));
      this.walls.push(new Wall(450, H - 260, 80, 80));
      this.walls.push(new Wall(1250, 180, 80, 80));
      this.walls.push(new Wall(1250, H - 260, 80, 80));

      // Core Beacon in the far center
      this.beaconAltar = new BeaconAltar(1500, H / 2);

      // Core Guardian Boss: Corrupted Titan Construct
      this.enemies.push(new CorruptedConstruct(1050, H / 2, 'boss'));
      this.enemies.push(new CorruptedConstruct(800, 250, 'knight'));
      this.enemies.push(new CorruptedConstruct(800, H - 250, 'knight'));
    }
  }

  setDoorState(doorId, open) {
    for (const d of this.doors) {
      if (d.id === doorId) {
        d.isOpen = open;
      }
    }
  }

  spawnGoldLoot(x, y, amount) {
    this.goldLoots.push(new GoldLoot(x, y, amount));
  }

  spawnHealthContainer(x, y, index) {
    this.healthContainerLoots.push(new HealthContainerLoot(x, y, index));
  }

  spawnWorldItem(x, y, key, name, desc) {
    this.worldItems.push(new WorldItemLoot(x, y, key, name, desc));
  }

  handleInteract() {
    // 1. Check Dialogue dismissal
    const diag = document.getElementById('dialogue-modal');
    if (diag.style.display === 'block') {
      this.closeDialogue();
      return;
    }

    // 2. Check Sanctuary Stone interaction
    for (const s of this.sanctuaryStones) {
      const d = Math.hypot(s.x - this.player.x, s.y - this.player.y);
      if (d < s.radius + this.player.radius + 20) {
        s.activate(this.player, this.sound, this.particles, this.floatingTexts);
        return;
      }
    }

    // 3. Check Weapon Altar interaction
    for (const a of this.weaponAltars) {
      const d = Math.hypot(a.x - this.player.x, a.y - this.player.y);
      if (d < a.radius + this.player.radius + 25) {
        a.activate(this.player, this.sound, this.particles, this.floatingTexts);
        return;
      }
    }

    // 4. Check Beacon Altar interaction in Zone 6
    if (this.beaconAltar && !this.beaconAltar.activated) {
      const d = Math.hypot(this.beaconAltar.x - this.player.x, this.beaconAltar.y - this.player.y);
      if (d < this.beaconAltar.radius + this.player.radius + 30) {
        // Only can activate once boss is defeated
        const bossAlive = this.enemies.some(e => e.type === 'boss' && !e.isDead);
        if (bossAlive) {
          this.showToast("The Corrupted Titan still guards the Beacon!");
        } else {
          this.beaconAltar.activate(this.player, this.sound, this.particles);
        }
        return;
      }
    }

    // 5. Check Shop Altar in Zone 2
    if (this.currentZoneId === 2) {
      const shopDist = Math.hypot(700 - this.player.x, 250 - this.player.y);
      if (shopDist < 70) {
        this.openShop();
        return;
      }
    }
  }

  useQuickItem(itemKey) {
    if (itemKey === 'vitalStem') {
      if (this.player.vitalStems > 0 && this.player.hp < this.player.maxHp) {
        this.player.vitalStems -= 1;
        this.player.heal(4, this.particles, this.floatingTexts); // 2 containers = 4 HP
        this.sound.playItemPickup();
        this.showToast("Used Vital Stem (+4 HP)");
      } else if (this.player.vitalStems <= 0) {
        this.showToast("No Vital Stems remaining!");
      }
    } else if (itemKey === 'bindingSeal') {
      if (this.player.bindingSeals > 0) {
        // Freeze closest active enemy within 280px for 3 seconds
        let closest = null;
        let minDist = 280;
        for (const e of this.enemies) {
          if (e.isDead) continue;
          const d = Math.hypot(e.x - this.player.x, e.y - this.player.y);
          if (d < minDist) {
            minDist = d;
            closest = e;
          }
        }

        if (closest) {
          this.player.bindingSeals -= 1;
          closest.freezeTimer = 3.0;
          this.sound.playFreeze();
          this.showToast("Corrupted Construct Bound in Stasis!");
          this.floatingTexts.push(new FloatingText(closest.x, closest.y - 25, "FROZEN (3s)", '#38bdf8', 16));

          for (let i = 0; i < 20; i++) {
            this.particles.push(new Particle(
              closest.x,
              closest.y,
              (Math.random() - 0.5) * 100,
              (Math.random() - 0.5) * 100,
              '#38bdf8',
              3,
              0.5,
              'ember'
            ));
          }
        } else {
          this.showToast("No targets in range for Binding Seal.");
        }
      } else {
        this.showToast("No Binding Seals remaining!");
      }
    }
    this.updateHUD();
  }

  toggleInventory() {
    const modal = document.getElementById('inventory-modal');
    if (modal.style.display === 'flex') {
      modal.style.display = 'none';
      this.isPaused = false;
    } else {
      this.renderInventory();
      modal.style.display = 'flex';
      this.isPaused = true;
    }
  }

  renderInventory() {
    const list = document.getElementById('inventory-list');
    list.innerHTML = '';

    const itemsData = [
      {
        key: 'wardensBand',
        name: "Warden's Band",
        type: "Accessory",
        desc: "Polished silver crest ring. Significantly speeds up weapon swing recovery.",
        has: this.inventory.includes('wardensBand')
      },
      {
        key: 'phantomLantern',
        name: "Phantom Lantern",
        type: "Accessory",
        desc: "Crystalline light source. Reveals hairline wall cracks and submerged hidden pathways.",
        has: this.inventory.includes('phantomLantern')
      },
      {
        key: 'shadowCloak',
        name: "Shadow Cloak",
        type: "Accessory",
        desc: "Starlight-woven shroud. Absorbs kinetic shock, granting armor against traps and blows.",
        has: this.inventory.includes('shadowCloak')
      },
      {
        key: 'vitalStem',
        name: `Vital Stem (x${this.player.vitalStems})`,
        type: "Consumable",
        desc: "Gleaming green stardust flora. Instantly regenerates 4 HP (2 full heart containers).",
        has: this.player.vitalStems > 0,
        isConsumable: true
      },
      {
        key: 'bindingSeal',
        name: `Binding Seal (x${this.player.bindingSeals})`,
        type: "Consumable",
        desc: "Etched cipher rune. Ensnared construct is frozen motionless in stasis for 3 seconds.",
        has: this.player.bindingSeals > 0,
        isConsumable: true
      }
    ];

    itemsData.forEach(item => {
      if (!item.has) return;
      const card = document.createElement('div');
      card.className = `item-card ${this.player.equipped[item.key] ? 'equipped' : ''}`;
      
      let actionBtnText = '';
      if (item.isConsumable) {
        actionBtnText = `<button class="btn" style="padding:4px 10px; font-size:11px;" onclick="game.useQuickItem('${item.key}'); game.renderInventory();">Use</button>`;
      } else {
        const isEq = this.player.equipped[item.key];
        actionBtnText = `<button class="btn" style="padding:4px 10px; font-size:11px;" onclick="game.toggleEquip('${item.key}')">${isEq ? 'Unequip' : 'Equip'}</button>`;
      }

      card.innerHTML = `
        <div class="item-info">
          <div class="item-name">${item.name} <span class="item-badge">${item.type}</span></div>
          <div class="item-desc">${item.desc}</div>
        </div>
        <div>${actionBtnText}</div>
      `;
      list.appendChild(card);
    });

    if (list.children.length === 0) {
      list.innerHTML = '<div style="color:#64748b; text-align:center; padding:20px;">Your satchel is currently empty. Explore the Observatory to discover relics.</div>';
    }
  }

  toggleEquip(itemKey) {
    this.player.equipped[itemKey] = !this.player.equipped[itemKey];
    this.sound.playItemPickup();
    this.renderInventory();
    this.updateHUD();
  }

  openShop() {
    this.isPaused = true;
    const modal = document.getElementById('shop-modal');
    modal.style.display = 'flex';
    this.renderShop();
  }

  closeShop() {
    const modal = document.getElementById('shop-modal');
    modal.style.display = 'none';
    this.isPaused = false;
  }

  renderShop() {
    document.getElementById('shop-gold-amount').innerText = this.gold;
    const list = document.getElementById('shop-list');
    list.innerHTML = '';

    const shopItems = [
      {
        id: 'vitalStem',
        name: "Vital Stem",
        cost: 50,
        desc: "Restores 2 full heart containers (4 HP).",
        available: this.shopVitalStems > 0,
        stock: this.shopVitalStems,
        buy: () => {
          if (this.gold >= 50 && this.shopVitalStems > 0) {
            this.gold -= 50;
            this.shopVitalStems -= 1;
            this.player.vitalStems += 1;
            this.sound.playShopPurchase();
            this.renderShop();
            this.updateHUD();
          }
        }
      },
      {
        id: 'bindingSeal',
        name: "Binding Seal",
        cost: 75,
        desc: "Cipher rune that freezes an enemy for 3 seconds.",
        available: this.shopBindingSeals > 0,
        stock: this.shopBindingSeals,
        buy: () => {
          if (this.gold >= 75 && this.shopBindingSeals > 0) {
            this.gold -= 75;
            this.shopBindingSeals -= 1;
            this.player.bindingSeals += 1;
            this.sound.playShopPurchase();
            this.renderShop();
            this.updateHUD();
          }
        }
      },
      {
        id: 'container5',
        name: "Heart Container Upgrade #5",
        cost: 300,
        desc: "Expand your maximum vitality to the 5th and final celestial heart container (10 Max HP).",
        available: this.shopContainer5Available && this.player.maxContainers < 5,
        stock: this.shopContainer5Available ? 1 : 0,
        buy: () => {
          if (this.gold >= 300 && this.shopContainer5Available) {
            this.gold -= 300;
            this.shopContainer5Available = false;
            this.player.maxContainers = 5;
            this.player.hp = this.player.maxHp;
            this.sound.playMajorPickup();
            this.triggerScreenFlash('rgba(230, 57, 70, 0.45)');
            this.triggerShimmer(2500);
            this.showToast("Heart Container #5 Purchased!");
            this.renderShop();
            this.updateHUD();
          }
        }
      }
    ];

    shopItems.forEach(item => {
      const card = document.createElement('div');
      card.className = 'item-card';
      const canAfford = this.gold >= item.cost && item.available;

      card.innerHTML = `
        <div class="item-info">
          <div class="item-name">${item.name} <span class="item-badge" style="color:var(--gold);">${item.cost} Sigils</span></div>
          <div class="item-desc">${item.desc} (In Stock: ${item.stock})</div>
        </div>
        <div>
          <button class="btn" style="padding:6px 14px; font-size:12px;" ${!canAfford ? 'disabled' : ''} onclick="window.shopAction('${item.id}')">Purchase</button>
        </div>
      `;
      list.appendChild(card);
    });

    window.shopAction = (id) => {
      const target = shopItems.find(i => i.id === id);
      if (target) target.buy();
    };
  }

  openDialogue(title, text) {
    const modal = document.getElementById('dialogue-modal');
    document.getElementById('dialogue-title').innerText = title;
    document.getElementById('dialogue-body').innerText = text;
    modal.style.display = 'block';
  }

  closeDialogue() {
    document.getElementById('dialogue-modal').style.display = 'none';
  }

  showToast(text) {
    const toast = document.getElementById('notification-toast');
    toast.innerText = text;
    toast.classList.add('show');
    if (this.toastTimeout) clearTimeout(this.toastTimeout);
    this.toastTimeout = setTimeout(() => {
      toast.classList.remove('show');
    }, 2800);
  }

  triggerScreenFlash(color) {
    const el = document.getElementById('screen-flash');
    el.style.background = color;
    setTimeout(() => {
      el.style.background = 'transparent';
    }, 150);
  }

  triggerShimmer(duration = 1800) {
    const el = document.getElementById('shimmer-overlay');
    el.style.boxShadow = 'inset 0 0 100px rgba(255, 224, 102, 0.4), 0 0 40px rgba(244, 197, 66, 0.3)';
    setTimeout(() => {
      el.style.boxShadow = 'inset 0 0 100px rgba(0,0,0,0.8)';
    }, duration);
  }

  updateHUD() {
    // 1. Update Hearts Display
    const container = document.getElementById('hearts-container');
    container.innerHTML = '';

    for (let i = 0; i < this.player.maxContainers; i++) {
      const heartBox = document.createElement('div');
      heartBox.className = 'heart-box';

      // Container has 2 HP
      const hpInThisContainer = Math.max(0, Math.min(2, this.player.hp - (i * 2)));

      // SVG Heart Container
      let fillColor = 'none';
      let strokeColor = '#94a3b8';

      if (hpInThisContainer === 2) {
        fillColor = '#e63946'; // Full heart
        strokeColor = '#fca5a5';
      } else if (hpInThisContainer === 1) {
        fillColor = 'url(#halfHeartGrad)'; // Half heart
        strokeColor = '#e63946';
      } else {
        fillColor = 'rgba(15, 22, 35, 0.8)'; // Empty heart
        strokeColor = '#475569';
      }

      heartBox.innerHTML = `
        <svg viewBox="0 0 32 32" width="28" height="28">
          <defs>
            <linearGradient id="halfHeartGrad" x1="0" x2="1" y1="0" y2="0">
              <stop offset="50%" stop-color="#e63946"/>
              <stop offset="50%" stop-color="rgba(15, 22, 35, 0.8)"/>
            </linearGradient>
          </defs>
          <path d="M16 28 C8 20 2 15 2 9 A7 7 0 0 1 16 6 A7 7 0 0 1 30 9 C30 15 24 20 16 28 Z" 
                fill="${fillColor}" stroke="${strokeColor}" stroke-width="2"/>
        </svg>
      `;
      container.appendChild(heartBox);
    }

    // 2. Gold count
    document.getElementById('gold-amount').innerText = this.gold;

    // 3. Weapon Name
    const weaponLabel = document.getElementById('weapon-name');
    if (this.player.weaponLevel === 0) weaponLabel.innerText = "Ember Blade";
    if (this.player.weaponLevel === 1) weaponLabel.innerText = "Ignited Edge";
    if (this.player.weaponLevel === 2) weaponLabel.innerText = "Solar Falchion";

    // 4. Quick Consumable counts
    document.getElementById('vital-stem-count').innerText = this.player.vitalStems;
    document.getElementById('binding-seal-count').innerText = this.player.bindingSeals;
  }

  onPlayerDeath() {
    this.sound.playDamagePlayer();
    document.getElementById('game-over-screen').style.display = 'flex';
  }

  respawnAtSanctuary() {
    document.getElementById('game-over-screen').style.display = 'none';
    this.player.isDead = false;
    this.player.hp = this.player.maxHp;
    this.loadZone(this.lastSanctuaryZone, this.lastSanctuaryPos);
  }

  onVictory() {
    this.isPaused = true;
    document.getElementById('victory-screen').style.display = 'flex';
    document.getElementById('victory-stats').innerHTML = `
      <div>Weapon State: <b>${this.player.weaponLevel === 2 ? 'Solar Falchion (Max)' : 'Ignited Edge'}</b></div>
      <div>Health Essence: <b>${this.player.maxContainers} Containers (${this.player.maxHp} Max HP)</b></div>
      <div>Sigils Collected: <b>${this.gold} Sigils</b></div>
      <div>Relics Acquired: <b>${this.inventory.length} Accessories</b></div>
    `;
  }

  restartGame() {
    document.getElementById('victory-screen').style.display = 'none';
    this.player = new Player(200, 450);
    this.gold = 0;
    this.inventory = [];
    this.lastSanctuaryZone = 1;
    this.lastSanctuaryPos = { x: 200, y: 450 };
    this.loadZone(1);
    this.isPaused = false;
  }

  gameLoop(timestamp) {
    const dt = Math.min(0.05, (timestamp - this.lastTime) / 1000);
    this.lastTime = timestamp;

    if (!this.isPaused && this.isRunning) {
      this.update(dt);
    }

    this.render();
    requestAnimationFrame((t) => this.gameLoop(t));
  }

  update(dt) {
    const zoneDef = ZONES.find(z => z.id === this.currentZoneId);

    // Calculate Mouse in World Position based on Camera
    this.mouseWorldPos.x = this.mouseScreenPos.x + this.camX;
    this.mouseWorldPos.y = this.mouseScreenPos.y + this.camY;

    // 1. Update Player
    this.player.update(dt, this.keys, this.mouseWorldPos, this.walls, this.sound, this.particles, this.floatingTexts, this.currentZoneId);

    // 2. Check Zone Exit Gateway to next zone
    if (this.currentZoneId < 6) {
      if (this.player.x > zoneDef.width - 50 && Math.abs(this.player.y - zoneDef.height / 2) < 80) {
        this.loadZone(this.currentZoneId + 1);
        return;
      }
    }

    // 3. Update Enemies
    for (const enemy of this.enemies) {
      enemy.update(dt, this.player, this.walls, this.sound, this.particles, this.floatingTexts);
    }

    // 4. Update Sanctuary Stones
    for (const s of this.sanctuaryStones) {
      s.update(dt, this.particles);
    }

    // 5. Update Pressure Plates
    for (const p of this.pressurePlates) {
      p.update(this.player, this.enemies, this.sound);
    }

    // 6. Update Projectiles
    for (let i = this.projectiles.length - 1; i >= 0; i--) {
      const proj = this.projectiles[i];
      proj.update(dt, this.particles);

      let collided = false;

      // Projectile vs Walls
      for (const w of this.walls) {
        if (w.broken) continue;
        if (pointInRect(proj.x, proj.y, w.x, w.y, w.w, w.h)) {
          collided = true;
          if (w.isCrack) {
            w.hit(proj.damage, this.sound, this.particles);
          }
          break;
        }
      }

      // Projectile vs Ranged Switches
      if (!collided && proj.isPlayer) {
        for (const sw of this.rangedSwitches) {
          const d = Math.hypot(sw.x - proj.x, sw.y - proj.y);
          if (d < sw.radius + proj.radius) {
            sw.hit(this.sound, this.particles, this.floatingTexts);
            collided = true;
            break;
          }
        }
      }

      // Projectile vs Enemies (if fired by player)
      if (!collided && proj.isPlayer) {
        for (const enemy of this.enemies) {
          if (enemy.isDead) continue;
          const d = Math.hypot(enemy.x - proj.x, enemy.y - proj.y);
          if (d < enemy.radius + proj.radius) {
            enemy.takeDamage(proj.damage, this.sound, this.particles, this.floatingTexts, 'aether');
            collided = true;
            break;
          }
        }
      }

      // Projectile vs Player (if fired by enemy/boss)
      if (!collided && !proj.isPlayer) {
        const d = Math.hypot(this.player.x - proj.x, this.player.y - proj.y);
        if (d < this.player.radius + proj.radius) {
          this.player.takeDamage(proj.damage, this.sound, this.particles, this.floatingTexts);
          collided = true;
        }
      }

      if (collided || proj.life <= 0) {
        this.projectiles.splice(i, 1);
      }
    }

    // 7. Update Collectibles & Loot
    for (const loot of this.goldLoots) {
      loot.update(dt, this.player, this.sound, this.particles, this.floatingTexts);
    }
    for (const heart of this.healthContainerLoots) {
      heart.update(dt, this.player, this.sound, this.particles, this.floatingTexts);
    }
    for (const item of this.worldItems) {
      item.update(dt, this.player, this.sound, this.particles, this.floatingTexts);
    }

    // 8. Update Beacon Altar
    if (this.beaconAltar) {
      this.beaconAltar.update(dt);
    }

    // 9. Update Particles & Floating Text
    for (let i = this.particles.length - 1; i >= 0; i--) {
      this.particles[i].update(dt);
      if (this.particles[i].life <= 0) this.particles.splice(i, 1);
    }
    for (let i = this.floatingTexts.length - 1; i >= 0; i--) {
      this.floatingTexts[i].update(dt);
      if (this.floatingTexts[i].life <= 0) this.floatingTexts.splice(i, 1);
    }

    // 10. Update Camera to follow player smoothly
    const targetCamX = this.player.x - this.canvas.width / 2;
    const targetCamY = this.player.y - this.canvas.height / 2;
    this.camX += (targetCamX - this.camX) * 0.1;
    this.camY += (targetCamY - this.camY) * 0.1;

    // Clamp Camera within zone bounds
    this.camX = Math.max(0, Math.min(zoneDef.width - this.canvas.width, this.camX));
    this.camY = Math.max(0, Math.min(zoneDef.height - this.canvas.height, this.camY));

    // 11. Check Interaction Prompts
    this.checkInteractionPrompts();
  }

  checkInteractionPrompts() {
    const prompt = document.getElementById('interaction-prompt');
    let show = false;
    let text = "[E] Interact";

    // Sanctuary stone prompt
    for (const s of this.sanctuaryStones) {
      const d = Math.hypot(s.x - this.player.x, s.y - this.player.y);
      if (d < 50) {
        show = true;
        text = "[E] Rest at Sanctuary Stone";
        break;
      }
    }

    // Weapon Altar prompt
    if (!show) {
      for (const a of this.weaponAltars) {
        if (!a.activated) {
          const d = Math.hypot(a.x - this.player.x, a.y - this.player.y);
          if (d < 55) {
            show = true;
            text = "[E] Claim Weapon Upgrade";
            break;
          }
        }
      }
    }

    // Shop prompt in Zone 2
    if (!show && this.currentZoneId === 2) {
      const d = Math.hypot(700 - this.player.x, 250 - this.player.y);
      if (d < 70) {
        show = true;
        text = "[E] Trade with Observatory Altar";
      }
    }

    // Beacon Altar in Zone 6
    if (!show && this.beaconAltar && !this.beaconAltar.activated) {
      const d = Math.hypot(this.beaconAltar.x - this.player.x, this.beaconAltar.y - this.player.y);
      if (d < 75) {
        show = true;
        text = "[E] Reignite Core Beacon";
      }
    }

    if (show) {
      prompt.innerText = text;
      prompt.style.display = 'block';
    } else {
      prompt.style.display = 'none';
    }
  }

  render() {
    const zoneDef = ZONES.find(z => z.id === this.currentZoneId);
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

    this.ctx.save();
    this.ctx.translate(-this.camX, -this.camY);

    // 1. Draw Floor Tile Grid
    this.ctx.fillStyle = zoneDef.bgFloor;
    this.ctx.fillRect(0, 0, zoneDef.width, zoneDef.height);

    // Floor tile lines
    this.ctx.strokeStyle = 'rgba(255,255,255,0.03)';
    this.ctx.lineWidth = 1;
    for (let x = 0; x < zoneDef.width; x += 60) {
      this.ctx.beginPath();
      this.ctx.moveTo(x, 0);
      this.ctx.lineTo(x, zoneDef.height);
      this.ctx.stroke();
    }
    for (let y = 0; y < zoneDef.height; y += 60) {
      this.ctx.beginPath();
      this.ctx.moveTo(0, y);
      this.ctx.lineTo(zoneDef.width, y);
      this.ctx.stroke();
    }

    // Zone 2: Merchant Altar pedestal in world
    if (this.currentZoneId === 2) {
      this.ctx.fillStyle = '#334155';
      this.ctx.strokeStyle = '#f4c542';
      this.ctx.lineWidth = 2;
      this.ctx.fillRect(660, 210, 80, 80);
      this.ctx.strokeRect(660, 210, 80, 80);
      // Floating merchant gold orb
      this.ctx.fillStyle = '#f59e0b';
      this.ctx.shadowBlur = 14;
      this.ctx.shadowColor = '#fde047';
      this.ctx.beginPath();
      this.ctx.arc(700, 250, 16, 0, Math.PI * 2);
      this.ctx.fill();
      this.ctx.shadowBlur = 0;
    }

    // 2. Draw Pressure Plates & Ranged Switches
    for (const p of this.pressurePlates) p.draw(this.ctx);
    for (const s of this.rangedSwitches) s.draw(this.ctx);

    // 3. Draw Sanctuary Stones & Altars
    for (const s of this.sanctuaryStones) s.draw(this.ctx);
    for (const a of this.weaponAltars) a.draw(this.ctx);
    if (this.beaconAltar) this.beaconAltar.draw(this.ctx);

    // 4. Draw Collectibles & Loot
    for (const l of this.goldLoots) l.draw(this.ctx);
    for (const h of this.healthContainerLoots) h.draw(this.ctx);
    for (const item of this.worldItems) item.draw(this.ctx);

    // 5. Draw Walls & Gates
    const phantomActive = this.player.equipped.phantomLantern;
    for (const w of this.walls) w.draw(this.ctx, phantomActive);
    for (const d of this.doors) d.draw(this.ctx);

    // 6. Draw Exit Portal to next zone (if not in final zone)
    if (this.currentZoneId < 6) {
      const exitY = zoneDef.height / 2 - 80;
      this.ctx.fillStyle = 'rgba(244, 197, 66, 0.15)';
      this.ctx.strokeStyle = '#f4c542';
      this.ctx.lineWidth = 2;
      this.ctx.fillRect(zoneDef.width - 40, exitY, 40, 160);
      this.ctx.strokeRect(zoneDef.width - 40, exitY, 40, 160);

      // Gateway chevron indicator
      this.ctx.fillStyle = '#fde047';
      this.ctx.beginPath();
      this.ctx.moveTo(zoneDef.width - 25, zoneDef.height / 2 - 12);
      this.ctx.lineTo(zoneDef.width - 10, zoneDef.height / 2);
      this.ctx.lineTo(zoneDef.width - 25, zoneDef.height / 2 + 12);
      this.ctx.fill();
    }

    // 7. Draw Enemies
    for (const enemy of this.enemies) enemy.draw(this.ctx);

    // 8. Draw Player
    this.player.draw(this.ctx);

    // 9. Draw Projectiles & Particles
    for (const p of this.projectiles) p.draw(this.ctx);
    for (const p of this.particles) p.draw(this.ctx);
    for (const txt of this.floatingTexts) txt.draw(this.ctx);

    this.ctx.restore();
  }
}

// Instantiate global engine
let game = null;
window.addEventListener('DOMContentLoaded', () => {
  game = new GameEngine();
});
</script>
</body>
</html>
<!-- agent-meta {"model":"google/gemini-3.7-flash","provider":"openrouter","persona":"full","sessionId":"4631c5ae-02c0-4e8b-a563-f0a5bc69f641","tokensIn":670043,"tokensOut":44195,"tokensTotal":714238,"cost":0.30092883749999993,"turns":18,"toolCalls":17,"failedToolCalls":0,"timestamp":"2026-08-17T17:16:24.309Z"} -->