← Creature Collection results

Creature Collection

Qwen 3.6 35b a3b · middle

Initial view of Creature Collection

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 96,219 bytes · SHA-256 4ebcb97eb11e
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Loombound</title>
<style>
:root {
  --bg-dark: #0a0a1a;
  --bg-mid: #1a1a3a;
  --bg-light: #2a2a5a;
  --accent: #7b68ee;
  --accent2: #ff6b9d;
  --accent3: #00e5ff;
  --text: #e8e8f0;
  --text-dim: #8888aa;
  --success: #4ade80;
  --danger: #f87171;
  --warning: #fbbf24;
  --glass-bg: rgba(20, 20, 50, 0.85);
  --glass-border: rgba(123, 104, 238, 0.3);
  --flux: #a855f7;
  --stone: #9ca3af;
  --tide: #3b82f6;
  --ember: #ef4444;
  --gale: #22c55e;
  --void: #6b21a8;
  --aether: #fbbf24;
}
* { margin:0; padding:0; box-sizing:border-box; }
html, body { width:100%; height:100%; overflow:hidden; background:var(--bg-dark); color:var(--text); font-family:system-ui,sans-serif; }
canvas { display:block; }
#game-container { position:relative; width:100vw; height:100vh; }
#title-screen { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; background:radial-gradient(ellipse at center, #1a1a4a 0%, #0a0a1a 70%); z-index:100; transition:opacity 0.5s; }
#title-screen.hidden { opacity:0; pointer-events:none; }
#title-screen h1 { font-size:clamp(2rem,8vw,5rem); background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3)); -webkit-background-clip:text; -webkit-text-fill-color:transparent; text-shadow:none; margin-bottom:0.5rem; letter-spacing:0.1em; }
#title-screen .subtitle { color:var(--text-dim); font-size:clamp(0.8rem,2.5vw,1.2rem); margin-bottom:3rem; }
.title-btn { padding:1rem 3rem; margin:0.5rem; font-size:1.1rem; border:1px solid var(--glass-border); border-radius:12px; background:var(--glass-bg); color:var(--text); cursor:pointer; transition:all 0.3s; backdrop-filter:blur(10px); }
.title-btn:hover { background:rgba(123,104,238,0.3); border-color:var(--accent); transform:scale(1.05); }
#title-particles { position:absolute; inset:0; pointer-events:none; }
#overlay { position:absolute; inset:0; z-index:50; display:none; }
#overlay.active { display:flex; align-items:center; justify-content:center; }
.modal { background:var(--glass-bg); border:1px solid var(--glass-border); border-radius:16px; padding:1.5rem; max-width:90vw; max-height:85vh; overflow-y:auto; backdrop-filter:blur(15px); box-shadow:0 20px 60px rgba(0,0,0,0.5); }
.modal h2 { margin-bottom:1rem; font-size:1.3rem; }
.modal-close { position:absolute; top:0.5rem; right:1rem; background:none; border:none; color:var(--text-dim); font-size:1.5rem; cursor:pointer; }
.modal-close:hover { color:var(--text); }
#battle-screen { position:absolute; inset:0; z-index:40; display:none; }
#battle-screen.active { display:flex; flex-direction:column; }
#battle-canvas { flex:1; width:100%; }
#battle-ui { position:absolute; bottom:0; left:0; right:0; background:var(--glass-bg); border-top:1px solid var(--glass-border); backdrop-filter:blur(10px); padding:0.8rem; min-height:120px; }
#battle-text { font-size:0.95rem; margin-bottom:0.5rem; min-height:1.5em; }
#battle-actions { display:flex; gap:0.5rem; flex-wrap:wrap; }
.battle-btn { padding:0.5rem 1rem; border:1px solid var(--glass-border); border-radius:8px; background:rgba(40,40,80,0.6); color:var(--text); cursor:pointer; font-size:0.85rem; transition:all 0.2s; }
.battle-btn:hover { background:rgba(123,104,238,0.4); border-color:var(--accent); }
.battle-btn:disabled { opacity:0.4; cursor:default; }
.battle-btn.ability { min-width:80px; }
.battle-btn.ability .type-indicator { display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:4px; vertical-align:middle; }
#hud { position:absolute; top:0; left:0; right:0; z-index:30; display:none; padding:0.5rem 1rem; background:linear-gradient(180deg,rgba(0,0,0,0.6),transparent); }
#hud.active { display:flex; gap:1rem; align-items:center; }
.hud-btn { background:var(--glass-bg); border:1px solid var(--glass-border); border-radius:8px; padding:0.4rem 0.8rem; color:var(--text); cursor:pointer; font-size:0.85rem; backdrop-filter:blur(5px); }
.hud-btn:hover { border-color:var(--accent); }
#hp-bar-container { width:140px; height:10px; background:rgba(0,0,0,0.5); border-radius:5px; overflow:hidden; }
#hp-bar { height:100%; background:linear-gradient(90deg,var(--success),#86efac); transition:width 0.5s; border-radius:5px; }
#hp-bar.low { background:linear-gradient(90deg,var(--danger),#fca5a5); }
#hp-bar.mid { background:linear-gradient(90deg,var(--warning),#fde68a); }
#minimap { position:absolute; bottom:10px; right:10px; z-index:25; border:1px solid var(--glass-border); border-radius:8px; overflow:hidden; }
#touch-controls { position:absolute; bottom:10px; left:10px; z-index:25; display:none; }
.touch-btn { width:50px; height:50px; border-radius:50%; background:rgba(255,255,255,0.15); border:1px solid rgba(255,255,255,0.2); color:white; font-size:1.2rem; display:flex; align-items:center; justify-content:center; cursor:pointer; user-select:none; -webkit-user-select:none; touch-action:none; }
.touch-btn:active { background:rgba(255,255,255,0.3); }
.touch-dpad { display:grid; grid-template-columns:repeat(3,50px); grid-template-rows:repeat(3,50px); gap:2px; }
#inventory-screen, #team-screen, #pokedex-screen, #map-screen { width:90vw; max-width:600px; }
.inv-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(80px,1fr)); gap:0.5rem; }
.inv-item { background:rgba(40,40,80,0.5); border:1px solid var(--glass-border); border-radius:8px; padding:0.5rem; text-align:center; cursor:pointer; transition:all 0.2s; }
.inv-item:hover { border-color:var(--accent); background:rgba(60,60,120,0.5); }
.inv-item .icon { font-size:1.5rem; }
.inv-item .count { font-size:0.7rem; color:var(--text-dim); }
.team-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:0.5rem; }
.team-card { background:rgba(30,30,60,0.6); border:1px solid var(--glass-border); border-radius:10px; padding:0.8rem; cursor:pointer; transition:all 0.2s; }
.team-card:hover { border-color:var(--accent); }
.team-card.selected { border-color:var(--accent3); box-shadow:0 0 10px rgba(0,229,255,0.3); }
.team-card .name { font-weight:bold; font-size:0.9rem; }
.team-card .level { color:var(--text-dim); font-size:0.75rem; }
.team-card .hp-mini { width:100%; height:6px; background:rgba(0,0,0,0.4); border-radius:3px; margin-top:4px; overflow:hidden; }
.team-card .hp-mini-fill { height:100%; background:var(--success); border-radius:3px; transition:width 0.3s; }
.pokedex-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:0.5rem; }
.pokedex-entry { background:rgba(30,30,60,0.5); border:1px solid var(--glass-border); border-radius:8px; padding:0.6rem; text-align:center; opacity:0.4; }
.pokedex-entry.discovered { opacity:1; border-color:var(--accent); }
.pokedex-entry .picon { font-size:1.8rem; }
.pokedex-entry .pname { font-size:0.75rem; margin-top:2px; }
.pokedex-entry .ptype { font-size:0.65rem; color:var(--text-dim); }
#tutorial-overlay { position:absolute; inset:0; z-index:60; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,0.7); }
#tutorial-overlay.active { display:flex; }
.tutorial-box { background:var(--glass-bg); border:1px solid var(--glass-border); border-radius:16px; padding:2rem; max-width:500px; text-align:center; backdrop-filter:blur(15px); }
.tutorial-box h3 { margin-bottom:1rem; color:var(--accent3); }
.tutorial-box p { margin-bottom:1rem; line-height:1.6; }
.tutorial-box button { padding:0.6rem 2rem; border:1px solid var(--accent); border-radius:8px; background:rgba(123,104,238,0.3); color:var(--text); cursor:pointer; }
.tutorial-box button:hover { background:rgba(123,104,238,0.5); }
#story-overlay { position:absolute; inset:0; z-index:55; display:none; align-items:center; justify-content:center; }
#story-overlay.active { display:flex; }
.story-vignette { position:absolute; inset:0; }
.story-text { position:relative; z-index:2; background:var(--glass-bg); border:1px solid var(--glass-border); border-radius:16px; padding:2rem; max-width:600px; text-align:center; backdrop-filter:blur(15px); }
.story-text h3 { margin-bottom:0.5rem; }
.story-text p { line-height:1.8; margin-bottom:1rem; }
.story-text button { padding:0.5rem 2rem; border:1px solid var(--accent); border-radius:8px; background:rgba(123,104,238,0.3); color:var(--text); cursor:pointer; }
.float-text { position:absolute; font-weight:bold; pointer-events:none; animation:floatUp 1s ease-out forwards; z-index:45; }
@keyframes floatUp { 0% { opacity:1; transform:translateY(0); } 100% { opacity:0; transform:translateY(-60px); } }
.screen-shake { animation:shake 0.3s ease-out; }
@keyframes shake { 0%,100% { transform:translate(0); } 25% { transform:translate(-5px,3px); } 50% { transform:translate(5px,-3px); } 75% { transform:translate(-3px,5px); } }
.crit-flash { animation:critFlash 0.4s ease-out; }
@keyframes critFlash { 0% { filter:brightness(1); } 50% { filter:brightness(2); } 100% { filter:brightness(1); } }
#mute-btn { position:absolute; top:10px; right:10px; z-index:100; background:var(--glass-bg); border:1px solid var(--glass-border); border-radius:50%; width:36px; height:36px; display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--text); font-size:1rem; }
#zone-name { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); z-index:35; font-size:clamp(1.5rem,5vw,3rem); font-weight:bold; color:white; text-shadow:0 0 30px rgba(123,104,238,0.8); opacity:0; pointer-events:none; transition:opacity 0.5s; }
#zone-name.show { opacity:1; }
::-webkit-scrollbar { width:6px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-thumb { background:var(--glass-border); border-radius:3px; }
@media (pointer:coarse) { #touch-controls { display:block; } }
</style>
</head>
<body>
<div id="game-container">
  <canvas id="game-canvas"></canvas>
  <canvas id="minimap" width="120" height="120"></canvas>
  <div id="hud">
    <span id="zone-label">Zone 1</span>
    <div id="hp-bar-container"><div id="hp-bar" style="width:100%"></div></div>
    <span id="hp-text">100/100</span>
    <button class="hud-btn" onclick="Game.openTeam()">Team</button>
    <button class="hud-btn" onclick="Game.openInventory()">Bag</button>
    <button class="hud-btn" onclick="Game.openPokedex()">Pokedex</button>
    <button class="hud-btn" onclick="Game.saveGame()">Save</button>
  </div>
  <div id="touch-controls">
    <div class="touch-dpad">
      <div></div><div class="touch-btn" data-dir="up">▲</div><div></div>
      <div class="touch-btn" data-dir="left">◀</div><div></div><div class="touch-btn" data-dir="right">▶</div>
      <div></div><div class="touch-btn" data-dir="down">▼</div><div></div>
    </div>
  </div>
  <div id="title-screen">
    <canvas id="title-particles"></canvas>
    <h1>LOOMBOUND</h1>
    <p class="subtitle">Weave the threads of fate</p>
    <button class="title-btn" onclick="Game.newGame()">New Binding</button>
    <button class="title-btn" id="continue-btn" onclick="Game.continueGame()" style="display:none">Continue</button>
  </div>
  <div id="overlay" class="modal-container"></div>
  <div id="battle-screen">
    <canvas id="battle-canvas"></canvas>
    <div id="battle-ui">
      <div id="battle-text">A wild creature appears!</div>
      <div id="battle-actions"></div>
    </div>
  </div>
  <div id="tutorial-overlay"><div class="tutorial-box" id="tutorial-content"></div></div>
  <div id="story-overlay"><div class="story-vignette" id="story-bg"></div><div class="story-text" id="story-content"></div></div>
  <div id="zone-name"></div>
  <button id="mute-btn" onclick="Game.toggleMute()">🔊</button>
</div>
<script>
"use strict";
// ==================== TYPE SYSTEM ====================
const TYPES = {
  FLUX: {name:'Flux',color:'#a855f7',emoji:'⚡'},
  STONE: {name:'Stone',color:'#9ca3af',emoji:'🪨'},
  TIDE: {name:'Tide',color:'#3b82f6',emoji:'🌊'},
  EMBER: {name:'Ember',color:'#ef4444',emoji:'🔥'},
  GALE: {name:'Gale',color:'#22c55e',emoji:'🍃'},
  VOID: {name:'Void',color:'#6b21a8',emoji:'🌀'},
  AETHER: {name:'Aether',color:'#fbbf24',emoji:'✨'}
};
const TYPE_CHART = {
  FLUX: {vs:{FLUX:0.5,STONE:2,TIDE:0.5,EMBER:0.5,GALE:1,VOID:0.5,AETHER:0.5}},
  STONE: {vs:{FLUX:0.5,STONE:1,TIDE:2,EMBER:1,GALE:0.5,VOID:0.5,AETHER:0.5}},
  TIDE: {vs:{FLUX:2,STONE:0.5,TIDE:0.5,EMBER:0.5,GALE:1,VOID:0.5,AETHER:0.5}},
  EMBER: {vs:{FLUX:2,STONE:1,TIDE:2,EMBER:0.5,GALE:1,VOID:0.5,AETHER:0.5}},
  GALE: {vs:{FLUX:1,STONE:2,TIDE:1,EMBER:2,GALE:0.5,VOID:0.5,AETHER:0.5}},
  VOID: {vs:{FLUX:1.5,STONE:1.5,TIDE:1.5,EMBER:1.5,GALE:1.5,VOID:0.5,AETHER:0.5}},
  AETHER: {vs:{FLUX:0.75,STONE:0.75,TIDE:0.75,EMBER:0.75,GALE:0.75,VOID:2,AETHER:1}}
};
// ==================== ABILITIES (40+) ====================
const ABILITIES = [
  {id:'thrust',name:'Thrust',type:'FLUX',power:40,acc:100,pp:20,eff:'none',desc:'A basic energy strike.'},
  {id:'slam',name:'Slam',type:'STONE',power:45,acc:95,pp:20,eff:'none',desc:'A heavy physical blow.'},
  {id:'lash',name:'Lash',type:'TIDE',power:40,acc:100,pp:25,eff:'none',desc:'A whipping water strike.'},
  {id:'burn',name:'Burn',type:'EMBER',power:40,acc:100,pp:20,eff:'burn',desc:'Attacks that burn the foe.'},
  {id:'slash',name:'Slash',type:'GALE',power:45,acc:95,pp:20,eff:'none',desc:'A swift cutting attack.'},
  {id:'drain',name:'Drain',type:'FLUX',power:35,acc:100,pp:15,eff:'drain',desc:'Drains life from foe.'},
  {id:'frost',name:'Frost',type:'TIDE',power:45,acc:90,pp:15,eff:'frost',desc:'Freezes foe, halving SPD.'},
  {id:'thunder',name:'Thunder',type:'EMBER',power:50,acc:85,pp:10,eff:'shock',desc:'Strikes with lightning.'},
  {id:'barrier',name:'Barrier',type:'AETHER',power:0,acc:100,pp:10,eff:'barrier',desc:'Creates a deflection shield.'},
  {id:'regen',name:'Regen',type:'GALE',power:0,acc:100,pp:10,eff:'regen',desc:'Restores HP over time.'},
  {id:'void_bolt',name:'Void Bolt',type:'VOID',power:50,acc:95,pp:15,eff:'none',desc:'A dark energy bolt.'},
  {id:'stone_wall',name:'Stone Wall',type:'STONE',power:0,acc:100,pp:15,eff:'defense_up',desc:'Raises DEF sharply.'},
  {id:'gale_strike',name:'Gale Strike',type:'GALE',power:55,acc:90,pp:10,eff:'multi',desc:'Hits 2-3 times.'},
  {id:'ember_blast',name:'Ember Blast',type:'EMBER',power:60,acc:85,pp:10,eff:'burn',desc:'A powerful burning blast.'},
  {id:'tide_pillar',name:'Tide Pillar',type:'TIDE',power:55,acc:90,pp:10,eff:'frost',desc:'A pillar of freezing water.'},
  {id:'flux_wave',name:'Flux Wave',type:'FLUX',power:60,acc:85,pp:10,eff:'none',desc:'A wave of raw energy.'},
  {id:'aether_beam',name:'Aether Beam',type:'AETHER',power:65,acc:80,pp:8,eff:'none',desc:'A concentrated light beam.'},
  {id:'void_scream',name:'Void Scream',type:'VOID',power:70,acc:75,pp:5,eff:'shock',desc:'A devastating dark scream.'},
  {id:'priority',name:'Quick Strike',type:'GALE',power:40,acc:100,pp:20,eff:'priority',desc:'Always strikes first.'},
  {id:'earthquake',name:'Quake',type:'STONE',power:70,acc:80,pp:8,eff:'none',desc:'Shakes the ground.'},
  {id:'waterfall',name:'Waterfall',type:'TIDE',power:65,acc:85,pp:10,eff:'frost',desc:'A crashing water attack.'},
  {id:'fire_pillar',name:'Fire Pillar',type:'EMBER',power:70,acc:80,pp:8,eff:'burn',desc:'A towering inferno.'},
  {id:'void_drain',name:'Void Drain',type:'VOID',power:45,acc:95,pp:15,eff:'drain',desc:'Drains with dark energy.'},
  {id:'aether_shield',name:'Aether Shield',type:'AETHER',power:0,acc:100,pp:10,eff:'barrier',desc:'A glowing protective barrier.'},
  {id:'gale_spin',name:'Gale Spin',type:'GALE',power:50,acc:95,pp:15,eff:'speed_up',desc:'Spins faster each hit.'},
  {id:'stone_crash',name:'Stone Crash',type:'STONE',power:65,acc:85,pp:10,eff:'none',desc:'Crushing rock impact.'},
  {id:'flux_burst',name:'Flux Burst',type:'FLUX',power:75,acc:75,pp:5,eff:'none',desc:'A massive energy explosion.'},
  {id:'tide_lash',name:'Tide Lash',type:'TIDE',power:50,acc:95,pp:15,eff:'none',desc:'A rapid water whip.'},
  {id:'ember_wave',name:'Ember Wave',type:'EMBER',power:55,acc:90,pp:12,eff:'burn',desc:'A wave of fire.'},
  {id:'void_step',name:'Void Step',type:'VOID',power:0,acc:100,pp:10,eff:'speed_up',desc:'Phases through space.'},
  {id:'aether_heal',name:'Aether Heal',type:'AETHER',power:0,acc:100,pp:10,eff:'heal',desc:'Heals a large amount.'},
  {id:'gale_burst',name:'Gale Burst',type:'GALE',power:60,acc:85,pp:10,eff:'multi',desc:'A burst of wind blades.'},
  {id:'stone_fang',name:'Stone Fang',type:'STONE',power:50,acc:95,pp:15,eff:'none',desc:'A crushing bite.'},
  {id:'flux_drain',name:'Flux Drain',type:'FLUX',power:40,acc:95,pp:15,eff:'drain',desc:'Siphons energy.'},
  {id:'tide_burst',name:'Tide Burst',type:'TIDE',power:65,acc:85,pp:10,eff:'none',desc:'A powerful water explosion.'},
  {id:'ember_strike',name:'Ember Strike',type:'EMBER',power:55,acc:90,pp:15,eff:'burn',desc:'A flaming punch.'},
  {id:'void_pulse',name:'Void Pulse',type:'VOID',power:55,acc:90,pp:12,eff:'none',desc:'A dark energy pulse.'},
  {id:'aether_wave',name:'Aether Wave',type:'AETHER',power:50,acc:90,pp:12,eff:'none',desc:'A wave of pure light.'},
  {id:'gale_fang',name:'Gale Fang',type:'GALE',power:55,acc:90,pp:12,eff:'multi',desc:'Swift fanged strikes.'},
  {id:'stone_bolt',name:'Stone Bolt',type:'STONE',power:55,acc:90,pp:12,eff:'none',desc:'A projectile of rock.'},
  {id:'flux_scream',name:'Flux Scream',type:'FLUX',power:65,acc:80,pp:8,eff:'shock',desc:'A disorienting energy cry.'},
  {id:'tide_drain',name:'Tide Drain',type:'TIDE',power:45,acc:95,pp:15,eff:'drain',desc:'Siphons water energy.'},
  {id:'ember_burst',name:'Ember Burst',type:'EMBER',power:70,acc:80,pp:8,eff:'burn',desc:'A fiery explosion.'},
  {id:'void_barrier',name:'Void Barrier',type:'VOID',power:0,acc:100,pp:8,eff:'barrier',desc:'A dark protective wall.'},
  {id:'aether_bolt',name:'Aether Bolt',type:'AETHER',power:60,acc:85,pp:10,eff:'none',desc:'A concentrated light bolt.'},
  {id:'gale_heal',name:'Gale Heal',type:'GALE',power:0,acc:100,pp:10,eff:'regen',desc:'Heals over several turns.'},
  {id:'stone_heal',name:'Stone Heal',type:'STONE',power:0,acc:100,pp:8,eff:'heal',desc:'A restorative earth touch.'},
  {id:'void_burst',name:'Void Burst',type:'VOID',power:80,acc:70,pp:3,eff:'shock',desc:'An ultimate dark explosion.'},
  {id:'aether_burst',name:'Aether Burst',type:'AETHER',power:75,acc:75,pp:5,eff:'none',desc:'A radiant energy burst.'}
];
const ABILITY_MAP = {};
ABILITIES.forEach(a => ABILITY_MAP[a.id] = a);

// ==================== CREATURES (27 total) ====================
const CREATURES = [
  {id:'sporel',name:'Sporel',affinity:'FLUX',baseHp:45,baseAtk:35,baseDef:25,baseSpd:30,
    lore:'A tiny spore-creature that drifts on thermal currents, leaving trails of shimmering dust.',
    abilities:['thrust','drain','flux_wave','gale_spin'],
    evolveTo:'sporel_prism',evoLevel:25,
    sprite:'🟣',color:'#a855f7'},
  {id:'sporel_prism',name:'Prism Sporel',affinity:'FLUX',baseHp:65,baseAtk:50,baseDef:40,baseSpd:38,
    lore:'A refracted bloom of light and spore, scattering prismatic shards across the sky.',
    abilities:['thrust','drain','flux_wave','gale_spin','flux_burst'],
    evolved:true,
    sprite:'🔮',color:'#c084fc'},
  {id:'rockle',name:'Rockle',affinity:'STONE',baseHp:55,baseAtk:40,baseDef:45,baseSpd:18,
    lore:'A slow-moving stone golem that grows crystalline formations on its back over centuries.',
    abilities:['slam','stone_wall','earthquake','stone_heal'],
    evolveTo:'rockle_golem',evoLevel:28,
    sprite:'🪨',color:'#9ca3af'},
  {id:'rockle_golem',name:'Golem Rockle',affinity:'STONE',baseHp:85,baseAtk:60,baseDef:70,baseSpd:22,
    lore:'A towering monolith of living rock, its crystalline crown hums with ancient resonance.',
    abilities:['slam','stone_wall','earthquake','stone_heal','stone_crash'],
    evolved:true,
    sprite:'🗿',color:'#6b7280'},
  {id:'tidalisk',name:'Tidalisk',affinity:'TIDE',baseHp:48,baseAtk:32,baseDef:30,baseSpd:35,
    lore:'A ribbon-like water spirit that weaves through river currents, singing in harmonic frequencies.',
    abilities:['lash','frost','tide_pillar','tide_drain'],
    evolveTo:'tidalisk_naga',evoLevel:26,
    sprite:'🌊',color:'#3b82f6'},
  {id:'tidalisk_naga',name:'Naga Tidalisk',affinity:'TIDE',baseHp:70,baseAtk:48,baseDef:45,baseSpd:42,
    lore:'A serpentine water sovereign, its coils can summon tidal waves and freezing mists.',
    abilities:['lash','frost','tide_pillar','tide_drain','tide_burst'],
    evolved:true,
    sprite:'🐉',color:'#60a5fa'},
  {id:'cinderwisp',name:'Cinderwisp',affinity:'EMBER',baseHp:42,baseAtk:38,baseDef:22,baseSpd:32,
    lore:'A flickering ember-sprite born from volcanic vents, dancing with restless heat.',
    abilities:['burn','thunder','ember_blast','ember_wave'],
    evolveTo:'cinderwisp_dragon',evoLevel:27,
    sprite:'🔥',color:'#ef4444'},
  {id:'cinderwisp_dragon',name:'Dragewisp',affinity:'EMBER',baseHp:68,baseAtk:58,baseDef:35,baseSpd:38,
    lore:'A winged ember-beast whose breath can melt stone and ignite the sky.',
    abilities:['burn','thunder','ember_blast','ember_wave','ember_burst'],
    evolved:true,
    sprite:'🐲',color:'#f87171'},
  {id:'leaflet',name:'Leaflet',affinity:'GALE',baseHp:40,baseAtk:30,baseDef:20,baseSpd:40,
    lore:'A leaf-like creature that rides wind currents, scattering seeds of new growth.',
    abilities:['slash','regen','gale_strike','gale_heal'],
    evolveTo:'leaflet_wraith',evoLevel:24,
    sprite:'🍃',color:'#22c55e'},
  {id:'leaflet_wraith',name:'Wraith Leaflet',affinity:'GALE',baseHp:60,baseAtk:48,baseDef:30,baseSpd:50,
    lore:'A spectral leaf-spirit that becomes invisible in the wind, striking with razor precision.',
    abilities:['slash','regen','gale_strike','gale_heal','gale_burst'],
    evolved:true,
    sprite:'👻',color:'#4ade80'},
  {id:'voidling',name:'Voidling',affinity:'VOID',baseHp:44,baseAtk:36,baseDef:28,baseSpd:33,
    lore:'A small shadow-creature that feeds on ambient light, leaving darkness in its wake.',
    abilities:['void_bolt','void_drain','void_step','void_barrier'],
    evolveTo:'voidling_maw',evoLevel:29,
    sprite:'🌀',color:'#6b21a8'},
  {id:'voidling_maw',name:'Maw Voidling',affinity:'VOID',baseHp:72,baseAtk:55,baseDef:42,baseSpd:40,
    lore:'A gaping void-entity that devours light itself, whispering in forgotten tongues.',
    abilities:['void_bolt','void_drain','void_step','void_barrier','void_burst'],
    evolved:true,
    sprite:'🕳️',color:'#9333ea'},
  {id:'aetheling',name:'Aetheling',affinity:'AETHER',baseHp:46,baseAtk:33,baseDef:32,baseSpd:28,
    lore:'A tiny golden being radiating warm light, said to be born from the first dawn.',
    abilities:['barrier','aether_beam','aether_heal','aether_wave'],
    evolveTo:'aetheling_seraph',evoLevel:30,
    sprite:'✨',color:'#fbbf24'},
  {id:'aetheling_seraph',name:'Seraph Aetheling',affinity:'AETHER',baseHp:75,baseAtk:52,baseDef:50,baseSpd:35,
    lore:'A radiant celestial being whose wings cast healing light across the land.',
    abilities:['barrier','aether_beam','aether_heal','aether_wave','aether_burst'],
    evolved:true,
    sprite:'👼',color:'#fde68a'},
  {id:'mossback',name:'Mossback',affinity:'STONE',baseHp:60,baseAtk:35,baseDef:50,baseSpd:15,
    lore:'A turtle-like creature with a moss-covered shell that harbors tiny ecosystems.',
    abilities:['slam','stone_wall','stone_fang','stone_heal'],
    evolveTo:'mossback_turtle',evoLevel:22,
    sprite:'🐢',color:'#65a30d'},
  {id:'mossback_turtle',name:'Turtle Mossback',affinity:'STONE',baseHp:90,baseAtk:45,baseDef:75,baseSpd:18,
    lore:'An ancient shell-bearer whose mossy dome is a living garden of rare flora.',
    abilities:['slam','stone_wall','stone_fang','stone_heal','stone_crash'],
    evolved:true,
    sprite:'🐢',color:'#84cc16'},
  {id:'flamefox',name:'Flamefox',affinity:'EMBER',baseHp:40,baseAtk:42,baseDef:20,baseSpd:38,
    lore:'A fox-like creature with a tail of living flame, quick and cunning.',
    abilities:['burn','thunder','ember_strike','gale_strike'],
    evolveTo:'flamefox_demon',evoLevel:26,
    sprite:'🦊',color:'#f97316'},
  {id:'flamefox_demon',name:'Demon Flamefox',affinity:'EMBER',baseHp:65,baseAtk:62,baseDef:30,baseSpd:48,
    lore:'A dual-tailed fire-beast of legendary speed, its eyes burn with ancient fire.',
    abilities:['burn','thunder','ember_strike','gale_strike','ember_burst'],
    evolved:true,
    sprite:'🦊',color:'#fb923c'},
  {id:'aqualisk',name:'Aqualisk',affinity:'TIDE',baseHp:50,baseAtk:38,baseDef:35,baseSpd:30,
    lore:'A fish-like creature with bioluminescent scales that glow in the deep dark.',
    abilities:['lash','frost','tide_lash','tide_burst'],
    evolveTo:'aqualisk_leviathan',evoLevel:28,
    sprite:'🐟',color:'#0ea5e9'},
  {id:'aqualisk_leviathan',name:'Leviathan Aqualisk',affinity:'TIDE',baseHp:80,baseAtk:55,baseDef:50,baseSpd:36,
    lore:'A massive deep-sea leviathan whose bioluminescence can blind predators.',
    abilities:['lash','frost','tide_lash','tide_burst','waterfall'],
    evolved:true,
    sprite:'🐋',color:'#38bdf8'},
  {id:'stormhawk',name:'Stormhawk',affinity:'GALE',baseHp:45,baseAtk:40,baseDef:25,baseSpd:42,
    lore:'A hawk-like bird that rides thunderstorms, its wings crackling with static.',
    abilities:['slash','thunder','gale_strike','gale_fang'],
    evolveTo:'stormhawk_griffin',evoLevel:27,
    sprite:'🦅',color:'#06b6d4'},
  {id:'stormhawk_griffin',name:'Griffin Stormhawk',affinity:'GALE',baseHp:70,baseAtk:58,baseDef:38,baseSpd:52,
    lore:'A griffin-like storm-beast whose wings summon thunder and whose claws split clouds.',
    abilities:['slash','thunder','gale_strike','gale_fang','gale_burst'],
    evolved:true,
    sprite:'🦅',color:'#22d3ee'},
  {id:'shadewolf',name:'Shadewolf',affinity:'VOID',baseHp:48,baseAtk:44,baseDef:28,baseSpd:36,
    lore:'A wolf-like shadow that materializes from darkness, hunting in packs.',
    abilities:['void_bolt','void_drain','void_step','void_barrier'],
    evolveTo:'shadewolf_alpha',evoLevel:28,
    sprite:'🐺',color:'#7c3aed'},
  {id:'shadewolf_alpha',name:'Alpha Shadewolf',affinity:'VOID',baseHp:75,baseAtk:65,baseDef:40,baseSpd:45,
    lore:'The pack leader of shadows, its howl can shatter light itself.',
    abilities:['void_bolt','void_drain','void_step','void_barrier','void_burst'],
    evolved:true,
    sprite:'🐺',color:'#a78bfa'},
  {id:'sunbird',name:'Sunbird',affinity:'AETHER',baseHp:44,baseAtk:36,baseDef:30,baseSpd:34,
    lore:'A golden bird that nests in high peaks, its song said to heal the wounded.',
    abilities:['barrier','aether_beam','aether_heal','aether_wave'],
    evolveTo:'sunbird_phoenix',evoLevel:29,
    sprite:'🐦',color:'#eab308'},
  {id:'sunbird_phoenix',name:'Phoenix Sunbird',affinity:'AETHER',baseHp:72,baseAtk:54,baseDef:48,baseSpd:42,
    lore:'A legendary phoenix-like bird reborn from golden flames, its wings heal all they touch.',
    abilities:['barrier','aether_beam','aether_heal','aether_wave','aether_burst'],
    evolved:true,
    sprite:'🐦',color:'#facc15'},
  {id:'crystalbug',name:'Crystalbug',affinity:'FLUX',baseHp:38,baseAtk:28,baseDef:35,baseSpd:32,
    lore:'A tiny insect with a crystal shell that refracts light into harmless rainbows.',
    abilities:['thrust','drain','flux_drain','barrier'],
    evolveTo:'crystalbug_king',evoLevel:24,
    sprite:'🐛',color:'#a78bfa'},
  {id:'crystalbug_king',name:'King Crystalbug',affinity:'FLUX',baseHp:62,baseAtk:42,baseDef:55,baseSpd:38,
    lore:'A majestic crystal-beetle whose shell can refract energy into devastating beams.',
    abilities:['thrust','drain','flux_drain','barrier','flux_burst'],
    evolved:true,
    sprite:'🪲',color:'#c084fc'},
  {id:'ashdrake',name:'Ashdrake',affinity:'EMBER',baseHp:52,baseAtk:45,baseDef:32,baseSpd:26,
    lore:'A small dragon-like creature born from volcanic ash, breathing smoky flames.',
    abilities:['burn','thunder','ember_blast','stone_fang'],
    evolveTo:'ashdrake_inferno',evoLevel:28,
    sprite:'🐲',color:'#dc2626'},
  {id:'ashdrake_inferno',name:'Inferno Ashdrake',affinity:'EMBER',baseHp:82,baseAtk:68,baseDef:45,baseSpd:30,
    lore:'A massive ash-dragon whose breath can level mountains and ignite the sky.',
    abilities:['burn','thunder','ember_blast','stone_fang','ember_burst'],
    evolved:true,
    sprite:'🐲',color:'#ef4444'},
  {id:'mistwisp',name:'Mistwisp',affinity:'GALE',baseHp:42,baseAtk:34,baseDef:24,baseSpd:36,
    lore:'A wispy cloud-creature that drifts through fog, leaving trails of dew.',
    abilities:['slash','regen','gale_spin','gale_heal'],
    evolveTo:'mistwisp_spirit',evoLevel:25,
    sprite:'💨',color:'#67e8f9'},
  {id:'mistwisp_spirit',name:'Spirit Mistwisp',affinity:'GALE',baseHp:66,baseAtk:50,baseDef:35,baseSpd:48,
    lore:'A spectral cloud-being that can phase through solid matter and strike from anywhere.',
    abilities:['slash','regen','gale_spin','gale_heal','gale_burst'],
    evolved:true,
    sprite:'👻',color:'#a5f3fc'},
  {id:'abyssal',name:'Abyssal',affinity:'VOID',baseHp:55,baseAtk:48,baseDef:35,baseSpd:28,
    lore:'A deep-dwelling shadow-creature from the abyssal trench, feeding on darkness.',
    abilities:['void_bolt','void_drain','void_step','void_barrier'],
    evolveTo:'abyssal_titan',evoLevel:30,
    sprite:'🦑',color:'#581c87'},
  {id:'abyssal_titan',name:'Titan Abyssal',affinity:'VOID',baseHp:90,baseAtk:72,baseDef:50,baseSpd:32,
    lore:'A colossal abyssal entity that commands the darkness of the deepest trenches.',
    abilities:['void_bolt','void_drain','void_step','void_barrier','void_burst'],
    evolved:true,
    sprite:'🦑',color:'#7c3aed'},
  {id:'dewdrop',name:'Dewdrop',affinity:'TIDE',baseHp:40,baseAtk:28,baseDef:30,baseSpd:34,
    lore:'A tiny water-bead creature that rolls through morning dew, collecting nutrients.',
    abilities:['lash','frost','tide_lash','regen'],
    evolveTo:'dewdrop_tide',evoLevel:23,
    sprite:'💧',color:'#06b6d4'},
  {id:'dewdrop_tide',name:'Tide Dewdrop',affinity:'TIDE',baseHp:64,baseAtk:44,baseDef:44,baseSpd:40,
    lore:'A large water-orb that can manipulate tides and create whirlpools.',
    abilities:['lash','frost','tide_lash','regen','tide_burst'],
    evolved:true,
    sprite:'💧',color:'#22d3ee'},

];
// ==================== ZONES ====================
const ZONES = [
  {id:0,name:'Verdant Hollow',gradient:['#1a3a1a','#0a2a0a'],particleColor:'#4ade80',particleCount:30,encounterRate:0.08,
    enemies:[{id:'sporel',weight:30},{id:'crystalbug',weight:25},{id:'leaflet',weight:20},{id:'dewdrop',weight:25}],
    boss:{id:'rockle',name:'Guardian Rockle',level:12},
    shrine:true,story:'The Verdant Hollow hums with life. Ancient spores drift on warm currents, and crystal beetles navigate the undergrowth. You feel the threads of fate beginning to weave.',
    storyColor:'rgba(10,42,10,0.85)'},
  {id:1,name:'Crystal Caverns',gradient:['#1a1a3a','#0a0a2a'],particleColor:'#a78bfa',particleCount:40,encounterRate:0.10,
    enemies:[{id:'crystalbug',weight:25},{id:'rockle',weight:20},{id:'aetheling',weight:20},{id:'mossback',weight:35}],
    boss:{id:'aetheling',name:'Guardian Aetheling',level:18},
    shrine:true,story:'The caverns glow with crystalline light. Mossback turtles navigate the dark passages, and the air vibrates with dormant energy. The Weave grows stronger here.',
    storyColor:'rgba(10,10,42,0.85)'},
  {id:2,name:'Ember Wastes',gradient:['#3a1a0a','#2a0a0a'],particleColor:'#f97316',particleCount:35,encounterRate:0.12,
    enemies:[{id:'cinderwisp',weight:25},{id:'flamefox',weight:20},{id:'ashdrake',weight:20},{id:'aqualisk',weight:35}],
    boss:{id:'cinderwisp',name:'Guardian Cinderwisp',level:24},
    shrine:true,story:'The Ember Wastes stretch endlessly, volcanic vents breathing fire into the sky. Flamefoxes prowl the ash fields, and the ground trembles with ancient power.',
    storyColor:'rgba(42,10,10,0.85)'},
  {id:3,name:'Storm Peaks',gradient:['#1a2a3a','#0a1a2a'],particleColor:'#22d3ee',particleCount:45,encounterRate:0.14,
    enemies:[{id:'stormhawk',weight:25},{id:'shadewolf',weight:20},{id:'aqualisk',weight:20},{id:'leaflet',weight:35}],
    boss:{id:'stormhawk',name:'Guardian Stormhawk',level:30},
    shrine:true,story:'The Storm Peaks pierce the clouds, lightning crackling between jagged spires. Stormhawks nest on the highest crags, and the wind carries whispers of the Weave.',
    storyColor:'rgba(10,26,42,0.85)'},
  {id:4,name:'Abyssal Trench',gradient:['#0a1a2a','#000a1a'],particleColor:'#06b6d4',particleCount:50,encounterRate:0.16,
    enemies:[{id:'abyssal',weight:25},{id:'tidalisk',weight:20},{id:'aqualisk',weight:20},{id:'shadewolf',weight:35}],
    boss:{id:'abyssal',name:'Guardian Abyssal',level:36},
    shrine:true,story:'The Abyssal Trench descends into darkness. Abyssal titans dwell in the crushing depths, and the water glows with bioluminescent life. The Weave is frayed here.',
    storyColor:'rgba(0,10,26,0.85)'},
  {id:5,name:'Void\'s Edge',gradient:['#1a0a2a','#0a001a'],particleColor:'#9333ea',particleCount:55,encounterRate:0.20,
    enemies:[{id:'voidling',weight:25},{id:'shadewolf',weight:20},{id:'abyssal',weight:20},{id:'sunbird',weight:35}],
    boss:{id:'voidling',name:'Anchor Voidling',level:42},
    shrine:false,story:'At the Edge of the Void, reality itself frays. The final anchor pulses with dark energy. You must weave the threads of fate to restore the Loom.',
    storyColor:'rgba(10,0,26,0.9)'}
];

// ==================== ITEMS ====================
const ITEMS = {
  'thread_pouch':{name:'Thread Pouch',icon:'🧵',type:'capture',tier:1,desc:'Basic capture tool.',price:100},
  'thread_pouch_l':{name:'Thread Pouch+',icon:'🧶',type:'capture',tier:2,desc:'Improved capture tool.',price:300},
  'thread_pouch_g':{name:'Thread Pouch++',icon:'✨',type:'capture',tier:3,desc:'Master capture tool.',price:800},
  'mender_tear':{name:'Mender Tear',icon:'💧',type:'heal',tier:1,desc:'Restores 30 HP.',price:50,heal:30},
  'mender_tear_l':{name:'Mender Tear+',icon:'💧',type:'heal',tier:2,desc:'Restores 80 HP.',price:150,heal:80},
  'mender_tear_g':{name:'Mender Tear++',icon:'💧',type:'heal',tier:3,desc:'Restores all HP.',price:400,heal:999},
  'catalyst_flux':{name:'Flux Catalyst',icon:'⚡',type:'catalyst',affinity:'FLUX',desc:'Transforms Flux creatures.',price:500},
  'catalyst_stone':{name:'Stone Catalyst',icon:'🪨',type:'catalyst',affinity:'STONE',desc:'Transforms Stone creatures.',price:500},
  'catalyst_tide':{name:'Tide Catalyst',icon:'🌊',type:'catalyst',affinity:'TIDE',desc:'Transforms Tide creatures.',price:500},
  'catalyst_ember':{name:'Ember Catalyst',icon:'🔥',type:'catalyst',affinity:'EMBER',desc:'Transforms Ember creatures.',price:500},
  'catalyst_gale':{name:'Gale Catalyst',icon:'🍃',type:'catalyst',affinity:'GALE',desc:'Transforms Gale creatures.',price:500},
  'catalyst_void':{name:'Void Catalyst',icon:'🌀',type:'catalyst',affinity:'VOID',desc:'Transforms Void creatures.',price:500},
  'catalyst_aether':{name:'Aether Catalyst',icon:'✨',type:'catalyst',affinity:'AETHER',desc:'Transforms Aether creatures.',price:500},
  'field_rope':{name:'Field Rope',icon:'🪢',type:'field',desc:'Cross gaps.',price:200},
  'field_lantern':{name:'Field Lantern',icon:'🏮',type:'field',desc:'Illuminates dark areas.',price:300},
  'exp_boost':{name:'Exp Boost',icon:'📈',type:'consumable',desc:'Boosts EXP gain.',price:250},
  'atk_boost':{name:'Atk Boost',icon:'⚔️',type:'consumable',desc:'Boosts ATK.',price:250},
  'def_boost':{name:'Def Boost',icon:'🛡️',type:'consumable',desc:'Boosts DEF.',price:250},
  'spd_boost':{name:'Spd Boost',icon:'💨',type:'consumable',desc:'Boosts SPD.',price:250}
};

// ==================== GAME STATE ====================
const Game = {
  state: 'title',
  canvas: null, ctx: null,
  battleCanvas: null, battleCtx: null,
  titleCanvas: null, titleCtx: null,
  minimapCanvas: null, minimapCtx: null,
  player: {x:5,y:5,zone:0,party:[],inventory:{},gold:500,flags:{},shrinePos:null,storyProgress:0},
  currentEnemy: null, battleState: null,
  particles: [], battleParticles: [],
  audioCtx: null, muted: false, currentOsc: null,
  moveDir: null, moveTimer: 0, moveInterval: 0.15,
  touchDir: null,
  tileW: 32, tileH: 32,
  mapTiles: [], mapW: 40, mapH: 40,
  camera: {x:0,y:0},
  floatTexts: [],
  animFrame: 0, lastTime: 0,
  tutorialStep: 0, tutorialActive: false,
  storyActive: false, storyQueue: [],
  captured: new Set(), defeatedBosses: new Set(),
  postGame: false, collectedCount: 0,

  init() {
    this.canvas = document.getElementById('game-canvas');
    this.ctx = this.canvas.getContext('2d');
    this.battleCanvas = document.getElementById('battle-canvas');
    this.battleCtx = this.battleCanvas.getContext('2d');
    this.titleCanvas = document.getElementById('title-particles');
    this.titleCtx = this.titleCanvas.getContext('2d');
    this.minimapCanvas = document.getElementById('minimap');
    this.minimapCtx = this.minimapCanvas.getContext('2d');
    this.resize();
    window.addEventListener('resize', () => this.resize());
    this.setupInput();
    this.setupTouch();
    this.initParticles();
    this.initTitleParticles();
    this.checkSave();
    this.renderTitle();
    this.loop();
  },

  resize() {
    const dpr = window.devicePixelRatio || 1;
    [this.canvas, this.battleCanvas].forEach(c => {
      c.width = c.clientWidth * dpr;
      c.height = c.clientHeight * dpr;
      c.getContext('2d').setTransform(dpr,0,0,dpr,0,0);
    });
    this.minimapCanvas.width = 120;
    this.minimapCanvas.height = 120;
  },

  setupInput() {
    const keys = {};
    window.addEventListener('keydown', e => {
      keys[e.key] = true;
      if(e.key === 'Escape') this.handleEscape();
      if(e.key === 's' || e.key === 'S') this.saveGame();
    });
    window.addEventListener('keyup', e => { keys[e.key] = false; });
    this.keyState = keys;
  },

  setupTouch() {
    document.querySelectorAll('.touch-btn').forEach(btn => {
      const dir = btn.dataset.dir;
      btn.addEventListener('touchstart', e => { e.preventDefault(); this.touchDir = dir; });
      btn.addEventListener('touchend', e => { e.preventDefault(); this.touchDir = null; });
      btn.addEventListener('mousedown', () => { this.touchDir = dir; });
      btn.addEventListener('mouseup', () => { this.touchDir = null; });
    });
  },

  initParticles() {
    this.particles = [];
    for(let i = 0; i < 60; i++) {
      this.particles.push({
        x: Math.random() * window.innerWidth,
        y: Math.random() * window.innerHeight,
        vx: (Math.random() - 0.5) * 0.5,
        vy: -Math.random() * 1 - 0.2,
        size: Math.random() * 3 + 1,
        alpha: Math.random() * 0.5 + 0.1,
        life: Math.random() * 200 + 100
      });
    }
  },

  initTitleParticles() {
    this.titleParticles = [];
    for(let i = 0; i < 80; i++) {
      this.titleParticles.push({
        x: Math.random() * window.innerWidth,
        y: Math.random() * window.innerHeight,
        vx: (Math.random() - 0.5) * 0.3,
        vy: (Math.random() - 0.5) * 0.3,
        size: Math.random() * 4 + 1,
        alpha: Math.random() * 0.6 + 0.1,
        hue: Math.random() * 60 + 240
      });
    }
  },

  renderTitle() {
    const c = this.titleCanvas;
    c.width = c.clientWidth;
    c.height = c.clientHeight;
    const ctx = this.titleCtx;
    ctx.clearRect(0, 0, c.width, c.height);
    this.titleParticles.forEach(p => {
      p.x += p.vx; p.y += p.vy;
      if(p.x < 0) p.x = c.width;
      if(p.x > c.width) p.x = 0;
      if(p.y < 0) p.y = c.height;
      if(p.y > c.height) p.y = 0;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      ctx.fillStyle = `hsla(${p.hue},70%,70%,${p.alpha})`;
      ctx.fill();
    });
  },

  // ==================== SAVE/LOAD ====================
  checkSave() {
    try {
      const s = localStorage.getItem('loombound_save');
      if(s) document.getElementById('continue-btn').style.display = 'inline-block';
    } catch(e) {}
  },

  saveGame() {
    const save = {
      player: {
        x: this.player.x, y: this.player.y, zone: this.player.zone,
        party: this.player.party.map(p => ({...p})),
        inventory: {...this.player.inventory},
        gold: this.player.gold,
        flags: {...this.player.flags},
        storyProgress: this.player.storyProgress,
        captured: [...this.captured],
        defeatedBosses: [...this.defeatedBosses],
        postGame: this.postGame
      },
      version: 1
    };
    localStorage.setItem('loombound_save', JSON.stringify(save));
    this.showFloatText(this.player.x * this.tileW, this.player.y * this.tileH, 'Saved!', '#4ade80');
  },

  loadGame() {
    try {
      const s = localStorage.getItem('loombound_save');
      if(!s) return false;
      const save = JSON.parse(s);
      this.player.x = save.player.x;
      this.player.y = save.player.y;
      this.player.zone = save.player.zone;
      this.player.party = save.player.party.map(p => ({...p}));
      this.player.inventory = save.player.inventory;
      this.player.gold = save.player.gold;
      this.player.flags = save.player.flags;
      this.player.storyProgress = save.player.storyProgress;
      this.captured = new Set(save.player.captured);
      this.defeatedBosses = new Set(save.player.defeatedBosses);
      this.postGame = save.player.postGame || false;
      return true;
    } catch(e) { return false; }
  },

  // ==================== MAP GENERATION ====================
  generateMap(zoneId) {
    const map = [];
    const w = this.mapW, h = this.mapH;
    const zone = ZONES[zoneId];
    for(let y = 0; y < h; y++) {
      map[y] = [];
      for(let x = 0; x < w; x++) {
        let tile = 0; // grass
        // Borders
        if(x === 0 || y === 0 || x === w-1 || y === h-1) tile = 1; // wall
        else if(x === 1 || y === 1 || x === w-2 || y === h-2) tile = 2; // path edge
        else tile = 0;
        // Add some variation
        const r = Math.random();
        if(r < 0.05 && tile === 0) tile = 3; // tree/rock
        if(r < 0.02 && tile === 0) tile = 4; // water
        // Path to shrine
        const mid = Math.floor(w/2);
        if(x >= mid-1 && x <= mid+1 && y >= mid-1 && y <= mid+1) tile = 2;
        // Shrine position
        if(x === mid && y === mid) tile = 5; // shrine
        // Boss position
        if(x === mid && y === h-3) tile = 6; // boss
        map[y][x] = tile;
      }
    }
    // Ensure path from start to shrine
    const sx = Math.floor(w/2), sy = h-2;
    for(let y = sy; y >= mid; y--) {
      for(let dx = -2; dx <= 2; dx++) {
        if(sx+dx >= 0 && sx+dx < w) map[y][sx+dx] = 2;
      }
    }
    // Ensure path from shrine to boss
    const bossY = h-3;
    for(let y = mid; y <= bossY; y++) {
      for(let dx = -2; dx <= 2; dx++) {
        if(sx+dx >= 0 && sx+dx < w) map[y][sx+dx] = 2;
      }
    }
    this.mapTiles = map;
    this.player.shrinePos = {x: mid, y: mid};
  },

  // ==================== NEW GAME / CONTINUE ====================
  newGame() {
    document.getElementById('title-screen').classList.add('hidden');
    this.player = {
      x: 5, y: this.mapH - 2, zone: 0,
      party: [], inventory: {'mender_tear':3, 'thread_pouch':3, 'field_rope':1},
      gold: 500, flags: {}, storyProgress: 0, shrinePos: null
    };
    this.captured = new Set();
    this.defeatedBosses = new Set();
    this.postGame = false;
    this.tutorialStep = 0;
    this.tutorialActive = true;
    this.storyActive = false;
    this.generateMap(0);
    this.startTutorial();
    this.showHUD();
    this.playAmbient();
  },

  continueGame() {
    if(this.loadGame()) {
      document.getElementById('title-screen').classList.add('hidden');
      this.generateMap(this.player.zone);
      this.showHUD();
      this.playAmbient();
    }
  },

  // ==================== TUTORIAL ====================
  startTutorial() {
    const steps = [
      {title:'Welcome to Loombound', text:'You are a Threadweaver, tasked with collecting creatures and restoring the Shattered Loom. Use WASD or arrow keys to move. On mobile, use the touch D-pad.', btn:'Continue'},
      {title:'Movement', text:'Walk through the Verdant Hollow to find your first wild creature. Approach it to initiate a battle.', btn:'Got it'},
      {title:'First Battle', text:'In battle, choose abilities to attack. Each creature has 4 abilities. Match your affinity for bonus damage!', btn:'Fight!'},
      {title:'Collection', text:'After weakening a creature, use a Thread Pouch to capture it. Lower HP = better capture chance!', btn:'Understood'},
      {title:'Your Party', text:'Build a team of up to 6 creatures. Visit shrines to heal, save, and buy items. Defeat guardians to unlock new zones.', btn:'Begin Weaving'}
    ];
    this.tutorialStep = 0;
    this.tutorialActive = true;
    this.showTutorialStep(steps[0]);
  },

  showTutorialStep(step) {
    const el = document.getElementById('tutorial-overlay');
    el.classList.add('active');
    document.getElementById('tutorial-content').innerHTML = `
      <h3>${step.title}</h3>
      <p>${step.text}</p>
      <button onclick="Game.nextTutorial()">${step.btn}</button>
    `;
  },

  nextTutorial() {
    const steps = [
      {title:'Welcome to Loombound', text:'You are a Threadweaver, tasked with collecting creatures and restoring the Shattered Loom. Use WASD or arrow keys to move. On mobile, use the touch D-pad.', btn:'Continue'},
      {title:'Movement', text:'Walk through the Verdant Hollow to find your first wild creature. Approach it to initiate a battle.', btn:'Got it'},
      {title:'First Battle', text:'In battle, choose abilities to attack. Each creature has 4 abilities. Match your affinity for bonus damage!', btn:'Fight!'},
      {title:'Collection', text:'After weakening a creature, use a Thread Pouch to capture it. Lower HP = better capture chance!', btn:'Understood'},
      {title:'Your Party', text:'Build a team of up to 6 creatures. Visit shrines to heal, save, and buy items. Defeat guardians to unlock new zones.', btn:'Begin Weaving'}
    ];
    this.tutorialStep++;
    if(this.tutorialStep < steps.length) {
      this.showTutorialStep(steps[this.tutorialStep]);
    } else {
      document.getElementById('tutorial-overlay').classList.remove('active');
      this.tutorialActive = false;
      this.player.storyProgress = 1;
      this.showStory(ZONES[0].story, ZONES[0].storyColor);
    }
  },

  // ==================== STORY ====================
  showStory(text, color) {
    this.storyActive = true;
    const el = document.getElementById('story-overlay');
    el.classList.add('active');
    document.getElementById('story-bg').style.background = color;
    document.getElementById('story-content').innerHTML = `
      <h3>${ZONES[this.player.zone].name}</h3>
      <p>${text}</p>
      <button onclick="Game.dismissStory()">Continue</button>
    `;
  },

  dismissStory() {
    document.getElementById('story-overlay').classList.remove('active');
    this.storyActive = false;
  },

  // ==================== HUD ====================
  showHUD() {
    document.getElementById('hud').classList.add('active');
    document.getElementById('zone-label').textContent = ZONES[this.player.zone].name;
    this.updateHUD();
  },

  hideHUD() {
    document.getElementById('hud').classList.remove('active');
  },

  updateHUD() {
    const front = this.player.party[0];
    if(!front) return;
    const hpPct = (front.hp / front.maxHp * 100);
    const bar = document.getElementById('hp-bar');
    bar.style.width = hpPct + '%';
    bar.className = hpPct < 25 ? 'low' : hpPct < 50 ? 'mid' : '';
    document.getElementById('hp-text').textContent = `${front.hp}/${front.maxHp}`;
  },

  // ==================== MOVEMENT ====================
  tryMove(dx, dy) {
    if(this.state !== 'overworld' || this.tutorialActive || this.storyActive || this.battleState) return;
    const nx = this.player.x + dx;
    const ny = this.player.y + dy;
    const map = this.mapTiles;
    if(!map || !map[ny] || !map[ny][nx]) return;
    const tile = map[ny][nx];
    if(tile === 1) return; // wall
    if(tile === 3 && Math.random() > 0.3) return; // partial obstacle
    if(tile === 4 && !this.player.inventory['field_rope']) return; // water

    this.player.x = nx;
    this.player.y = ny;

    // Shrine interaction
    if(this.player.shrinePos && nx === this.player.shrinePos.x && ny === this.player.shrinePos.y) {
      this.restAtShrine();
      return;
    }

    // Boss interaction
    if(nx === Math.floor(this.mapW/2) && ny === this.mapH - 3) {
      const zone = ZONES[this.player.zone];
      if(!this.defeatedBosses.has(zone.boss.id)) {
        this.startBossBattle(zone.boss);
      }
      return;
    }

    // Random encounter
    const zone = ZONES[this.player.zone];
    const rate = this.postGame ? zone.encounterRate * 1.5 : zone.encounterRate;
    if(Math.random() < rate) {
      this.startWildEncounter();
    }
  },

  // ==================== SHRINE ====================
  restAtShrine() {
    // Heal party
    this.player.party.forEach(p => { p.hp = p.maxHp; });
    this.updateHUD();
    // Show shrine UI
    const overlay = document.getElementById('overlay');
    overlay.classList.add('active');
    overlay.innerHTML = `
      <div class="modal" style="position:relative;min-width:300px">
        <button class="modal-close" onclick="Game.closeOverlay()">✕</button>
        <h2>🏛️ Shrine</h2>
        <p style="margin-bottom:1rem;color:var(--text-dim)">A place of rest and renewal.</p>
        <div style="display:flex;flex-direction:column;gap:0.5rem">
          <button class="battle-btn" onclick="Game.shrineHeal()">Heal Party (${this.player.gold}g)</button>
          <button class="battle-btn" onclick="Game.shrineShop()">Shop</button>
          <button class="battle-btn" onclick="Game.saveGame();Game.closeOverlay()">Save Game</button>
          ${this.player.storyProgress <= 2 ? `<button class="battle-btn" onclick="Game.advanceStory()">Continue Story</button>` : ''}
        </div>
      </div>
    `;
  },

  shrineHeal() {
    const cost = this.player.party.length * 20;
    if(this.player.gold >= cost) {
      this.player.gold -= cost;
      this.player.party.forEach(p => { p.hp = p.maxHp; });
      this.updateHUD();
      this.restAtShrine();
    } else {
      this.showFloatText(this.player.x * this.tileW, this.player.y * this.tileH, 'Not enough gold!', '#f87171');
    }
  },

  shrineShop() {
    const overlay = document.getElementById('overlay');
    overlay.innerHTML = `
      <div class="modal" style="position:relative;min-width:350px" id="shop-modal">
        <button class="modal-close" onclick="Game.closeOverlay()">✕</button>
        <h2>🏪 Shrine Shop</h2>
        <p style="margin-bottom:0.5rem;color:var(--text-dim)">Gold: ${this.player.gold}g</p>
        <div class="inv-grid" style="margin-bottom:1rem">
          ${Object.entries(ITEMS).filter(([k,v]) => v.price <= this.player.gold * 2).map(([k,v]) => `
            <div class="inv-item" onclick="Game.buyItem('${k}')">
              <div class="icon">${v.icon}</div>
              <div style="font-size:0.7rem">${v.name}</div>
              <div style="font-size:0.65rem;color:var(--warning)">${v.price}g</div>
            </div>
          `).join('')}
        </div>
      </div>
    `;
  },

  buyItem(itemId) {
    const item = ITEMS[itemId];
    if(!item || this.player.gold < item.price) return;
    this.player.gold -= item.price;
    this.player.inventory[itemId] = (this.player.inventory[itemId] || 0) + 1;
    this.shrineShop();
    this.showFloatText(this.player.x * this.tileW, this.player.y * this.tileH, `+${item.name}`, '#4ade80');
  },

  advanceStory() {
    this.player.storyProgress++;
    const zone = ZONES[this.player.zone];
    if(!this.defeatedBosses.has(zone.boss.id)) {
      this.showStory('Defeat the zone guardian to advance the story.', zone.storyColor);
      return;
    }
    if(this.player.storyProgress <= 4) {
      const nextZone = this.player.storyProgress;
      if(nextZone < 6) {
        this.showStory(`The path to ${ZONES[nextZone].name} opens before you. The threads of fate guide your way.`, ZONES[nextZone].storyColor);
      }
    }
    if(this.player.storyProgress >= 5) {
      this.player.storyProgress = 0;
      this.showStory('You have woven the threads. The Loom is restored. The Weave continues...', '#0a0a1a');
    }
  },

  // ==================== OVERWORLD RENDERING ====================
  renderOverworld(dt) {
    const ctx = this.ctx;
    const w = this.canvas.clientWidth;
    const h = this.canvas.clientHeight;
    const zone = ZONES[this.player.zone];

    // Background gradient
    const grad = ctx.createLinearGradient(0, 0, 0, h);
    grad.addColorStop(0, zone.gradient[0]);
    grad.addColorStop(1, zone.gradient[1]);
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, w, h);

    // Camera
    this.camera.x = this.player.x * this.tileW - w/2 + this.tileW/2;
    this.camera.y = this.player.y * this.tileH - h/2 + this.tileH/2;

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

    // Draw map
    const map = this.mapTiles;
    const tileColors = {
      0: '#1a3a1a', 1: '#0a0a1a', 2: '#2a4a2a',
      3: '#0a2a0a', 4: '#0a1a3a', 5: '#2a2a5a', 6: '#3a1a1a'
    };
    const tileEmojis = { 5: '🏛️', 6: '💀' };
    const start = Math.max(0, Math.floor(this.camera.x / this.tileW));
    const end = Math.min(this.mapW, Math.ceil((this.camera.x + w) / this.tileW) + 1);
    const startY = Math.max(0, Math.floor(this.camera.y / this.tileH));
    const endY = Math.min(this.mapH, Math.ceil((this.camera.y + h) / this.tileH) + 1);

    for(let y = startY; y < endY; y++) {
      for(let x = start; x < end; x++) {
        if(!map[y] || !map[y][x]) continue;
        const tile = map[y][x];
        ctx.fillStyle = tileColors[tile] || '#1a1a1a';
        ctx.fillRect(x * this.tileW, y * this.tileH, this.tileW, this.tileH);
        if(tileEmojis[tile]) {
          ctx.font = `${this.tileW * 0.7}px serif`;
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.fillText(tileEmojis[tile], x * this.tileW + this.tileW/2, y * this.tileH + this.tileH/2);
        }
      }
    }

    // Player
    const px = this.player.x * this.tileW;
    const py = this.player.y * this.tileH;
    const pulse = Math.sin(Date.now() / 300) * 0.1 + 1;
    ctx.save();
    ctx.translate(px + this.tileW/2, py + this.tileH/2);
    ctx.scale(pulse, pulse);
    ctx.font = `${this.tileW * 0.8}px serif`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('🧝', 0, 0);
    ctx.restore();

    // Ambient particles
    const pColor = zone.particleColor;
    this.particles.forEach(p => {
      p.x += p.vx; p.y += p.vy;
      p.life -= dt;
      if(p.life <= 0) {
        p.x = this.camera.x + Math.random() * w;
        p.y = this.camera.y + Math.random() * h;
        p.life = Math.random() * 200 + 100;
      }
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      ctx.fillStyle = pColor + Math.floor(p.alpha * 255).toString(16).padStart(2,'0');
      ctx.fill();
    });

    ctx.restore();

    // Zone name display
    const zn = document.getElementById('zone-name');
    if(this.zoneNameTimer > 0) {
      zn.classList.add('show');
      this.zoneNameTimer -= dt;
      if(this.zoneNameTimer <= 0) zn.classList.remove('show');
    }
  },

  // ==================== BATTLE SYSTEM ====================
  startWildEncounter() {
    const zone = ZONES[this.player.zone];
    const spawnPool = this.postGame ? zone.enemies.map(e => ({...e, weight: e.weight * 1.5})) : zone.enemies;
    const totalWeight = spawnPool.reduce((s,e) => s + e.weight, 0);
    let r = Math.random() * totalWeight;
    let chosen = spawnPool[0];
    for(const e of spawnPool) {
      r -= e.weight;
      if(r <= 0) { chosen = e; break; }
    }
    const base = CREATURES.find(c => c.id === chosen.id);
    if(!base) return;
    const level = 3 + this.player.zone * 5 + Math.floor(Math.random() * 8);
    const enemy = this.createCreature(base, level);
    const numEnemies = Math.random() < 0.2 ? 2 : 1;
    const enemies = [enemy];
    if(numEnemies === 2) {
      const extra = CREATURES.find(c => c.id === chosen.id);
      if(extra) enemies.push(this.createCreature(extra, level - 1));
    }
    this.currentEnemy = enemies;
    this.startBattle(enemies, false);
  },

  startBossBattle(bossData) {
    const base = CREATURES.find(c => c.id === bossData.id);
    if(!base) return;
    const enemy = this.createCreature(base, bossData.level);
    this.currentEnemy = [enemy];
    this.startBattle([enemy], true, bossData);
  },

  createCreature(base, level) {
    const hp = base.baseHp + level * 5;
    const atk = base.baseAtk + level * 3;
    const def = base.baseDef + level * 2;
    const spd = base.baseSpd + Math.floor(level * 0.5);
    return {
      ...base,
      level, hp, maxHp: hp, atk, def, spd,
      status: null, statusTurns: 0,
      barrierActive: false, regenTurns: 0, drainTurns: 0
    };
  },

  startBattle(enemies, isBoss, bossData) {
    this.state = 'battle';
    this.hideHUD();
    document.getElementById('battle-screen').classList.add('active');
    this.battleState = {
      enemies,
      isBoss,
      bossData,
      turn: 0,
      turnOrder: [],
      phase: 'player_turn', // player_turn, enemy_turn, capture, victory
      playerIdx: 0,
      selectedCreature: 0,
      animating: false,
      result: null
    };
    this.battleParticles = [];
    this.initBattleParticles();

    // Build turn order based on SPD
    const all = [...enemies.map((e,i) => ({unit:e, type:'enemy', idx:i, spd:e.spd}))];
    this.player.party.forEach((p,i) => {
      all.push({unit:p, type:'player', idx:i, spd:p.spd});
    });
    all.sort((a,b) => b.spd - a.spd);
    this.battleState.turnOrder = all;

    // Show encounter text
    if(isBoss) {
      this.showBattleText(`⚠️ ${bossData.name} blocks your path!`);
    } else {
      const name = enemies[0].name;
      this.showBattleText(`A wild ${name} appears!`);
    }

    // Show capture items
    this.renderBattleActions();
    this.renderBattleScene();
  },

  initBattleParticles() {
    this.battleParticles = [];
    for(let i = 0; i < 30; i++) {
      this.battleParticles.push({
        x: Math.random() * this.battleCanvas.clientWidth,
        y: Math.random() * this.battleCanvas.clientHeight,
        vx: (Math.random() - 0.5) * 0.3,
        vy: (Math.random() - 0.5) * 0.3,
        size: Math.random() * 2 + 0.5,
        alpha: Math.random() * 0.3 + 0.05,
        color: '#ffffff'
      });
    }
  },

  showBattleText(text) {
    document.getElementById('battle-text').textContent = text;
  },

  renderBattleActions() {
    const bs = this.battleState;
    if(bs.phase !== 'player_turn') return;
    const container = document.getElementById('battle-actions');
    const creature = this.player.party[bs.selectedCreature];
    if(!creature) return;

    let html = '';

    // Abilities
    creature.abilities.forEach(abId => {
      const ab = ABILITY_MAP[abId];
      if(!ab) return;
      const ppLeft = creature.pp ? (creature.pp[abId] || ab.pp) : ab.pp;
      const typeColor = TYPES[ab.type]?.color || '#fff';
      html += `<button class="battle-btn ability" onclick="Game.useAbility('${abId}')" ${ppLeft <= 0 ? 'disabled' : ''}>
        <span class="type-indicator" style="background:${typeColor}"></span>
        ${ab.name}
        <span style="font-size:0.65rem;color:var(--text-dim)">PP:${ppLeft}</span>
      </button>`;
    });

    // Switch
    html += `<button class="battle-btn" onclick="Game.openSwitch()">Switch</button>`;

    // Capture
    const hasCapture = Object.keys(this.player.inventory).some(k => ITEMS[k]?.type === 'capture');
    if(hasCapture && !bs.isBoss) {
      html += `<button class="battle-btn" onclick="Game.tryCapture()">Capture</button>`;
    }

    // Run
    html += `<button class="battle-btn" onclick="Game.runAway()">Run</button>`;

    container.innerHTML = html;
  },

  useAbility(abId) {
    const bs = this.battleState;
    if(bs.phase !== 'player_turn' || bs.animating) return;
    const creature = this.player.party[bs.selectedCreature];
    if(!creature) return;
    const ab = ABILITY_MAP[abId];
    if(!ab) return;

    bs.animating = true;
    bs.phase = 'animating';

    // Check PP
    if(!creature.pp) creature.pp = {};
    if(creature.pp[abId] !== undefined) {
      creature.pp[abId]--;
    } else {
      creature.pp[abId] = ab.pp - 1;
    }

    // Handle priority
    if(ab.eff === 'priority') {
      // Will be handled in turn order
    }

    // Target enemies
    const targets = bs.enemies.filter(e => e.hp > 0);
    if(targets.length === 0) {
      this.endBattle(true);
      return;
    }

    // Execute ability
    this.executeAbility(creature, ab, targets, 'player');
  },

  executeAbility(attacker, ability, targets, side) {
    const bs = this.battleState;
    const isPlayer = side === 'player';

    // Check for barrier on target
    let barrierDeflected = false;
    if(!isPlayer) {
      for(const t of targets) {
        if(t.barrierActive) {
          t.barrierActive = false;
          barrierDeflected = true;
          this.showBattleText(`${t.name}'s barrier deflects the attack!`);
          this.addBattleParticle(t.x || 200, t.y || 300, '#fbbf24', 10);
          break;
        }
      }
    }

    if(!barrierDeflected) {
      // Calculate damage
      for(const target of targets) {
        if(target.hp <= 0) continue;

        let power = ability.power;
        let accuracy = ability.acc;

        if(power > 0) {
          // STAB
          let stab = 1;
          if(ability.type === attacker.affinity) stab = 1.25;

          // Type effectiveness
          let eff = 1;
          const typeChart = TYPE_CHART[ability.type];
          if(typeChart && typeChart.vs[attacker.affinity] !== undefined) {
            eff = typeChart.vs[target.affinity] || 1;
          }

          // Damage formula
          const raw = (0.4 * attacker.level * attacker.atk / target.def) * stab * eff;
          const variance = 0.9 + Math.random() * 0.2;
          const damage = Math.max(1, Math.floor(raw * variance));

          // Critical hit (6.25% chance)
          let crit = false;
          if(Math.random() < 0.0625) {
            crit = true;
            target.hp -= Math.floor(damage * 1.5);
            this.showFloatText(target.x || 200, target.y || 250, `💥${Math.floor(damage * 1.5)}`, '#fbbf24');
          } else {
            target.hp -= damage;
            this.showFloatText(target.x || 200, target.y || 250, `-${damage}`, crit ? '#fbbf24' : '#f87171');
          }

          // Screen shake on hit
          if(damage > 30) {
            document.getElementById('battle-screen').classList.add('screen-shake');
            setTimeout(() => document.getElementById('battle-screen').classList.remove('screen-shake'), 300);
          }

          // Super effective flash
          if(eff > 1) {
            this.addBattleParticle(target.x || 200, target.y || 300, TYPES[target.affinity]?.color || '#fff', 15);
          }

          // Apply effects
          if(ability.eff === 'burn' && !target.status) {
            target.status = 'burn';
            target.statusTurns = 3;
            this.showBattleText(`${target.name} is burned!`);
          }
          if(ability.eff === 'frost' && !target.status) {
            target.status = 'frost';
            target.statusTurns = 3;
            this.showBattleText(`${target.name} is frozen!`);
          }
          if(ability.eff === 'shock' && !target.status) {
            target.status = 'shock';
            target.statusTurns = 1;
            this.showBattleText(`${target.name} is shocked!`);
          }
          if(ability.eff === 'barrier') {
            attacker.barrierActive = true;
            this.showBattleText(`${attacker.name} raised a barrier!`);
          }
          if(ability.eff === 'regen') {
            attacker.regenTurns = 4;
            this.showBattleText(`${attacker.name} is regenerating!`);
          }
          if(ability.eff === 'heal') {
            const healAmt = Math.floor(attacker.maxHp * 0.4);
            attacker.hp = Math.min(attacker.maxHp, attacker.hp + healAmt);
            this.showFloatText(attacker.x || 150, attacker.y || 250, `+${healAmt}`, '#4ade80');
          }
          if(ability.eff === 'drain') {
            const drainAmt = Math.floor(damage * 0.5);
            attacker.hp = Math.min(attacker.maxHp, attacker.hp + drainAmt);
            this.showFloatText(attacker.x || 150, attacker.y || 250, `+${drainAmt}`, '#4ade80');
          }
          if(ability.eff === 'defense_up') {
            attacker.def = Math.floor(attacker.def * 1.3);
            this.showBattleText(`${attacker.name}'s DEF rose!`);
          }
          if(ability.eff === 'speed_up') {
            attacker.spd = Math.floor(attacker.spd * 1.2);
            this.showBattleText(`${attacker.name}'s SPD rose!`);
          }
          if(ability.eff === 'multi') {
            // Multi-hit handled separately
          }
        } else {
          // Status-only ability
          if(ability.eff === 'barrier') {
            attacker.barrierActive = true;
            this.showBattleText(`${attacker.name} raised a barrier!`);
          }
          if(ability.eff === 'heal') {
            const healAmt = Math.floor(attacker.maxHp * 0.4);
            attacker.hp = Math.min(attacker.maxHp, attacker.hp + healAmt);
            this.showFloatText(attacker.x || 150, attacker.y || 250, `+${healAmt}`, '#4ade80');
          }
          if(ability.eff === 'regen') {
            attacker.regenTurns = 4;
            this.showBattleText(`${attacker.name} is regenerating!`);
          }
          if(ability.eff === 'defense_up') {
            attacker.def = Math.floor(attacker.def * 1.3);
            this.showBattleText(`${attacker.name}'s DEF rose!`);
          }
          if(ability.eff === 'speed_up') {
            attacker.spd = Math.floor(attacker.spd * 1.2);
            this.showBattleText(`${attacker.name}'s SPD rose!`);
          }
        }

        // Clamp HP
        target.hp = Math.max(0, Math.min(target.maxHp, target.hp));
      }
    }

    this.renderBattleScene();

    // Check for KO
    setTimeout(() => {
      const aliveEnemies = bs.enemies.filter(e => e.hp > 0);
      if(aliveEnemies.length === 0) {
        this.endBattle(true);
        return;
      }

      // Check if player has any creatures alive
      const alivePlayer = this.player.party.filter(p => p.hp > 0);
      if(alivePlayer.length === 0) {
        this.endBattle(false);
        return;
      }

      // Enemy turn
      bs.phase = 'enemy_turn';
      this.renderBattleActions();
      setTimeout(() => this.enemyTurn(), 800);
    }, 600);
  },

  enemyTurn() {
    const bs = this.battleState;
    const enemyUnits = bs.enemies.filter(e => e.hp > 0);
    if(enemyUnits.length === 0) {
      this.endBattle(true);
      return;
    }

    let idx = 0;
    const processNext = () => {
      if(idx >= enemyUnits.length) {
        // Back to player turn
        bs.phase = 'player_turn';
        bs.animating = false;
        this.renderBattleActions();
        this.renderBattleScene();
        return;
      }

      const enemy = enemyUnits[idx];
      idx++;

      // Check status effects
      if(enemy.status === 'shock' && enemy.statusTurns > 0) {
        enemy.statusTurns--;
        this.showBattleText(`${enemy.name} is paralyzed and can't move!`);
        setTimeout(processNext, 600);
        return;
      }

      // Choose ability
      const availableAbilities = enemy.abilities.filter(abId => {
        const ab = ABILITY_MAP[abId];
        if(!ab) return false;
        if(!enemy.pp) enemy.pp = {};
        return (enemy.pp[abId] !== undefined ? enemy.pp[abId] : ab.pp) > 0;
      });

      if(availableAbilities.length === 0) {
        // Struggle
        const alivePlayer = this.player.party.filter(p => p.hp > 0);
        if(alivePlayer.length > 0) {
          const target = alivePlayer[Math.floor(Math.random() * alivePlayer.length)];
          const damage = Math.max(1, Math.floor(enemy.atk * 0.3));
          target.hp = Math.max(0, target.hp - damage);
          this.showBattleText(`${enemy.name} struggles! ${target.name} takes ${damage} damage.`);
          this.showFloatText(target.x || 150, target.y || 250, `-${damage}`, '#f87171');
        }
        setTimeout(processNext, 600);
        return;
      }

      const abId = availableAbilities[Math.floor(Math.random() * availableAbilities.length)];
      const ab = ABILITY_MAP[abId];

      // Consume PP
      if(!enemy.pp) enemy.pp = {};
      if(enemy.pp[abId] === undefined) enemy.pp[abId] = ab.pp;
      enemy.pp[abId]--;

      const alivePlayer = this.player.party.filter(p => p.hp > 0);
      if(alivePlayer.length === 0) {
        this.endBattle(false);
        return;
      }

      // Target random alive player
      const targets = [alivePlayer[Math.floor(Math.random() * alivePlayer.length)]];
      this.executeAbility(enemy, ab, targets, 'enemy');

      setTimeout(processNext, 800);
    };

    processNext();
  },

  tryCapture() {
    const bs = this.battleState;
    if(bs.phase !== 'player_turn' || bs.animating) return;
    if(bs.isBoss) return;

    const target = bs.enemies[0];
    if(target.hp <= 0) return;

    // Find best capture item
    let bestItem = null;
    let bestTier = 0;
    for(const [k, v] of Object.entries(this.player.inventory)) {
      if(ITEMS[k] && ITEMS[k].type === 'capture' && v > 0 && ITEMS[k].tier > bestTier) {
        bestItem = k;
        bestTier = ITEMS[k].tier;
      }
    }

    if(!bestItem) {
      this.showBattleText('No capture items!');
      return;
    }

    // Calculate capture rate
    const hpRatio = target.hp / target.maxHp;
    const tierBonus = bestTier * 0.15;
    const captureRate = (1 - hpRatio) * 0.6 + tierBonus + 0.1;
    const success = Math.random() < Math.min(captureRate, 0.95);

    // Consume item
    this.player.inventory[bestItem]--;
    if(this.player.inventory[bestItem] <= 0) delete this.player.inventory[bestItem];

    if(success) {
      // Add to party
      const newCreature = this.createCreature(target, target.level);
      if(this.player.party.length < 6) {
        this.player.party.push(newCreature);
      } else {
        // Replace last with captured
        this.player.party[this.player.party.length - 1] = newCreature;
      }
      this.captured.add(target.id);
      this.collectedCount = this.captured.size;
      this.showBattleText(`Captured ${target.name}!`);
      this.addBattleParticle(200, 300, '#4ade80', 20);

      // Check collection goal
      if(this.collectedCount >= 8 && !this.postGame) {
        this.postGame = true;
        this.showBattleText('You have collected 8 creatures! The post-game opens!');
      }

      this.endBattle(true);
    } else {
      this.showBattleText(`${target.name} broke free!`);
      this.addBattleParticle(200, 300, '#f87171', 10);
      bs.phase = 'enemy_turn';
      this.renderBattleActions();
      setTimeout(() => this.enemyTurn(), 800);
    }
  },

  runAway() {
    const bs = this.battleState;
    if(bs.isBoss) {
      this.showBattleText('Cannot flee from a boss!');
      return;
    }
    if(Math.random() < 0.7) {
      this.showBattleText('Got away safely!');
      setTimeout(() => this.endBattle(false), 500);
    } else {
      this.showBattleText('Couldn\'t escape!');
      bs.phase = 'enemy_turn';
      this.renderBattleActions();
      setTimeout(() => this.enemyTurn(), 800);
    }
  },

  openSwitch() {
    const bs = this.battleState;
    if(bs.phase !== 'player_turn' || bs.animating) return;

    const overlay = document.getElementById('overlay');
    overlay.classList.add('active');
    overlay.innerHTML = `
      <div class="modal" style="position:relative;min-width:300px" id="switch-modal">
        <button class="modal-close" onclick="Game.closeOverlay()">✕</button>
        <h2>🔄 Switch Creature</h2>
        <div class="team-grid" style="margin-top:1rem">
          ${this.player.party.map((p, i) => `
            <div class="team-card ${i === bs.selectedCreature ? 'selected' : ''}" onclick="Game.switchCreature(${i})">
              <div class="name">${p.sprite} ${p.name}</div>
              <div class="level">Lv.${p.level}</div>
              <div class="hp-mini"><div class="hp-mini-fill" style="width:${p.hp/p.maxHp*100}%"></div></div>
              <div style="font-size:0.7rem;color:var(--text-dim)">${p.hp}/${p.maxHp} HP</div>
            </div>
          `).join('')}
        </div>
      </div>
    `;
  },

  switchCreature(idx) {
    this.battleState.selectedCreature = idx;
    this.closeOverlay();
    this.renderBattleActions();
  },

  endBattle(won) {
    const bs = this.battleState;
    bs.phase = won ? 'victory' : 'defeat';

    if(won) {
      // EXP calculation
      const totalExp = bs.enemies.reduce((s, e) => {
        return s + e.level * 10 * (bs.isBoss ? 3 : 1);
      }, 0);

      // Distribute EXP to party
      this.player.party.forEach(p => {
        if(p.hp > 0) {
          let expGain = Math.floor(totalExp / this.player.party.filter(pp => pp.hp > 0).length);
          // Apply exp boost
          if(this.player.inventory['exp_boost']) {
            expGain = Math.floor(expGain * 1.2);
          }
          p.exp = (p.exp || 0) + expGain;
          // Level up check
          while(p.exp >= p.level * 20) {
            p.exp -= p.level * 20;
            p.level++;
            p.maxHp += 5;
            p.hp = Math.min(p.hp + 5, p.maxHp);
            p.atk += 3;
            p.def += 2;
            p.spd += 1;
            // Learn new abilities
            this.learnAbility(p);
          }
          p.hp = Math.min(p.hp, p.maxHp);
        }
      });

      // Gold reward
      this.player.gold += bs.isBoss ? 200 : 20 + bs.enemies[0]?.level * 5;

      // Boss defeated
      if(bs.isBoss && bs.bossData) {
        this.defeatedBosses.add(bs.bossData.id);
        this.showBattleText(`Defeated ${bs.bossData.name}!`);
      } else {
        this.showBattleText('Victory!');
      }

      this.renderBattleScene();

      // Check zone completion
      if(bs.isBoss && bs.bossData) {
        setTimeout(() => {
          this.endBattleScreen();
          this.showStory(`The guardian of ${ZONES[this.player.zone].name} has fallen. The path forward opens.`, ZONES[this.player.zone].storyColor);
        }, 1500);
      } else {
        setTimeout(() => this.endBattleScreen(), 1000);
      }
    } else {
      this.showBattleText('Defeat...');
      // Auto-heal party
      this.player.party.forEach(p => { p.hp = Math.floor(p.maxHp * 0.5); });
      this.player.gold = Math.max(0, this.player.gold - 50);
      setTimeout(() => {
        this.endBattleScreen();
        this.showStory('You were defeated. Rest at the nearest shrine to recover.', '#1a0a0a');
      }, 1500);
    }
  },

  learnAbility(creature) {
    const base = CREATURES.find(c => c.id === creature.id || (c.evolved && c.evolveTo === creature.id));
    if(!base) return;
    const allAbilities = base.abilities;
    const learned = creature.abilities || base.abilities;
    // Simple: add one new ability at certain levels
    if(creature.level === 10 && !learned.includes('thunder')) learned.push('thunder');
    if(creature.level === 15 && !learned.includes('gale_spin')) learned.push('gale_spin');
    if(creature.level === 20 && !learned.includes('gale_strike')) learned.push('gale_strike');
    if(creature.level === 25 && !learned.includes('flux_wave')) learned.push('flux_wave');
    if(creature.level === 30 && !learned.includes('earthquake')) learned.push('earthquake');
    if(creature.level === 35 && !learned.includes('void_burst')) learned.push('void_burst');
    if(creature.level === 40 && !learned.includes('aether_burst')) learned.push('aether_burst');
  },

  endBattleScreen() {
    this.state = 'overworld';
    document.getElementById('battle-screen').classList.remove('active');
    this.showHUD();
    this.updateHUD();
    this.battleState = null;
    this.currentEnemy = null;
  },

  // ==================== BATTLE SCENE RENDERING ====================
  renderBattleScene() {
    const ctx = this.battleCtx;
    const w = this.battleCanvas.clientWidth;
    const h = this.battleCanvas.clientHeight;
    const bs = this.battleState;
    if(!bs) return;

    ctx.clearRect(0, 0, w, h);

    // Background gradient based on zone
    const zone = ZONES[this.player.zone];
    const grad = ctx.createLinearGradient(0, 0, 0, h);
    grad.addColorStop(0, zone.gradient[0]);
    grad.addColorStop(1, zone.gradient[1]);
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, w, h);

    // Ground
    ctx.fillStyle = 'rgba(30,30,60,0.5)';
    ctx.fillRect(0, h * 0.6, w, h * 0.4);

    // Draw enemies
    bs.enemies.forEach((enemy, i) => {
      if(enemy.hp <= 0) return;
      const ex = w * 0.6 + i * 80;
      const ey = h * 0.35;
      enemy.x = ex;
      enemy.y = ey;

      // Enemy sprite
      ctx.font = '48px serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(enemy.sprite, ex, ey);

      // Enemy name & level
      ctx.font = '14px system-ui';
      ctx.fillStyle = '#e8e8f0';
      ctx.fillText(`${enemy.name} Lv.${enemy.level}`, ex, ey - 40);

      // Enemy HP bar
      const barW = 100;
      const barH = 8;
      const barX = ex - barW/2;
      const barY = ey - 25;
      ctx.fillStyle = 'rgba(0,0,0,0.5)';
      ctx.fillRect(barX, barY, barW, barH);
      const hpPct = enemy.hp / enemy.maxHp;
      ctx.fillStyle = hpPct < 0.25 ? '#f87171' : hpPct < 0.5 ? '#fbbf24' : '#4ade80';
      ctx.fillRect(barX, barY, barW * hpPct, barH);

      // Status indicators
      if(enemy.status) {
        const statusEmoji = enemy.status === 'burn' ? '🔥' : enemy.status === 'frost' ? '❄️' : enemy.status === 'shock' ? '⚡' : '';
        if(statusEmoji) {
          ctx.font = '16px serif';
          ctx.fillText(statusEmoji, ex + 25, ey - 10);
        }
      }
      if(enemy.barrierActive) {
        ctx.font = '20px serif';
        ctx.fillText('🛡️', ex, ey + 30);
      }
    });

    // Draw player party (front creature)
    const playerCreature = this.player.party[bs.selectedCreature];
    if(playerCreature && playerCreature.hp > 0) {
      const px = w * 0.2;
      const py = h * 0.55;
      playerCreature.x = px;
      playerCreature.y = py;

      // Creature sprite
      ctx.font = '56px serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(playerCreature.sprite, px, py);

      // Name & level
      ctx.font = '14px system-ui';
      ctx.fillStyle = '#e8e8f0';
      ctx.fillText(`${playerCreature.name} Lv.${playerCreature.level}`, px, py - 45);

      // HP bar
      const barW = 120;
      const barH = 10;
      const barX = px - barW/2;
      const barY = py - 30;
      ctx.fillStyle = 'rgba(0,0,0,0.5)';
      ctx.fillRect(barX, barY, barW, barH);
      const hpPct = playerCreature.hp / playerCreature.maxHp;
      ctx.fillStyle = hpPct < 0.25 ? '#f87171' : hpPct < 0.5 ? '#fbbf24' : '#4ade80';
      ctx.fillRect(barX, barY, barW * hpPct, barH);

      // Status
      if(playerCreature.status) {
        const statusEmoji = playerCreature.status === 'burn' ? '🔥' : playerCreature.status === 'frost' ? '❄️' : playerCreature.status === 'shock' ? '⚡' : '';
        if(statusEmoji) {
          ctx.font = '16px serif';
          ctx.fillText(statusEmoji, px + 30, py - 10);
        }
      }
      if(playerCreature.barrierActive) {
        ctx.font = '20px serif';
        ctx.fillText('🛡️', px, py + 35);
      }
      if(playerCreature.regenTurns > 0) {
        ctx.font = '14px serif';
        ctx.fillText('💚', px - 30, py - 10);
      }
    }

    // Battle particles
    this.battleParticles.forEach(p => {
      p.x += p.vx; p.y += p.vy;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      ctx.fillStyle = p.color + Math.floor(p.alpha * 255).toString(16).padStart(2,'0');
      ctx.fill();
    });
  },

  addBattleParticle(x, y, color, count) {
    for(let i = 0; i < count; i++) {
      this.battleParticles.push({
        x, y,
        vx: (Math.random() - 0.5) * 4,
        vy: (Math.random() - 0.5) * 4,
        size: Math.random() * 4 + 1,
        alpha: Math.random() * 0.8 + 0.2,
        color
      });
    }
  },

  showFloatText(x, y, text, color) {
    const el = document.createElement('div');
    el.className = 'float-text';
    el.textContent = text;
    el.style.left = x + 'px';
    el.style.top = y + 'px';
    el.style.color = color;
    el.style.fontSize = text.length > 5 ? '1rem' : '1.2rem';
    document.getElementById('game-container').appendChild(el);
    setTimeout(() => el.remove(), 1000);
  },

  // ==================== MENUS ====================
  openTeam() {
    const overlay = document.getElementById('overlay');
    overlay.classList.add('active');
    overlay.innerHTML = `
      <div class="modal" style="position:relative;min-width:400px" id="team-modal">
        <button class="modal-close" onclick="Game.closeOverlay()">✕</button>
        <h2>👥 Team</h2>
        <p style="margin-bottom:1rem;color:var(--text-dim)">Collected: ${this.captured.size}/27</p>
        <div class="team-grid">
          ${this.player.party.map((p, i) => `
            <div class="team-card" onclick="Game.openTeamDetail(${i})">
              <div class="name">${p.sprite} ${p.name}</div>
              <div class="level">Lv.${p.level} ${p.affinity}</div>
              <div class="hp-mini"><div class="hp-mini-fill" style="width:${p.hp/p.maxHp*100}%"></div></div>
              <div style="font-size:0.7rem;color:var(--text-dim)">HP:${p.hp}/${p.maxHp} ATK:${p.atk} DEF:${p.def} SPD:${p.spd}</div>
            </div>
          `).join('')}
        </div>
      </div>
    `;
  },

  openTeamDetail(idx) {
    const p = this.player.party[idx];
    const overlay = document.getElementById('overlay');
    overlay.innerHTML = `
      <div class="modal" style="position:relative;min-width:350px" id="detail-modal">
        <button class="modal-close" onclick="Game.closeOverlay()">✕</button>
        <h2>${p.sprite} ${p.name}</h2>
        <p style="color:var(--text-dim);margin-bottom:0.5rem">Lv.${p.level} ${p.affinity}</p>
        <p style="font-size:0.85rem;margin-bottom:1rem;font-style:italic">"${p.lore}"</p>
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;margin-bottom:1rem">
          <div>HP: ${p.hp}/${p.maxHp}</div>
          <div>ATK: ${p.atk}</div>
          <div>DEF: ${p.def}</div>
          <div>SPD: ${p.spd}</div>
        </div>
        <h3 style="margin-bottom:0.5rem">Abilities</h3>
        <div style="display:flex;flex-direction:column;gap:0.3rem">
          ${p.abilities.map(abId => {
            const ab = ABILITY_MAP[abId];
            if(!ab) return '';
            const ppLeft = p.pp ? (p.pp[abId] !== undefined ? p.pp[abId] : ab.pp) : ab.pp;
            const typeColor = TYPES[ab.type]?.color || '#fff';
            return `<div style="display:flex;align-items:center;gap:0.5rem;padding:0.3rem;background:rgba(30,30,60,0.5);border-radius:6px">
              <span style="width:8px;height:8px;border-radius:50%;background:${typeColor};flex-shrink:0"></span>
              <span style="flex:1;font-size:0.85rem">${ab.name}</span>
              <span style="font-size:0.7rem;color:var(--text-dim)">PP:${ppLeft}/${ab.pp}</span>
              <span style="font-size:0.7rem;color:var(--text-dim)">${ab.power > 0 ? ab.power + ' power' : ab.eff}</span>
            </div>`;
          }).join('')}
        </div>
        ${p.evolved ? '' : `<div style="margin-top:1rem;padding:0.5rem;background:rgba(123,104,238,0.2);border-radius:8px;font-size:0.8rem">
          <strong>Evolution:</strong> ${p.evoLevel || '?'}+ ${p.evolveTo ? '→ ' + CREATURES.find(c=>c.id===p.evolveTo)?.name : '?'}
        </div>`}
      </div>
    `;
  },

  openInventory() {
    const overlay = document.getElementById('overlay');
    overlay.classList.add('active');
    const invItems = Object.entries(this.player.inventory).filter(([k,v]) => v > 0);
    overlay.innerHTML = `
      <div class="modal" style="position:relative;min-width:350px" id="inv-modal">
        <button class="modal-close" onclick="Game.closeOverlay()">✕</button>
        <h2>🎒 Bag</h2>
        <p style="margin-bottom:1rem;color:var(--text-dim)">Gold: ${this.player.gold}g</p>
        ${invItems.length === 0 ? '<p style="color:var(--text-dim)">Empty</p>' : ''}
        <div class="inv-grid">
          ${invItems.map(([k,v]) => {
            const item = ITEMS[k];
            if(!item) return '';
            return `<div class="inv-item" onclick="Game.useItem('${k}')">
              <div class="icon">${item.icon}</div>
              <div style="font-size:0.7rem">${item.name}</div>
              <div style="font-size:0.65rem;color:var(--text-dim)">x${v}</div>
            </div>`;
          }).join('')}
        </div>
      </div>
    `;
  },

  useItem(itemId) {
    const item = ITEMS[itemId];
    if(!item || !this.player.inventory[itemId] || this.player.inventory[itemId] <= 0) return;

    if(item.type === 'heal') {
      const front = this.player.party[0];
      if(!front) return;
      const healAmt = item.heal >= 999 ? front.maxHp - front.hp : item.heal;
      front.hp = Math.min(front.maxHp, front.hp + healAmt);
      this.player.inventory[itemId]--;
      if(this.player.inventory[itemId] <= 0) delete this.player.inventory[itemId];
      this.showFloatText(this.player.x * this.tileW, this.player.y * this.tileH, `+${healAmt} HP`, '#4ade80');
      this.updateHUD();
      this.openInventory();
    } else if(item.type === 'catalyst') {
      // Check if any creature can evolve
      const canEvolve = this.player.party.find(p => {
        if(p.evolved) return false;
        const base = CREATURES.find(c => c.id === p.id);
        if(!base || !base.evoLevel) return false;
        return p.level >= base.evoLevel;
      });
      if(canEvolve && canEvolve.affinity === item.affinity) {
        const newBase = CREATURES.find(c => c.id === canEvolve.evolveTo);
        if(newBase) {
          const newCreature = this.createCreature(newBase, canEvolve.level);
          newCreature.abilities = newBase.abilities;
          newCreature.exp = canEvolve.exp || 0;
          newCreature.pp = canEvolve.pp || {};
          newCreature.status = canEvolve.status;
          newCreature.statusTurns = canEvolve.statusTurns;
          newCreature.barrierActive = canEvolve.barrierActive;
          newCreature.regenTurns = canEvolve.regenTurns;
          newCreature.drainTurns = canEvolve.drainTurns;
          const idx = this.player.party.indexOf(canEvolve);
          this.player.party[idx] = newCreature;
          this.captured.delete(canEvolve.id);
          this.captured.add(newBase.id);
          this.player.inventory[itemId]--;
          if(this.player.inventory[itemId] <= 0) delete this.player.inventory[itemId];
          this.showFloatText(this.player.x * this.tileW, this.player.y * this.tileH, `Evolved to ${newBase.name}!`, '#fbbf24');
          this.updateHUD();
          this.openInventory();
        }
      } else {
        this.showFloatText(this.player.x * this.tileW, this.player.y * this.tileH, 'No compatible creature!', '#f87171');
      }
    } else if(item.type === 'consumable') {
      const front = this.player.party[0];
      if(!front) return;
      if(itemId === 'atk_boost') { front.atk += 2; }
      if(itemId === 'def_boost') { front.def += 2; }
      if(itemId === 'spd_boost') { front.spd += 2; }
      this.player.inventory[itemId]--;
      if(this.player.inventory[itemId] <= 0) delete this.player.inventory[itemId];
      this.showFloatText(this.player.x * this.tileW, this.player.y * this.tileH, 'Stat boosted!', '#4ade80');
      this.updateHUD();
      this.openInventory();
    } else if(item.type === 'capture') {
      // Use in battle
      this.tryCapture();
    } else {
      this.player.inventory[itemId]--;
      if(this.player.inventory[itemId] <= 0) delete this.player.inventory[itemId];
      this.openInventory();
    }
  },

  openPokedex() {
    const overlay = document.getElementById('overlay');
    overlay.classList.add('active');
    overlay.innerHTML = `
      <div class="modal" style="position:relative;min-width:400px" id="pokedex-modal">
        <button class="modal-close" onclick="Game.closeOverlay()">✕</button>
        <h2>📖 Creature Codex</h2>
        <p style="margin-bottom:1rem;color:var(--text-dim)">Discovered: ${this.captured.size}/27</p>
        <div class="pokedex-grid">
          ${CREATURES.map(c => {
            const discovered = this.captured.has(c.id);
            return `<div class="pokedex-entry ${discovered ? 'discovered' : ''}">
              <div class="picon">${discovered ? c.sprite : '❓'}</div>
              <div class="pname">${discovered ? c.name : '???'}</div>
              <div class="ptype" style="color:${discovered ? TYPES[c.affinity]?.color : '#555'}">${discovered ? c.affinity : '???'}</div>
            </div>`;
          }).join('')}
        </div>
      </div>
    `;
  },

  closeOverlay() {
    document.getElementById('overlay').classList.remove('active');
  },

  // ==================== AUDIO ====================
  toggleMute() {
    this.muted = !this.muted;
    document.getElementById('mute-btn').textContent = this.muted ? '🔇' : '🔊';
    if(this.muted) {
      if(this.audioCtx) this.audioCtx.suspend();
    } else {
      if(this.audioCtx) this.audioCtx.resume();
    }
  },

  playAmbient() {
    if(this.muted) return;
    try {
      if(!this.audioCtx) {
        this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      }
      if(this.audioCtx.state === 'suspended') this.audioCtx.resume();

      // Create ambient pad
      const osc1 = this.audioCtx.createOscillator();
      const osc2 = this.audioCtx.createOscillator();
      const gain = this.audioCtx.createGain();
      const filter = this.audioCtx.createBiquadFilter();

      osc1.type = 'sine';
      osc2.type = 'sine';
      osc1.frequency.value = 110;
      osc2.frequency.value = 165;
      gain.gain.value = 0.03;
      filter.type = 'lowpass';
      filter.frequency.value = 400;

      osc1.connect(filter);
      osc2.connect(filter);
      filter.connect(gain);
      gain.connect(this.audioCtx.destination);

      osc1.start();
      osc2.start();

      // LFO for movement
      const lfo = this.audioCtx.createOscillator();
      const lfoGain = this.audioCtx.createGain();
      lfo.frequency.value = 0.1;
      lfoGain.gain.value = 10;
      lfo.connect(lfoGain);
      lfoGain.connect(osc1.frequency);
      lfo.start();

      this.currentOsc = {osc1, osc2, gain, filter, lfo, lfoGain};
    } catch(e) {}
  },

  playSound(type) {
    if(this.muted || !this.audioCtx) return;
    try {
      const osc = this.audioCtx.createOscillator();
      const gain = this.audioCtx.createGain();
      osc.connect(gain);
      gain.connect(this.audioCtx.destination);

      if(type === 'hit') {
        osc.type = 'square';
        osc.frequency.value = 200;
        gain.gain.setValueAtTime(0.1, this.audioCtx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.audioCtx.currentTime + 0.2);
        osc.start();
        osc.stop(this.audioCtx.currentTime + 0.2);
      } else if(type === 'capture') {
        osc.type = 'sine';
        osc.frequency.setValueAtTime(400, this.audioCtx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(800, this.audioCtx.currentTime + 0.3);
        gain.gain.setValueAtTime(0.08, this.audioCtx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.audioCtx.currentTime + 0.3);
        osc.start();
        osc.stop(this.audioCtx.currentTime + 0.3);
      } else if(type === 'victory') {
        osc.type = 'sine';
        osc.frequency.setValueAtTime(300, this.audioCtx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(600, this.audioCtx.currentTime + 0.5);
        gain.gain.setValueAtTime(0.06, this.audioCtx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.audioCtx.currentTime + 0.5);
        osc.start();
        osc.stop(this.audioCtx.currentTime + 0.5);
      }
    } catch(e) {}
  },

  // ==================== MINIMAP ====================
  renderMinimap() {
    const ctx = this.minimapCtx;
    const cw = this.minimapCanvas.width;
    const ch = this.minimapCanvas.height;
    ctx.clearRect(0, 0, cw, ch);

    const map = this.mapTiles;
    if(!map) return;

    const scaleX = cw / this.mapW;
    const scaleY = ch / this.mapH;

    // Draw tiles
    for(let y = 0; y < this.mapH; y++) {
      for(let x = 0; x < this.mapW; x++) {
        const tile = map[y][x];
        if(tile === 1) ctx.fillStyle = '#0a0a1a';
        else if(tile === 5) ctx.fillStyle = '#fbbf24';
        else if(tile === 6) ctx.fillStyle = '#ef4444';
        else if(tile === 2) ctx.fillStyle = '#2a4a2a';
        else if(tile === 4) ctx.fillStyle = '#1a3a5a';
        else ctx.fillStyle = '#1a3a1a';
        ctx.fillRect(x * scaleX, y * scaleY, scaleX + 0.5, scaleY + 0.5);
      }
    }

    // Player dot
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(this.player.x * scaleX, this.player.y * scaleY, 3, 0, Math.PI * 2);
    ctx.fill();

    // Zone name
    ctx.fillStyle = '#8888aa';
    ctx.font = '8px system-ui';
    ctx.textAlign = 'center';
    ctx.fillText(ZONES[this.player.zone].name, cw/2, ch - 3);
  },

  // ==================== MAIN LOOP ====================
  loop() {
    const now = performance.now();
    const dt = Math.min((now - (this.lastTime || now)) / 1000, 0.05);
    this.lastTime = now;
    this.animFrame++;

    if(this.state === 'overworld') {
      // Movement
      if(!this.tutorialActive && !this.storyActive) {
        this.moveTimer += dt;
        if(this.moveTimer >= this.moveInterval) {
          this.moveTimer = 0;
          if(this.keyState['ArrowUp'] || this.keyState['w'] || this.keyState['W'] || this.touchDir === 'up') this.tryMove(0, -1);
          if(this.keyState['ArrowDown'] || this.keyState['s'] || this.keyState['S'] || this.touchDir === 'down') this.tryMove(0, 1);
          if(this.keyState['ArrowLeft'] || this.keyState['a'] || this.keyState['A'] || this.touchDir === 'left') this.tryMove(-1, 0);
          if(this.keyState['ArrowRight'] || this.keyState['d'] || this.keyState['D'] || this.touchDir === 'right') this.tryMove(1, 0);
        }
      }

      // Zone name timer
      if(this.zoneNameTimer !== undefined && this.zoneNameTimer > 0) {
        this.zoneNameTimer -= dt;
      }

      this.renderOverworld(dt);
      this.renderMinimap();
    } else if(this.state === 'battle') {
      this.renderBattleScene();
      // Update particles
      this.battleParticles = this.battleParticles.filter(p => {
        p.alpha -= dt * 0.5;
        return p.alpha > 0;
      });
    }

    // Title particles
    if(!document.getElementById('title-screen').classList.contains('hidden')) {
      this.renderTitle();
    }

    requestAnimationFrame(() => this.loop());
  },

  handleEscape() {
    if(this.state === 'battle') return;
    if(document.getElementById('overlay').classList.contains('active')) {
      this.closeOverlay();
    }
  },

  zoneNameTimer: 0
};

// ==================== INIT ====================
window.addEventListener('DOMContentLoaded', () => Game.init());
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-35b-a3b","provider":"lmstudio","persona":"middle","sessionId":"d0c378f3-ad1d-4e12-aa4b-63de17e6d635","tokensIn":725643,"tokensOut":40002,"tokensTotal":765645,"turns":17,"toolCalls":16,"failedToolCalls":0,"timestamp":"2026-07-13T00:30:35.125Z"} -->