← Aethelgard results

Aethelgard

inkling · full

Initial view of Aethelgard

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

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 57,470 bytes · SHA-256 cc7df757e069
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aether Prisms — Resonants of Aethelgard</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&family=Press+Start+2P&display=swap" rel="stylesheet">
<style>
  :root {
    --bg-deep: #0b0c15;
    --glass: rgba(255,255,255,0.06);
    --glass-border: rgba(255,255,255,0.08);
    --text-main: #e8e6f3;
    --text-dim: #a09fb8;
    --text-muted: #6b6880;
  }
  * { box-sizing: border-box; }
  html, body { background: var(--bg-deep); color: var(--text-main); font-family: 'Inter', sans-serif; overflow-x: hidden; height: 100vh; }
  .font-retro { font-family: 'Press Start 2P', cursive; }
  .glass-card { background: linear-gradient(135deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.01) 100%); backdrop-filter: blur(14px) saturate(140%); border: 1px solid var(--glass-border); border-radius: 1.25rem; box-shadow: 0 8px 32px 0 rgba(0,0,0,0.4), inset 0 0 0 1px rgba(255,255,255,0.03); }
  .glass-card:hover { border-color: rgba(255,255,255,0.15); box-shadow: 0 12px 40px 0 rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.06) inset; }
  .btn-glass { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); border-radius: 0.75rem; padding: 0.6rem 1.2rem; color: var(--text-main); transition: all .2s ease; backdrop-filter: blur(8px); }
  .btn-glass:hover { background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.25); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255,255,255,0.05); }
  .glow-pyro { box-shadow: 0 0 20px rgba(255,100,50,0.35), 0 0 60px rgba(255,100,50,0.15); border-color: rgba(255,120,70,0.4) !important; }
  .glow-hydro { box-shadow: 0 0 20px rgba(60,150,255,0.35), 0 0 60px rgba(60,150,255,0.15); border-color: rgba(80,170,255,0.4) !important; }
  .glow-flora { box-shadow: 0 0 20px rgba(50,220,100,0.35), 0 0 60px rgba(50,220,100,0.15); border-color: rgba(70,240,120,0.4) !important; }
  .glow-zephyr { box-shadow: 0 0 20px rgba(130,220,200,0.35), 0 0 60px rgba(130,220,200,0.15); border-color: rgba(150,240,220,0.4) !important; }
  .glow-earth { box-shadow: 0 0 20px rgba(180,160,80,0.35), 0 0 60px rgba(180,160,80,0.15); border-color: rgba(200,180,100,0.4) !important; }
  .glow-volt { box-shadow: 0 0 20px rgba(60,240,255,0.35), 0 0 60px rgba(60,240,255,0.15); border-color: rgba(80,255,255,0.4) !important; }
  .glow-cosmos { box-shadow: 0 0 20px rgba(200,160,255,0.35), 0 0 60px rgba(200,160,255,0.15); border-color: rgba(220,180,255,0.4) !important; }
  @keyframes fadeSlideUp { from { opacity:0; transform: translateY(16px); } to { opacity:1; transform: translateY(0); } }
  @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
  @keyframes shake { 0%, 100% { transform: translateX(0); } 10% { transform: translateX(-6px); } 30% { transform: translateX(6px); } 50% { transform: translateX(-4px); } 70% { transform: translateX(4px); } 90% { transform: translateX(-2px); } }
  @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
  .anim-fade-slide { animation: fadeSlideUp 0.5s ease-out forwards; }
  .anim-shake { animation: shake 0.35s ease-in-out; }
  .anim-float { animation: float 4s ease-in-out infinite; }
  .hp-green { background: linear-gradient(90deg, #34d399, #10b981); }
  .hp-yellow { background: linear-gradient(90deg, #fbbf24, #f59e0b); }
  .hp-red { background: linear-gradient(90deg, #f87171, #ef4444); }
  ::-webkit-scrollbar { width: 8px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 4px; }
</style>
</head>
<body class="min-h-screen flex flex-col">

<!-- Audio Context -->
<script>
window.addEventListener('click', () => {
  if (window.AudioCtx && window.AudioCtx.state === 'suspended') window.AudioCtx.resume();
}, { once: true });
</script>

<div id="app" class="relative w-full h-screen overflow-hidden flex flex-col">

  <!-- Background ambient gradients -->
  <div class="absolute inset-0 pointer-events-none z-0">
    <div class="absolute top-[-10%] left-[-10%] w-[60vw] h-[60vw] rounded-full bg-violet-900/40 blur-[100px] opacity-60"></div>
    <div class="absolute bottom-[-10%] right-[-10%] w-[60vw] h-[60vw] rounded-full bg-indigo-900/40 blur-[100px] opacity-60"></div>
  </div>

  <!-- Screen: MAIN MENU -->
  <section id="screen-menu" class="absolute inset-0 z-10 flex flex-col items-center justify-center gap-8 anim-fade-slide">
    <div class="text-center space-y-2">
      <h1 class="font-retro text-4xl md:text-6xl tracking-tight text-transparent bg-clip-text bg-gradient-to-br from-violet-200 via-cyan-200 to-rose-200 drop-shadow-[0_0_25px_rgba(200,180,255,0.4)]">AETHER PRISMS</h1>
      <p class="text-[10px] md:text-xs uppercase tracking-[0.3em] text-violet-300/80">Resonants of Aethelgard</p>
    </div>
    <div class="glass-card p-8 md:p-10 max-w-md w-[92%] flex flex-col gap-4">
      <button onclick="startAdventure()" class="btn-glass font-retro text-sm md:text-base py-4 w-full hover:scale-[1.02]">New Conductor</button>
      <button onclick="loadGame()" id="btn-continue" class="btn-glass font-retro text-sm md:text-base py-4 w-full opacity-60" disabled>Continue Journey</button>
      <div class="flex gap-3">
        <button onclick="openDex()" class="btn-glass flex-1 text-xs uppercase tracking-wide">Dex</button>
        <button onclick="resetProgress()" class="btn-glass flex-1 text-xs uppercase tracking-wide text-rose-300 border-rose-500/20 hover:border-rose-400/40">Reset</button>
      </div>
    </div>
    <div class="text-xs text-violet-300/40 max-w-md text-center px-6">A turn-based Resonant collection RPG. Capture spirits, master elements, and become the ultimate Conductor.</div>
  </section>

  <!-- Screen: OVERWORLD -->
  <section id="screen-overworld" class="hidden absolute inset-0 z-10 flex flex-col">
    <header class="flex items-center justify-between px-6 py-4 glass-card rounded-none border-x-0 border-t-0 bg-black/20 backdrop-blur-md">
      <div class="flex items-center gap-3">
        <div class="w-10 h-10 rounded-full bg-gradient-to-br from-violet-500 to-indigo-600 shadow-lg shadow-violet-900/40 flex items-center justify-center text-xs font-retro">C</div>
        <div>
          <h2 class="font-retro text-sm leading-tight">Conductor</h2>
          <span id="overworld-shards" class="text-[10px] text-amber-300">0 Aether Shards</span>
        </div>
      </div>
      <div class="text-xs text-violet-200/70">Zone <span id="current-zone-display">1</span></div>
    </header>
    <main class="flex-1 overflow-y-auto p-6 md:p-8">
      <div class="max-w-3xl mx-auto space-y-6">
        <div class="glass-card p-6">
          <h2 id="zone-title" class="font-retro text-lg mb-1 text-violet-200">The Verdant Canopy</h2>
          <p id="zone-desc" class="text-xs text-violet-100/60 leading-relaxed">Ancient trees humming with Flora and Zephyr resonance. Begin your training here.</p>
        </div>
        <div id="node-grid" class="grid grid-cols-2 md:grid-cols-4 gap-4"></div>
      </div>
    </main>
    <footer class="p-4 flex justify-between items-center glass-card rounded-none border-x-0 border-b-0 bg-black/20">
      <button onclick="openShop()" class="btn-glass text-xs">Camp Shop</button>
      <button onclick="openMenu()" class="btn-glass text-xs">Menu</button>
    </footer>
  </section>

  <!-- Screen: BATTLE -->
  <section id="screen-battle" class="hidden absolute inset-0 z-20 flex flex-col bg-gradient-to-b from-[#0f101a] via-[#12122a] to-[#0f101a]">
    <!-- Arena top -->
    <div class="flex-1 relative overflow-hidden">
      <div class="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.03),transparent_70%)]"></div>
      <!-- Enemy -->
      <div class="absolute top-12 right-8 md:right-16 flex flex-col items-end gap-2">
        <div id="enemy-card" class="glass-card p-3 min-w-[140px] text-right glow-pyro">
          <div class="font-retro text-xs text-violet-200 mb-1" id="enemy-name">Emberkit</div>
          <div class="text-[10px] text-violet-100/50 mb-2">Lv <span id="enemy-lv">3</span> · <span id="enemy-type" class="uppercase tracking-wider">Pyro</span></div>
          <div class="w-full h-2 rounded-full bg-black/30 overflow-hidden mb-1">
            <div id="enemy-hp-bar" class="h-full w-full hp-green rounded-full transition-all duration-300"></div>
          </div>
          <div class="text-[9px] text-violet-200/40">HP <span id="enemy-hp-text">30/30</span></div>
        </div>
        <div id="enemy-sprite" class="w-24 h-24 md:w-32 md:h-32 drop-shadow-[0_0_20px_rgba(255,100,50,0.3)]"></div>
      </div>
      <!-- Player -->
      <div class="absolute bottom-16 left-6 md:left-16 flex flex-col items-start gap-2">
        <div id="player-card" class="glass-card p-3 min-w-[140px] glow-hydro">
          <div class="font-retro text-xs text-violet-200 mb-1" id="player-name">Droplet</div>
          <div class="text-[10px] text-violet-100/50 mb-2">Lv <span id="player-lv">3</span> · <span id="player-type" class="uppercase tracking-wider">Hydro</span></div>
          <div class="w-full h-2 rounded-full bg-black/30 overflow-hidden mb-1">
            <div id="player-hp-bar" class="h-full w-full hp-green rounded-full transition-all duration-300"></div>
          </div>
          <div class="text-[9px] text-violet-200/40">HP <span id="player-hp-text">30/30</span></div>
        </div>
        <div id="player-sprite" class="w-24 h-24 md:w-32 md:h-32 drop-shadow-[0_0_20px_rgba(60,150,255,0.3)]"></div>
      </div>
    </div>

    <!-- Log & Actions -->
    <div class="z-10 p-4 md:p-6 space-y-4">
      <div id="battle-log" class="glass-card p-4 min-h-[80px] text-sm leading-relaxed text-violet-100/90 shadow-inner">
        <span class="text-amber-300 font-retro text-xs">Battle Start!</span><br>
        <span id="log-text">A wild Emberkit appears!</span>
      </div>
      <div id="battle-actions" class="grid grid-cols-2 md:grid-cols-4 gap-3">
        <button onclick="battleAction('attack')" class="btn-glass text-xs md:text-sm">Attack</button>
        <button onclick="battleAction('prism')" class="btn-glass text-xs md:text-sm">Prism</button>
        <button onclick="battleAction('swap')" class="btn-glass text-xs md:text-sm">Exchange</button>
        <button onclick="battleAction('item')" class="btn-glass text-xs md:text-sm">Item</button>
      </div>
      <div id="move-panel" class="hidden grid grid-cols-2 gap-2"></div>
      <div id="prism-panel" class="hidden flex gap-2 flex-wrap"></div>
      <div id="item-panel" class="hidden grid grid-cols-2 gap-2"></div>
    </div>
  </section>

  <!-- Screen: DEX -->
  <section id="screen-dex" class="hidden absolute inset-0 z-30 bg-gradient-to-b from-[#0f101a] via-[#12122a] to-[#0f101a] overflow-y-auto">
    <header class="sticky top-0 z-10 glass-card rounded-none border-x-0 border-t-0 bg-black/30 backdrop-blur-lg px-6 py-4 flex items-center justify-between">
      <h2 class="font-retro text-xl text-violet-200">Conductor's Dex</h2>
      <button onclick="closeScreen('dex')" class="btn-glass text-xs">Close</button>
    </header>
    <main class="p-6 md:p-10 max-w-5xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-6" id="dex-grid"></main>
  </section>

  <!-- Screen: SHOP -->
  <section id="screen-shop" class="hidden absolute inset-0 z-30 bg-gradient-to-b from-[#0f101a] via-[#12122a] to-[#0f101a] overflow-y-auto">
    <header class="sticky top-0 z-10 glass-card rounded-none border-x-0 border-t-0 bg-black/30 backdrop-blur-lg px-6 py-4 flex items-center justify-between">
      <h2 class="font-retro text-xl text-violet-200">Camp Shop</h2>
      <button onclick="closeScreen('shop')" class="btn-glass text-xs">Leave</button>
    </header>
    <main class="p-6 md:p-10 max-w-4xl mx-auto space-y-6" id="shop-grid"></main>
  </section>

  <!-- Screen: MENU OVERLAY -->
  <section id="screen-menu-overlay" class="hidden absolute inset-0 z-40 bg-black/60 backdrop-blur-sm flex items-center justify-center">
    <div class="glass-card p-8 max-w-sm w-[90%] space-y-4 text-center">
      <h3 class="font-retro text-xl text-violet-200 mb-4">Conductor Menu</h3>
      <button onclick="saveGame(); showToast('Progress saved.')" class="btn-glass w-full">Save Progress</button>
      <button onclick="closeScreen('menu-overlay')" class="btn-glass w-full">Resume</button>
      <button onclick="openMenu(); resetProgress()" class="btn-glass w-full text-rose-300">Reset Journey</button>
    </div>
  </section>

  <!-- Toast -->
  <div id="toast" class="absolute bottom-6 left-1/2 -translate-x-1/2 z-50 px-4 py-2 rounded-full bg-violet-900/80 text-violet-200 text-xs backdrop-blur-md border border-violet-400/20 opacity-0 transition-opacity duration-300 pointer-events-none shadow-xl shadow-violet-900/40"></div>

</div>

<script>
// ============================================================
// AUDIO SYNTH
// ============================================================
const AudioCtx = (() => { try { return (window.AudioContext || window.webkitAudioContext) ? new (window.AudioContext || window.webkitAudioContext)() : null; } catch(e){ return null; } })();

class Synth {
  constructor() { this.ctx = AudioCtx; }
  play(type) {
    if (!this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.connect(gain); gain.connect(this.ctx.destination);
      const now = this.ctx.currentTime;
      if (type === 'hover') { osc.type = 'sine'; osc.frequency.setValueAtTime(880, now); osc.frequency.exponentialRampToValueAtTime(1100, now + 0.05); gain.gain.setValueAtTime(0.05, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15); osc.start(now); osc.stop(now + 0.15); }
      else if (type === 'select') { osc.type = 'triangle'; osc.frequency.setValueAtTime(660, now); osc.frequency.linearRampToValueAtTime(990, now + 0.05); gain.gain.setValueAtTime(0.08, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.2); osc.start(now); osc.stop(now + 0.2); }
      else if (type === 'hit-phys') { osc.type = 'square'; osc.frequency.setValueAtTime(200, now); osc.frequency.linearRampToValueAtTime(80, now + 0.1); gain.gain.setValueAtTime(0.1, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.25); osc.start(now); osc.stop(now + 0.25); }
      else if (type === 'hit-elem') { osc.type = 'sine'; osc.frequency.setValueAtTime(400, now); osc.frequency.setValueAtTime(600, now + 0.08); osc.frequency.setValueAtTime(800, now + 0.15); gain.gain.setValueAtTime(0.08, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3); osc.start(now); osc.stop(now + 0.3); }
      else if (type === 'level') { osc.type = 'triangle'; [880, 1100, 1320, 1760].forEach((f, i) => { osc.frequency.setValueAtTime(f, now + i*0.08); }); gain.gain.setValueAtTime(0.1, now); gain.gain.linearRampToValueAtTime(0, now + 0.5); osc.start(now); osc.stop(now + 0.5); }
      else if (type === 'capture') { osc.type = 'sine'; [440, 660, 880, 1320].forEach((f, i) => { osc.frequency.setValueAtTime(f, now + i*0.06); }); gain.gain.setValueAtTime(0.1, now); gain.gain.exponentialRampToValueAtTime(0.01, now + 0.5); osc.start(now); osc.stop(now + 0.5); }
    } catch(e) {}
  }
}
const synth = new Synth();

// ============================================================
// DATA: RESONANTS (18 full entries)
// ============================================================
const ELEMENTS = {
  Pyro: { color: 'text-orange-300', glow: 'glow-pyro', gradient: 'from-orange-500 to-rose-500', bg: 'bg-gradient-to-br from-orange-900/40 to-rose-900/40' },
  Hydro: { color: 'text-cyan-300', glow: 'glow-hydro', gradient: 'from-cyan-500 to-blue-500', bg: 'bg-gradient-to-br from-cyan-900/40 to-blue-900/40' },
  Flora: { color: 'text-emerald-300', glow: 'glow-flora', gradient: 'from-emerald-400 to-lime-500', bg: 'bg-gradient-to-br from-emerald-900/40 to-lime-900/40' },
  Zephyr: { color: 'text-teal-200', glow: 'glow-zephyr', gradient: 'from-teal-300 to-cyan-300', bg: 'bg-gradient-to-br from-teal-900/30 to-cyan-900/30' },
  Earth: { color: 'text-amber-200', glow: 'glow-earth', gradient: 'from-amber-400 to-yellow-700', bg: 'bg-gradient-to-br from-amber-900/30 to-yellow-900/30' },
  Volt: { color: 'text-sky-300', glow: 'glow-volt', gradient: 'from-sky-300 to-cyan-300', bg: 'bg-gradient-to-br from-sky-900/30 to-cyan-900/30' }
};

const MONSTERS = [
  // Pyro family
  { id: 'emberkit', name: 'Emberkit', type: 'Pyro', base: { hp:35, atk:12, def:8, spd:14 }, sprite: { shape:'cat', color:'#fb7185', glow:'#f87171' }, moves: [
    { name: 'Scratch', power: 40, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Ember Spark', power: 45, acc: 100, el: 'Pyro', unlock: 3 },
    { name: 'Flame Bite', power: 65, acc: 90, el: 'Pyro', unlock: 6 },
    { name: 'Inferno Surge', power: 85, acc: 85, el: 'Pyro', unlock: 10 }
  ], evoLevel: 8, evoTo: 'pyrowolf' },
  { id: 'pyrowolf', name: 'Pyrowolf', type: 'Pyro', base: { hp:65, atk:28, def:14, spd:22 }, sprite: { shape:'wolf', color:'#f97316', glow:'#fb923c' }, moves: [
    { name: 'Claw', power: 45, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Flame Bite', power: 65, acc: 90, el: 'Pyro', unlock: 3 },
    { name: 'Scorching Roar', power: 75, acc: 95, el: 'Pyro', unlock: 7 },
    { name: 'Inferno Surge', power: 85, acc: 85, el: 'Pyro', unlock: 10 }
  ], evoLevel: 15, evoTo: 'ignisaur' },
  { id: 'ignisaur', name: 'Ignisaur', type: 'Pyro', base: { hp:110, atk:48, def:32, spd:28 }, sprite: { shape:'dragon', color:'#ea580c', glow:'#f97316' }, moves: [
    { name: 'Dragon Claw', power: 80, acc: 95, el: 'Physical', unlock: 1 },
    { name: 'Scorching Roar', power: 75, acc: 95, el: 'Pyro', unlock: 3 },
    { name: 'Solar Flare', power: 100, acc: 85, el: 'Pyro', unlock: 8 },
    { name: 'Inferno Surge', power: 85, acc: 85, el: 'Pyro', unlock: 10 }
  ], evoLevel: 0, evoTo: null },

  // Hydro family
  { id: 'droplet', name: 'Droplet', type: 'Hydro', base: { hp:45, atk:10, def:14, spd:12 }, sprite: { shape:'blob', color:'#38bdf8', glow:'#7dd3fc' }, moves: [
    { name: 'Splash', power: 40, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Water Pulse', power: 50, acc: 100, el: 'Hydro', unlock: 3 },
    { name: 'Aqua Jet', power: 70, acc: 95, el: 'Hydro', unlock: 6 },
    { name: 'Tidal Wave', power: 90, acc: 85, el: 'Hydro', unlock: 10 }
  ], evoLevel: 8, evoTo: 'geoxus' },
  { id: 'geoxus', name: 'Geoxus', type: 'Hydro', base: { hp:80, atk:22, def:32, spd:18 }, sprite: { shape:'turtle', color:'#0ea5e9', glow:'#38bdf8' }, moves: [
    { name: 'Shell Slam', power: 55, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Aqua Jet', power: 70, acc: 95, el: 'Hydro', unlock: 3 },
    { name: 'Hydro Surge', power: 80, acc: 90, el: 'Hydro', unlock: 7 },
    { name: 'Tidal Wave', power: 90, acc: 85, el: 'Hydro', unlock: 10 }
  ], evoLevel: 15, evoTo: 'neptunian' },
  { id: 'neptunian', name: 'Neptunian', type: 'Hydro', base: { hp:125, atk:42, def:55, spd:24 }, sprite: { shape:'serpent', color:'#0284c7', glow:'#38bdf8' }, moves: [
    { name: 'Ocean Bite', power: 85, acc: 95, el: 'Physical', unlock: 1 },
    { name: 'Hydro Surge', power: 80, acc: 90, el: 'Hydro', unlock: 3 },
    { name: 'Abyssal Vortex', power: 105, acc: 80, el: 'Hydro', unlock: 8 },
    { name: 'Tidal Wave', power: 90, acc: 85, el: 'Hydro', unlock: 10 }
  ], evoLevel: 0, evoTo: null },

  // Flora family
  { id: 'seedling', name: 'Seedling', type: 'Flora', base: { hp:40, atk:14, def:10, spd:10 }, sprite: { shape:'flower', color:'#4ade80', glow:'#86efac' }, moves: [
    { name: 'Tackle', power: 40, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Leaf Cut', power: 50, acc: 95, el: 'Flora', unlock: 3 },
    { name: 'Thorn Whip', power: 65, acc: 90, el: 'Flora', unlock: 6 },
    { name: 'Bloom Burst', power: 80, acc: 85, el: 'Flora', unlock: 10 }
  ], evoLevel: 8, evoTo: 'thornback' },
  { id: 'thornback', name: 'Thornback', type: 'Flora', base: { hp:75, atk:30, def:22, spd:16 }, sprite: { shape:'turtle', color:'#22c55e', glow:'#4ade80' }, moves: [
    { name: 'Thorn Slam', power: 55, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Thorn Whip', power: 65, acc: 90, el: 'Flora', unlock: 3 },
    { name: 'Root Crush', power: 75, acc: 85, el: 'Flora', unlock: 7 },
    { name: 'Bloom Burst', power: 80, acc: 85, el: 'Flora', unlock: 10 }
  ], evoLevel: 15, evoTo: 'bloomgoliath' },
  { id: 'bloomgoliath', name: 'Bloomgoliath', type: 'Flora', base: { hp:120, atk:46, def:42, spd:20 }, sprite: { shape:'golem', color:'#16a34a', glow:'#22c55e' }, moves: [
    { name: 'Earth Slam', power: 80, acc: 95, el: 'Physical', unlock: 1 },
    { name: 'Root Crush', power: 75, acc: 85, el: 'Flora', unlock: 3 },
    { name: 'Photosynthesis', power: 100, acc: 80, el: 'Flora', unlock: 8 },
    { name: 'Bloom Burst', power: 80, acc: 85, el: 'Flora', unlock: 10 }
  ], evoLevel: 0, evoTo: null },

  // Zephyr family
  { id: 'gusty', name: 'Gusty', type: 'Zephyr', base: { hp:42, atk:15, def:9, spd:22 }, sprite: { shape:'bird', color:'#5eead4', glow:'#99f6e4' }, moves: [
    { name: 'Peck', power: 40, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Gust', power: 45, acc: 100, el: 'Zephyr', unlock: 3 },
    { name: 'Air Slash', power: 65, acc: 90, el: 'Zephyr', unlock: 6 },
    { name: 'Cyclone Strike', power: 85, acc: 85, el: 'Zephyr', unlock: 10 }
  ], evoLevel: 8, evoTo: 'slipstream' },
  { id: 'slipstream', name: 'Slipstream', type: 'Zephyr', base: { hp:70, atk:30, def:18, spd:32 }, sprite: { shape:'bird', color:'#2dd4bf', glow:'#5eead4' }, moves: [
    { name: 'Wing Attack', power: 55, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Air Slash', power: 65, acc: 90, el: 'Zephyr', unlock: 3 },
    { name: 'Aero Blast', power: 80, acc: 85, el: 'Zephyr', unlock: 7 },
    { name: 'Cyclone Strike', power: 85, acc: 85, el: 'Zephyr', unlock: 10 }
  ], evoLevel: 15, evoTo: 'zephyrus' },
  { id: 'zephyrus', name: 'Zephyrus', type: 'Zephyr', base: { hp:105, atk:48, def:28, spd:42 }, sprite: { shape:'dragon', color:'#14b8a6', glow:'#2dd4bf' }, moves: [
    { name: 'Sky Claw', power: 80, acc: 95, el: 'Physical', unlock: 1 },
    { name: 'Aero Blast', power: 80, acc: 85, el: 'Zephyr', unlock: 3 },
    { name: 'Tempest Surge', power: 105, acc: 80, el: 'Zephyr', unlock: 8 },
    { name: 'Cyclone Strike', power: 85, acc: 85, el: 'Zephyr', unlock: 10 }
  ], evoLevel: 0, evoTo: null },

  // Earth family
  { id: 'pebble', name: 'Pebble', type: 'Earth', base: { hp:50, atk:12, def:18, spd:8 }, sprite: { shape:'rock', color:'#d4a843', glow:'#eab308' }, moves: [
    { name: 'Roll', power: 45, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Mud Slap', power: 50, acc: 95, el: 'Earth', unlock: 3 },
    { name: 'Rock Throw', power: 65, acc: 90, el: 'Earth', unlock: 6 },
    { name: 'Earthquake', power: 85, acc: 80, el: 'Earth', unlock: 10 }
  ], evoLevel: 8, evoTo: 'obsidian' },
  { id: 'obsidian', name: 'Obsidian', type: 'Earth', base: { hp:95, atk:26, def:42, spd:14 }, sprite: { shape:'golem', color:'#a16207', glow:'#ca8a04' }, moves: [
    { name: 'Rock Slam', power: 60, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Rock Throw', power: 65, acc: 90, el: 'Earth', unlock: 3 },
    { name: 'Seismic Crush', power: 80, acc: 85, el: 'Earth', unlock: 7 },
    { name: 'Earthquake', power: 85, acc: 80, el: 'Earth', unlock: 10 }
  ], evoLevel: 15, evoTo: 'terrasunder' },
  { id: 'terrasunder', name: 'Terrasunder', type: 'Earth', base: { hp:135, atk:45, def:65, spd:20 }, sprite: { shape:'golem', color:'#854d0e', glow:'#a16207' }, moves: [
    { name: 'Mountain Slam', power: 85, acc: 95, el: 'Physical', unlock: 1 },
    { name: 'Seismic Crush', power: 80, acc: 85, el: 'Earth', unlock: 3 },
    { name: 'Tectonic Rift', power: 105, acc: 80, el: 'Earth', unlock: 8 },
    { name: 'Earthquake', power: 85, acc: 80, el: 'Earth', unlock: 10 }
  ], evoLevel: 0, evoTo: null },

  // Volt family
  { id: 'sparky', name: 'Sparky', type: 'Volt', base: { hp:38, atk:14, def:10, spd:20 }, sprite: { shape:'cat', color:'#38bdf8', glow:'#0ea5e9' }, moves: [
    { name: 'Tackle', power: 40, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Thunder Spark', power: 50, acc: 100, el: 'Volt', unlock: 3 },
    { name: 'Volt Charge', power: 65, acc: 90, el: 'Volt', unlock: 6 },
    { name: 'Lightning Storm', power: 90, acc: 85, el: 'Volt', unlock: 10 }
  ], evoLevel: 8, evoTo: 'boltsaber' },
  { id: 'boltsaber', name: 'Boltsaber', type: 'Volt', base: { hp:72, atk:32, def:20, spd:28 }, sprite: { shape:'saber', color:'#0284c7', glow:'#38bdf8' }, moves: [
    { name: 'Saber Strike', power: 60, acc: 100, el: 'Physical', unlock: 1 },
    { name: 'Volt Charge', power: 65, acc: 90, el: 'Volt', unlock: 3 },
    { name: 'Electric Surge', power: 80, acc: 85, el: 'Volt', unlock: 7 },
    { name: 'Lightning Storm', power: 90, acc: 85, el: 'Volt', unlock: 10 }
  ], evoLevel: 15, evoTo: 'stormlord' },
  { id: 'stormlord', name: 'Stormlord', type: 'Volt', base: { hp:115, atk:48, def:32, spd:36 }, sprite: { shape:'dragon', color:'#0369a1', glow:'#0284c7' }, moves: [
    { name: 'Thunder Claw', power: 85, acc: 95, el: 'Physical', unlock: 1 },
    { name: 'Electric Surge', power: 80, acc: 85, el: 'Volt', unlock: 3 },
    { name: 'Thunderstorm', power: 110, acc: 80, el: 'Volt', unlock: 8 },
    { name: 'Lightning Storm', power: 90, acc: 85, el: 'Volt', unlock: 10 }
  ], evoLevel: 0, evoTo: null }
];

function getMonster(id) { return MONSTERS.find(m => m.id === id); }
function getMonsterSVG(id, size=120) {
  const m = getMonster(id);
  if (!m) return `<svg width="${size}" height="${size}" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="30" fill="#888"/></svg>`;
  const c = m.sprite.color;
  const gl = m.sprite.glow;
  let path = '';
  if (m.sprite.shape === 'cat') path = `<path d="M30 60 Q20 30 40 25 Q50 10 60 25 Q80 30 70 60 L65 80 L35 80 Z" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="45" cy="40" r="3" fill="#fff"/><circle cx="55" cy="40" r="3" fill="#fff"/><path d="M50 52 Q48 58 50 60" stroke="#fff" stroke-width="1.5" fill="none"/>`;
  else if (m.sprite.shape === 'wolf') path = `<path d="M25 55 Q15 35 35 30 Q50 15 65 30 Q85 35 75 55 L70 80 L30 80 Z" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="42" cy="45" r="3" fill="#fff"/><circle cx="58" cy="45" r="3" fill="#fff"/><path d="M40 58 L60 58" stroke="#fff" stroke-width="2"/>`;
  else if (m.sprite.shape === 'dragon') path = `<path d="M35 70 Q20 50 30 30 Q50 10 70 30 Q80 50 65 70 L60 82 L40 82 Z" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="42" cy="45" r="4" fill="#fff"/><circle cx="58" cy="45" r="4" fill="#fff"/><path d="M48 60 Q55 70 52 75" stroke="#fff" stroke-width="2" fill="none"/>`;
  else if (m.sprite.shape === 'blob') path = `<ellipse cx="50" cy="60" rx="28" ry="22" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="42" cy="55" r="4" fill="#fff"/><circle cx="58" cy="55" r="4" fill="#fff"/><path d="M45 65 Q50 72 55 65" stroke="#fff" stroke-width="2" fill="none"/>`;
  else if (m.sprite.shape === 'turtle') path = `<ellipse cx="50" cy="55" rx="30" ry="22" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="42" cy="50" r="3" fill="#fff"/><circle cx="58" cy="50" r="3" fill="#fff"/><path d="M45 62 Q50 68 55 62" stroke="#fff" stroke-width="2" fill="none"/>`;
  else if (m.sprite.shape === 'serpent') path = `<path d="M30 50 Q40 30 50 30 Q60 30 70 50 T85 60 Q70 80 50 80 T30 60 Z" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="45" cy="45" r="3" fill="#fff"/><circle cx="55" cy="45" r="3" fill="#fff"/>`;
  else if (m.sprite.shape === 'flower') path = `<circle cx="50" cy="65" r="20" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="50" cy="45" r="10" fill="#fff" opacity="0.3"/><path d="M50 25 L50 45" stroke="${gl}" stroke-width="3"/>`;
  else if (m.sprite.shape === 'golem') path = `<rect x="25" y="40" width="50" height="40" rx="10" fill="${c}" stroke="${gl}" stroke-width="2"/><rect x="35" y="25" width="30" height="15" rx="5" fill="#ddd" opacity="0.2"/><circle cx="42" cy="50" r="3" fill="#fff"/><circle cx="58" cy="50" r="3" fill="#fff"/>`;
  else if (m.sprite.shape === 'bird') path = `<path d="M20 50 Q30 20 50 25 Q70 20 80 50 L75 70 L25 70 Z" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="45" cy="45" r="3" fill="#fff"/><circle cx="55" cy="45" r="3" fill="#fff"/><path d="M50 58 L50 65" stroke="#fff" stroke-width="2"/>`;
  else if (m.sprite.shape === 'rock') path = `<polygon points="25,70 50,30 75,70 65,85 35,85" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="45" cy="60" r="3" fill="#fff"/><circle cx="55" cy="60" r="3" fill="#fff"/>`;
  else if (m.sprite.shape === 'saber') path = `<path d="M30 40 L70 60 L60 75 L40 55 Z" fill="${c}" stroke="${gl}" stroke-width="2"/><circle cx="42" cy="52" r="3" fill="#fff"/><circle cx="58" cy="58" r="3" fill="#fff"/>`;
  else path = `<circle cx="50" cy="50" r="30" fill="${c}" stroke="${gl}" stroke-width="2"/>`;
  return `<svg width="${size}" height="${size}" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" class="drop-shadow-[0_0_15px_${m.sprite.glow}]">${path}</svg>`;
}

// ============================================================
// GAME DATA: ZONES & NODES
// ============================================================
const ZONES = [
  { id: 'canopy', name: 'Verdant Canopy', desc: 'Ancient trees humming with Flora and Zephyr resonance. Begin your training here.', levels: [1,2,3,4,5], types: ['Flora','Zephyr','Flora','Pyro','Flora'], bossId: 'bloomgoliath' },
  { id: 'grotto', name: 'Sunken Grotto', desc: 'Deep caverns echoing with Hydro and Earth energy. Watch the currents.', levels: [5,6,7,8,9,10], types: ['Hydro','Earth','Hydro','Flora','Earth','Hydro'], bossId: 'terrasunder' },
  { id: 'crags', name: 'Obsidian Crags', desc: 'Volcanic peaks crackling with Pyro and Volt storms. Only the worthy pass.', levels: [10,11,12,13,14,15], types: ['Pyro','Volt','Pyro','Flora','Volt','Pyro'], bossId: 'stormlord' },
  { id: 'apex', name: 'Apex Pillar', desc: 'The final ascension. All elements converge at the summit.', levels: [15,16,17,18,19,20], types: ['Cosmos','Pyro','Hydro','Flora','Zephyr','Earth'], bossId: 'ignisaur' }
];

function getZone(index) { return ZONES[index] || ZONES[0]; }

// ============================================================
// PLAYER STATE
// ============================================================
let PLAYER = {
  name: 'Conductor',
  shards: 50,
  zoneIndex: 0,
  zoneProgress: { canopy: 0, grotto: 0, crags: 0, apex: 0 },
  completedBosses: {},
  team: [], // objects: { id, level, xp, currentHp, maxHp, movesUnlocked }
  inventory: { prism: 5, hyperPrism: 2, coreRestorer: 3, elixir: 1 },
  dexSeen: new Set(),
  unlockedZones: ['canopy']
};

function initPlayer() {
  PLAYER.team = [
    { id: 'droplet', level: 3, xp: 0, currentHp: 45, maxHp: 45, movesUnlocked: [1,3,6,10] },
    { id: 'gusty', level: 3, xp: 0, currentHp: 42, maxHp: 42, movesUnlocked: [1,3,6,10] },
  ];
}

function getCombatStats(monsterId, level) {
  const base = getMonster(monsterId).base;
  // Simple scaling
  const scale = 1 + (level - 1) * 0.15;
  return {
    hp: Math.round(base.hp * scale),
    atk: Math.round(base.atk * scale),
    def: Math.round(base.def * scale),
    spd: Math.round(base.spd * scale)
  };
}

function getTeamStats(id) {
  const p = PLAYER.team.find(t => t.id === id);
  const m = getMonster(id);
  if (!p) return getCombatStats(id, 1);
  return getCombatStats(id, p.level);
}

function evolveIfReady(id) {
  const p = PLAYER.team.find(t => t.id === id);
  if (!p) return false;
  const m = getMonster(id);
  if (m.evoLevel > 0 && p.level >= m.evoLevel && m.evoTo) {
    p.id = m.evoTo;
    p.maxHp = getCombatStats(p.id, p.level).hp;
    p.currentHp = p.maxHp;
    synth.play('level');
    showToast(`${m.name} evolved into ${getMonster(p.id).name}!`);
    return true;
  }
  return false;
}

// ============================================================
// SAVE / LOAD
// ============================================================
function saveGame() {
  const data = JSON.stringify({ team: PLAYER.team, shards: PLAYER.shards, zoneIndex: PLAYER.zoneIndex, zoneProgress: PLAYER.zoneProgress, completedBosses: PLAYER.completedBosses, unlockedZones: PLAYER.unlockedZones, inventory: PLAYER.inventory, dexSeen: Array.from(PLAYER.dexSeen) });
  localStorage.setItem('aether_save', data);
  showToast('Progress saved to Aether Core');
  document.getElementById('btn-continue').disabled = false;
  document.getElementById('btn-continue').classList.remove('opacity-60');
}

function loadGame() {
  const raw = localStorage.getItem('aether_save');
  if (!raw) { showToast('No saved data found.'); return; }
  try {
    const data = JSON.parse(raw);
    PLAYER.shards = data.shards || 0;
    PLAYER.zoneIndex = data.zoneIndex || 0;
    PLAYER.zoneProgress = data.zoneProgress || { canopy: 0, grotto: 0, crags: 0, apex: 0 };
    PLAYER.completedBosses = data.completedBosses || {};
    PLAYER.unlockedZones = data.unlockedZones || ['canopy'];
    PLAYER.inventory = data.inventory || { prism: 5, hyperPrism: 2, coreRestorer: 3, elixir: 1 };
    PLAYER.dexSeen = new Set(data.dexSeen || []);
    PLAYER.team = data.team || [];
    startOverworld();
    synth.play('select');
  } catch (e) { showToast('Corrupted save.'); }
}

function resetProgress() {
  if (!confirm('Reset all progress?')) return;
  localStorage.removeItem('aether_save');
  initPlayer();
  PLAYER.shards = 50;
  PLAYER.zoneIndex = 0;
  PLAYER.zoneProgress = { canopy: 0, grotto: 0, crags: 0, apex: 0 };
  PLAYER.completedBosses = {};
  PLAYER.unlockedZones = ['canopy'];
  PLAYER.inventory = { prism: 5, hyperPrism: 2, coreRestorer: 3, elixir: 1 };
  PLAYER.dexSeen = new Set();
  document.getElementById('btn-continue').disabled = true;
  document.getElementById('btn-continue').classList.add('opacity-60');
  showToast('Aether Prisms reset');
  openMenu();
}

// ============================================================
// UI & RENDER
// ============================================================
function showScreen(id) {
  document.querySelectorAll('#app > section').forEach(s => s.classList.add('hidden'));
  const el = document.getElementById('screen-' + id);
  if (el) { el.classList.remove('hidden'); el.classList.add('anim-fade-slide'); }
}

function openMenu() { showScreen('menu'); }
function openOverworld() { showScreen('overworld'); renderOverworld(); }
function openBattle() { showScreen('battle'); }
function openDex() { showScreen('dex'); renderDex(); }
function openShop() { showScreen('shop'); renderShop(); }
function closeScreen(suffix) { if (suffix === 'menu-overlay') { document.getElementById('screen-menu-overlay').classList.add('hidden'); return; } if (suffix === 'dex') { openOverworld(); } else if (suffix === 'shop') { openOverworld(); } }

function startAdventure() {
  initPlayer();
  PLAYER.unlockedZones = ['canopy'];
  PLAYER.zoneIndex = 0;
  saveGame();
  openOverworld();
  synth.play('select');
}

function startOverworld() {
  openOverworld();
}

function showToast(msg) {
  const t = document.getElementById('toast');
  t.textContent = msg;
  t.classList.remove('opacity-0');
  setTimeout(() => t.classList.add('opacity-0'), 1800);
}

// ============================================================
// OVERWORLD RENDER
// ============================================================
function renderOverworld() {
  const zone = getZone(PLAYER.zoneIndex);
  document.getElementById('zone-title').textContent = zone.name;
  document.getElementById('zone-desc').textContent = zone.desc;
  document.getElementById('current-zone-display').textContent = PLAYER.zoneIndex + 1;
  document.getElementById('overworld-shards').textContent = PLAYER.shards + ' Aether Shards';

  const grid = document.getElementById('node-grid');
  grid.innerHTML = '';
  zone.types.forEach((type, i) => {
    const progress = PLAYER.zoneProgress[zone.id] || 0;
    const unlocked = i <= progress || i === 0;
    const btn = document.createElement('button');
    btn.className = `glass-card p-4 flex flex-col gap-2 text-left transition-all hover:scale-[1.03] ${!unlocked ? 'opacity-40 pointer-events-none' : ''}`;
    btn.innerHTML = `<div class="flex items-center justify-between"><span class="font-retro text-xs">Node ${i+1}</span><span class="text-[10px] text-violet-200/50">${type}</span></div>
      <div class="text-xs text-violet-100/70">${i === zone.types.length - 1 ? 'Zone Gatekeeper' : (i === 0 ? 'Wild Encounter' : 'Encounter / Cache')}</div>`;
    btn.onclick = () => visitNode(i, zone);
    grid.appendChild(btn);
  });
}

function visitNode(index, zone) {
  if (!zone) return;
  const progress = PLAYER.zoneProgress[zone.id] || 0;
  if (index > progress + 1 && index !== progress) {
    showToast('Path ahead blocked. Defeat current challenge.');
    return;
  }
  // Update progress
  if (index > PLAYER.zoneProgress[zone.id]) {
    PLAYER.zoneProgress[zone.id] = index;
  }

  // Random encounter type based on node index
  if (index === zone.types.length - 1) {
    // Boss
    startBossBattle(zone.bossId, zone);
  } else if (index === 0) {
    startBattle(zone.types[index], 3 + PLAYER.zoneIndex);
  } else {
    const roll = Math.random();
    if (roll < 0.6) startBattle(zone.types[index], 2 + PLAYER.zoneIndex + Math.floor(index/2));
    else if (roll < 0.85) findCache();
    else startNpcBattle();
  }
  saveGame();
}

function findCache() {
  const rewards = [
    { name: 'Aether Shard', amount: 10 },
    { name: 'Aether Shard', amount: 15 },
    { name: 'Core Restorer', amount: 1 },
    { name: 'Resonance Elixir', amount: 1 },
  ];
  const r = rewards[Math.floor(Math.random() * rewards.length)];
  if (r.name === 'Aether Shard') PLAYER.shards += r.amount;
  else if (r.name === 'Core Restorer') PLAYER.inventory.coreRestorer += r.amount;
  else if (r.name === 'Resonance Elixir') PLAYER.inventory.elixir += r.amount;
  showToast(`Found cache: ${r.amount > 1 ? r.amount + 'x ' : ''}${r.name}`);
  synth.play('select');
  renderOverworld();
}

// ============================================================
// COMBAT ENGINE
// ============================================================
let BATTLE = { activePlayer: 0, enemyMonster: null, enemyLevel: 1, enemyCurrentHp: 0, enemyMaxHp: 0, turnPhase: 'start', logHistory: [] };

function startBattle(type, level) {
  const families = MONSTERS.filter(m => m.type === type);
  const seed = families[Math.floor(Math.random() * families.length)];
  if (!seed) return;
  const id = seed.id;
  const stats = getCombatStats(id, level);
  BATTLE = {
    activePlayer: 0,
    enemyMonster: id,
    enemyLevel: level,
    enemyCurrentHp: stats.hp,
    enemyMaxHp: stats.hp,
    enemyAtk: stats.atk,
    enemyDef: stats.def,
    enemySpd: stats.spd,
    turnPhase: 'player'
  };
  document.getElementById('enemy-name').textContent = seed.name;
  document.getElementById('enemy-lv').textContent = level;
  document.getElementById('enemy-type').textContent = seed.type;
  document.getElementById('enemy-hp-text').textContent = stats.hp + '/' + stats.hp;
  document.getElementById('enemy-sprite').innerHTML = getMonsterSVG(id, 120);
  document.getElementById('battle-log').innerHTML = `<span class="font-retro text-xs text-amber-300">Encounter!</span><br>A wild ${seed.name} appears! <span class="text-xs text-violet-200/50">(Lv ${level})</span>`;
  openBattle();
  renderPlayerCard();
  renderEnemyHp();
  synth.play('hover');
}

function startBossBattle(bossId, zone) {
  const seed = getMonster(bossId);
  if (!seed) return;
  const level = zone.levels[zone.levels.length - 1] || 5;
  const stats = getCombatStats(bossId, level);
  BATTLE = {
    activePlayer: 0,
    enemyMonster: bossId,
    enemyLevel: level,
    enemyCurrentHp: stats.hp,
    enemyMaxHp: stats.hp,
    enemyAtk: stats.atk,
    enemyDef: stats.def,
    enemySpd: stats.spd,
    turnPhase: 'player'
  };
  document.getElementById('enemy-name').textContent = seed.name;
  document.getElementById('enemy-lv').textContent = level;
  document.getElementById('enemy-type').textContent = seed.type;
  document.getElementById('enemy-hp-text').textContent = stats.hp + '/' + stats.hp;
  document.getElementById('enemy-sprite').innerHTML = getMonsterSVG(bossId, 140);
  document.getElementById('battle-log').innerHTML = `<span class="font-retro text-xs text-rose-300">Boss Encounter!</span><br>The Gatekeeper <span class="text-violet-200">${seed.name}</span> challenges you!`;
  openBattle();
  renderPlayerCard();
  renderEnemyHp();
  synth.play('hover');
}

function startNpcBattle() {
  const npc = { name: 'Wandering Conductor', team: ['pyrowolf', 'geoxus', 'slipstream'] };
  // Simplified: just a regular wild battle with level scaled
  startBattle('Flora', PLAYER.zoneIndex * 3 + 4);
}

function renderPlayerCard() {
  const p = PLAYER.team[BATTLE.activePlayer];
  if (!p) return;
  const m = getMonster(p.id);
  const stats = getTeamStats(p.id);
  document.getElementById('player-name').textContent = m.name;
  document.getElementById('player-lv').textContent = p.level;
  document.getElementById('player-type').textContent = m.type;
  document.getElementById('player-hp-bar').style.width = (p.currentHp / stats.hp * 100) + '%';
  document.getElementById('player-hp-text').textContent = Math.round(p.currentHp) + '/' + stats.hp;
  const pct = p.currentHp / stats.hp;
  document.getElementById('player-hp-bar').className = 'h-full rounded-full transition-all duration-300 ' + (pct > 0.5 ? 'hp-green' : pct > 0.2 ? 'hp-yellow' : 'hp-red');
  document.getElementById('player-sprite').innerHTML = getMonsterSVG(p.id, 120);
}

function renderEnemyHp() {
  const stats = getCombatStats(BATTLE.enemyMonster, BATTLE.enemyLevel);
  const pct = BATTLE.enemyCurrentHp / stats.hp;
  document.getElementById('enemy-hp-bar').style.width = (pct * 100) + '%';
  document.getElementById('enemy-hp-bar').className = 'h-full rounded-full transition-all duration-300 ' + (pct > 0.5 ? 'hp-green' : pct > 0.2 ? 'hp-yellow' : 'hp-red');
  document.getElementById('enemy-hp-text').textContent = Math.round(BATTLE.enemyCurrentHp) + '/' + stats.hp;
}

function renderMoves() {
  const p = PLAYER.team[BATTLE.activePlayer];
  if (!p) return;
  const panel = document.getElementById('move-panel');
  panel.innerHTML = '';
  panel.classList.remove('hidden');
  document.getElementById('prism-panel').classList.add('hidden');
  document.getElementById('item-panel').classList.add('hidden');
  const available = (getMonster(p.id).moves || []).filter(mv => (p.movesUnlocked || []).includes(mv.unlock));
  available.forEach(mv => {
    const btn = document.createElement('button');
    btn.className = 'btn-glass text-xs';
    btn.textContent = mv.name + ' (' + mv.power + ')';
    btn.onclick = () => executeMove(mv);
    panel.appendChild(btn);
  });
}

function renderPrisms() {
  const panel = document.getElementById('prism-panel');
  panel.innerHTML = '';
  panel.classList.remove('hidden');
  document.getElementById('move-panel').classList.add('hidden');
  document.getElementById('item-panel').classList.add('hidden');
  const tiers = [
    { name: 'Basic', rate: 0.35, count: PLAYER.inventory.prism },
    { name: 'Hyper', rate: 0.55, count: PLAYER.inventory.hyperPrism }
  ];
  tiers.forEach(t => {
    if (t.count <= 0) return;
    const btn = document.createElement('button');
    btn.className = 'btn-glass text-xs';
    btn.textContent = `${t.name} Prism (${Math.round(t.rate*100)}%) — ${t.count}`;
    btn.onclick = () => attemptCapture(t);
    panel.appendChild(btn);
  });
}

function renderSwap() {
  // Show team members to swap in
  const panel = document.getElementById('move-panel');
  panel.innerHTML = '';
  panel.classList.remove('hidden');
  document.getElementById('prism-panel').classList.add('hidden');
  document.getElementById('item-panel').classList.add('hidden');
  PLAYER.team.forEach((member, i) => {
    if (i === BATTLE.activePlayer) return;
    const btn = document.createElement('button');
    btn.className = 'btn-glass text-xs';
    btn.textContent = getMonster(member.id).name + ' Lv' + member.level;
    btn.onclick = () => swapActive(i);
    panel.appendChild(btn);
  });
}

function renderItems() {
  const panel = document.getElementById('item-panel');
  panel.innerHTML = '';
  panel.classList.remove('hidden');
  document.getElementById('move-panel').classList.add('hidden');
  document.getElementById('prism-panel').classList.add('hidden');
  const items = [
    { key: 'coreRestorer', label: 'Core Restorer (+50% HP)', count: PLAYER.inventory.coreRestorer },
    { key: 'elixir', label: 'Resonance Elixir', count: PLAYER.inventory.elixir }
  ];
  items.forEach(it => {
    if (it.count <= 0) return;
    const btn = document.createElement('button');
    btn.className = 'btn-glass text-xs';
    btn.textContent = `${it.label} — ${it.count}`;
    btn.onclick = () => useItem(it.key);
    panel.appendChild(btn);
  });
}

function battleAction(action) {
  if (BATTLE.turnPhase !== 'player') return;
  synth.play('select');
  if (action === 'attack') renderMoves();
  else if (action === 'prism') renderPrisms();
  else if (action === 'swap') renderSwap();
  else if (action === 'item') renderItems();
}

function executeMove(move) {
  BATTLE.turnPhase = 'resolving';
  const p = PLAYER.team[BATTLE.activePlayer];
  const pStats = getTeamStats(p.id);
  const eStats = { hp: BATTLE.enemyCurrentHp, atk: BATTLE.enemyAtk, def: BATTLE.enemyDef, spd: BATTLE.enemySpd };
  let damage = calculateDamage(pStats.atk, eStats.def, move.power, p.id, BATTLE.enemyMonster);
  let isCrit = Math.random() < 0.1;
  if (isCrit) damage = Math.round(damage * 1.5);
  damage = Math.max(1, Math.round(damage));
  BATTLE.enemyCurrentHp -= damage;
  BATTLE.enemyCurrentHp = Math.max(0, BATTLE.enemyCurrentHp);
  let logMsg = `${getMonster(p.id).name} used ${move.name}!`;
  if (move.el !== 'Physical') logMsg += ` <span class="text-xs text-violet-300">(${move.el})</span>`;
  if (isCrit) {
    logMsg += ` <span class="text-rose-300 font-retro text-xs">CRITICAL HIT!</span>`;
    synth.play('hit-elem');
  } else {
    synth.play(move.el === 'Physical' ? 'hit-phys' : 'hit-elem');
  }
  logMsg += ` It dealt <span class="text-rose-300">${damage}</span> damage.`;
  appendLog(logMsg);
  renderEnemyHp();
  document.getElementById('enemy-card').classList.add('anim-shake');
  setTimeout(() => document.getElementById('enemy-card').classList.remove('anim-shake'), 350);
  document.getElementById('battle-log').scrollTop = document.getElementById('battle-log').scrollHeight;

  // Evolution check
  evolveIfReady(p.id);
  gainXP(8 + BATTLE.enemyLevel);

  if (BATTLE.enemyCurrentHp <= 0) {
    appendLog(`<span class="text-emerald-300 font-retro">${getMonster(BATTLE.enemyMonster).name} fainted!</span>`);
    setTimeout(endBattle, 1000);
    return;
  }
  setTimeout(enemyTurn, 900);
}

function calculateDamage(atk, def, power, attackerId, defenderId) {
  const aMonster = getMonster(attackerId);
  const dMonster = getMonster(defenderId);
  let modifier = 1;
  // Type effectiveness
  const typeChart = {
    Pyro: { strong: 'Flora', weak: 'Hydro' },
    Hydro: { strong: 'Pyro', weak: 'Flora' },
    Flora: { strong: 'Hydro', weak: 'Pyro' },
    Zephyr: { strong: 'Earth', weak: 'Volt' },
    Earth: { strong: 'Volt', weak: 'Zephyr' },
    Volt: { strong: 'Zephyr', weak: 'Earth' }
  };
  // Simple: if attacker element is same as move element, apply to defender
  // We'll approximate by using attacker type as proxy for move type in this simple version
  // Actually let's just rely on move.el for a simpler approach in full version, but here we'll use attacker id's type for basic effectiveness.
  const aType = aMonster.type;
  const dType = dMonster.type;
  const chart = typeChart[aType];
  if (chart) {
    if (dType === chart.strong) modifier = 1.6;
    else if (dType === chart.weak) modifier = 0.7;
  }
  const rand = 0.85 + Math.random() * 0.15;
  const dmg = (((2 * BATTLE.enemyLevel / 5 + 2) * power * (atk / def)) / 50 + 2) * modifier * rand;
  return dmg;
}

function enemyTurn() {
  const p = PLAYER.team[BATTLE.activePlayer];
  const pStats = getTeamStats(p.id);
  // Simple AI: random attack
  const damage = Math.round((((2 * BATTLE.enemyLevel / 5 + 2) * 55 * (BATTLE.enemyAtk / pStats.def)) / 50 + 2) * (0.85 + Math.random() * 0.15));
  const dmg = Math.max(1, Math.round(damage));
  p.currentHp -= dmg;
  p.currentHp = Math.max(0, p.currentHp);
  appendLog(`<span class="text-violet-200">${getMonster(BATTLE.enemyMonster).name}</span> struck back! <span class="text-rose-300">${dmg}</span> damage.`);
  synth.play('hit-phys');
  renderPlayerCard();
  document.getElementById('player-card').classList.add('anim-shake');
  setTimeout(() => document.getElementById('player-card').classList.remove('anim-shake'), 350);
  document.getElementById('battle-log').scrollTop = document.getElementById('battle-log').scrollHeight;

  if (p.currentHp <= 0) {
    appendLog(`<span class="text-violet-400">${getMonster(p.id).name} is unable to continue!</span>`);
    setTimeout(() => {
      // Heal party slightly and return to overworld (simplified defeat)
      PLAYER.team.forEach(t => t.currentHp = Math.round(getTeamStats(t.id).hp * 0.3));
      saveGame();
      openOverworld();
    }, 1000);
    return;
  }
  BATTLE.turnPhase = 'player';
  renderPlayerCard();
}

function appendLog(html) {
  const log = document.getElementById('battle-log');
  const div = document.createElement('div');
  div.innerHTML = html;
  log.appendChild(div);
  log.scrollTop = log.scrollHeight;
}

function attemptCapture(tier) {
  BATTLE.turnPhase = 'resolving';
  const rate = tier.rate;
  const hpRatio = BATTLE.enemyCurrentHp / BATTLE.enemyMaxHp;
  const successRate = Math.min(0.95, rate + (1 - hpRatio) * 0.3);
  tier.count--;
  if (PLAYER.inventory.prism !== undefined && tier.name === 'Basic') PLAYER.inventory.prism--;
  else if (PLAYER.inventory.hyperPrism !== undefined && tier.name === 'Hyper') PLAYER.inventory.hyperPrism--;
  if (Math.random() < successRate) {
    PLAYER.team.push({ id: BATTLE.enemyMonster, level: BATTLE.enemyLevel, xp: 0, currentHp: BATTLE.enemyMaxHp, maxHp: BATTLE.enemyMaxHp, movesUnlocked: [1,3,6,10] });
    appendLog(`<span class="text-emerald-300 font-retro">Captured!</span> A new <span class="text-violet-200">${getMonster(BATTLE.enemyMonster).name}</span> joined the team!`);
    synth.play('capture');
    PLAYER.dexSeen.add(BATTLE.enemyMonster);
    setTimeout(endBattle, 1200);
  } else {
    appendLog(`<span class="text-rose-300">Failed!</span> The ${getMonster(BATTLE.enemyMonster).name} broke free.`);
    synth.play('hit-phys');
    setTimeout(enemyTurn, 800);
  }
  saveGame();
}

function swapActive(index) {
  BATTLE.activePlayer = index;
  BATTLE.turnPhase = 'player';
  appendLog(`Exchanged! <span class="text-violet-200">${getMonster(PLAYER.team[index].id).name}</span> entered.`);
  renderPlayerCard();
  renderMoves();
}

function useItem(key) {
  BATTLE.turnPhase = 'resolving';
  const p = PLAYER.team[BATTLE.activePlayer];
  if (key === 'coreRestorer') {
    const stats = getTeamStats(p.id);
    p.currentHp = Math.min(p.maxHp || stats.hp, p.currentHp + Math.round(stats.hp * 0.5));
    PLAYER.inventory.coreRestorer--;
    appendLog(`Used Core Restorer! <span class="text-emerald-300">+50% HP</span>`);
    synth.play('select');
  } else if (key === 'elixir') {
    p.currentHp = p.maxHp || getTeamStats(p.id).hp;
    PLAYER.inventory.elixir--;
    appendLog(`Used Resonance Elixir! <span class="text-violet-300">Status cleared.</span>`);
    synth.play('select');
  }
  renderPlayerCard();
  setTimeout(enemyTurn, 600);
  saveGame();
}

function gainXP(amount) {
  PLAYER.team.forEach(t => {
    t.xp += amount;
    // Simple level thresholds
    const thresholds = [0, 20, 60, 120, 200, 300, 450, 600, 800, 1000, 1300, 1600, 2000, 2400, 2800];
    let newLevel = t.level;
    for (let i = 1; i < thresholds.length; i++) {
      if (t.xp >= thresholds[i]) newLevel = i + 1;
    }
    if (newLevel > t.level) {
      const oldMax = t.maxHp || getTeamStats(t.id).hp;
      t.level = newLevel;
      const newStats = getTeamStats(t.id);
      t.maxHp = newStats.hp;
      t.currentHp = Math.round(t.currentHp + (newStats.hp - oldMax));
      appendLog(`<span class="text-amber-300 font-retro">Level Up!</span> ${getMonster(t.id).name} is now level ${t.level}!`);
      synth.play('level');
    }
  });
  saveGame();
}

function endBattle() {
  if (BATTLE.enemyCurrentHp <= 0) {
    PLAYER.shards += 15 + BATTLE.enemyLevel * 3;
    // Check boss defeat
    const zone = getZone(PLAYER.zoneIndex);
    if (BATTLE.enemyMonster === zone.bossId) {
      PLAYER.completedBosses[zone.id] = true;
      const nextIndex = PLAYER.zoneIndex + 1;
      if (nextIndex < ZONES.length && !PLAYER.unlockedZones.includes(ZONES[nextIndex].id)) {
        PLAYER.unlockedZones.push(ZONES[nextIndex].id);
        showToast('Zone unlocked: ' + ZONES[nextIndex].name);
      }
    }
    showToast('Victory! +' + (15 + BATTLE.enemyLevel * 3) + ' shards');
  }
  saveGame();
  openOverworld();
  renderOverworld();
}

// ============================================================
// DEX RENDER
// ============================================================
function renderDex() {
  const grid = document.getElementById('dex-grid');
  grid.innerHTML = '';
  MONSTERS.forEach(m => {
    const seen = PLAYER.dexSeen.has(m.id);
    const card = document.createElement('div');
    card.className = 'glass-card p-5 flex gap-4 items-center ' + (seen ? '' : 'opacity-50 grayscale');
    const el = ELEMENTS[m.type] || ELEMENTS.Hydro;
    card.innerHTML = `<div class="w-16 h-16 shrink-0">${seen ? getMonsterSVG(m.id, 80) : '<svg width="80" height="80" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="20" y="20" width="60" height="60" rx="10" fill="#2a2a3a"/></svg>'}</div>
    <div class="min-w-0">
      <h3 class="font-retro text-sm ${el.color} mb-1">${seen ? m.name : '???'}</h3>
      <div class="text-[10px] text-violet-200/50 mb-1">${seen ? m.type + ' · ' + m.id.toUpperCase() : 'Unknown Type'}</div>
      <div class="text-[10px] text-violet-200/40">${seen ? 'HP ' + m.base.hp + ' · ATK ' + m.base.atk + ' · DEF ' + m.base.def : 'Data hidden'}</div>
    </div>`;
    grid.appendChild(card);
  });
}

// ============================================================
// SHOP RENDER
// ============================================================
function renderShop() {
  const grid = document.getElementById('shop-grid');
  grid.innerHTML = `
    <div class="glass-card p-6"><h3 class="font-retro text-sm mb-4 text-violet-200">Aether Shards: <span class="text-amber-300">${PLAYER.shards}</span></h3>
    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
      <button onclick="buyItem('prism')" class="btn-glass text-left p-4 hover:scale-[1.01]">
        <div class="font-retro text-xs text-violet-200">Harmonizer Prism</div>
        <div class="text-[10px] text-violet-200/50">Basic capture rate. 25 shards.</div>
      </button>
      <button onclick="buyItem('hyperPrism')" class="btn-glass text-left p-4 hover:scale-[1.01]">
        <div class="font-retro text-xs text-violet-200">Hyper-Conductive Prism</div>
        <div class="text-[10px] text-violet-200/50">High capture rate. 70 shards.</div>
      </button>
      <button onclick="buyItem('coreRestorer')" class="btn-glass text-left p-4 hover:scale-[1.01]">
        <div class="font-retro text-xs text-violet-200">Core Restorer</div>
        <div class="text-[10px] text-violet-200/50">Restores 50% HP. 25 shards.</div>
      </button>
      <button onclick="buyItem('elixir')" class="btn-glass text-left p-4 hover:scale-[1.01]">
        <div class="font-retro text-xs text-violet-200">Resonance Elixir</div>
        <div class="text-[10px] text-violet-200/50">Clears status. 40 shards.</div>
      </button>
    </div></div>`;
}

function buyItem(key) {
  const costs = { prism: 25, hyperPrism: 70, coreRestorer: 25, elixir: 40 };
  if (PLAYER.shards < costs[key]) { showToast('Not enough Aether Shards'); return; }
  PLAYER.shards -= costs[key];
  PLAYER.inventory[key]++;
  showToast('Purchase complete');
  renderShop();
  synth.play('capture');
  saveGame();
}

// ============================================================
// EVENT BINDINGS & INIT
// ============================================================
function init() {
  initPlayer();
  const saved = localStorage.getItem('aether_save');
  document.getElementById('btn-continue').disabled = !saved;
  if (saved) document.getElementById('btn-continue').classList.remove('opacity-60');
  // Add hover sounds to buttons globally
  document.querySelectorAll('button').forEach(btn => {
    btn.addEventListener('mouseenter', () => synth.play('hover'));
  });
}

window.init = init;
init();
</script>

</body>
</html>
<!-- agent-meta {"model":"thinkingmachines/inkling","provider":"openrouter","persona":"full","sessionId":"06d215ba-acb9-4d40-93fc-4e7b8131707e","tokensIn":30444,"tokensOut":20547,"tokensTotal":50991,"turns":2,"toolCalls":1,"failedToolCalls":0,"timestamp":"2026-07-18T22:41:26.869Z"} -->