← Creature Collection results

Creature Collection

ornith 1.0 35b · middle

Initial view of Creature Collection

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

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 77,577 bytes · SHA-256 bcbced41bdbd
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>Aetherium: Resonant Bindings</title>
<style>
:root{--bg:#0a0e1a;--panel:rgba(15,20,40,0.85);--glass:rgba(30,40,70,0.6);--accent:#7b68ee;--accent2:#ff6b9d;--accent3:#00e5a0;--text:#e8e6f0;--dim:#6b7194;--hp-good:#00e676;--hp-mid:#ffc107;--hp-low:#ff1744;--border:rgba(123,104,238,0.3)}
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden;background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,sans-serif;user-select:none;-webkit-user-select:none}
#game{position:relative;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center}
canvas{display:block;background:#0a0e1a}
.overlay{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10}
.overlay.active{pointer-events:auto}
.screen{position:absolute;top:0;left:0;width:100%;height:100%;display:none;flex-direction:column;align-items:center;justify-content:center;background:var(--bg)}
.screen.visible{display:flex}
.btn{background:linear-gradient(135deg,var(--glass),rgba(20,25,50,0.9));border:1px solid var(--border);color:var(--text);padding:12px 32px;border-radius:12px;font-size:16px;cursor:pointer;transition:all .2s;margin:6px;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);min-width:180px;text-align:center}
.btn:hover{border-color:var(--accent);box-shadow:0 0 20px rgba(123,104,238,0.3);transform:translateY(-1px)}
.btn:active{transform:scale(0.97)}
.btn-sm{padding:8px 16px;font-size:13px;min-width:auto}
.glass-panel{background:linear-gradient(145deg,rgba(25,30,60,0.9),rgba(15,18,35,0.95));border:1px solid var(--border);border-radius:16px;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
#title-screen{background:radial-gradient(ellipse at 50% 40%,rgba(30,20,80,0.8),var(--bg))}
#title-text{font-size:clamp(28px,5vw,48px);text-align:center;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:8px;font-weight:700;letter-spacing:-1px}
#title-sub{color:var(--dim);font-size:14px;text-align:center;margin-bottom:36px}
.hud{position:absolute;top:0;left:0;width:100%;padding:8px 12px;display:flex;justify-content:space-between;align-items:center;font-size:12px;color:var(--dim);pointer-events:none;z-index:5}
.hud button{background:none;border:1px solid var(--border);color:var(--text);padding:4px 10px;border-radius:8px;cursor:pointer;font-size:11px;pointer-events:auto}
#battle-hud{position:absolute;top:0;left:0;width:100%;padding:6px;display:flex;justify-content:space-between;z-index:5;font-size:11px;color:var(--dim)}
#dialogue-box{position:absolute;bottom:0;left:0;width:100%;padding:12px 16px;background:linear-gradient(transparent,rgba(0,0,0,0.85));z-index:8;display:none}
#dialogue-text{font-size:14px;line-height:1.5;color:var(--text);max-width:700px;margin:auto;text-align:center}
.particle-canvas{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:3}
#touch-controls{position:absolute;bottom:12px;left:12px;z-index:6;display:none}
.touch-btn{width:48px;height:48px;border-radius:50%;background:rgba(123,104,238,0.2);border:1px solid rgba(123,104,238,0.4);color:var(--text);font-size:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;position:absolute}
.touch-btn:active{background:rgba(123,104,238,0.5)}
#touch-dpad{position:relative;width:160px;height:160px}
.dpad-up{top:0;left:56px}.dpad-down{bottom:0;left:56px}.dpad-left{left:0;top:56px}.dpad-right{right:0;top:56px}
#pause-menu,#inventory-screen,#team-screen,#ency-screen,#map-screen,#shop-screen,#capture-screen{position:absolute;top:0;left:0;width:100%;height:100%;display:none;flex-direction:column;padding:20px;z-index:15}
#pause-menu.visible,#inventory-screen.visible,#team-screen.visible,#ency-screen.visible,#map-screen.visible,#shop-screen.visible,#capture-screen.visible{display:flex}
.menu-title{font-size:20px;font-weight:700;margin-bottom:16px;color:var(--accent)}
.scroll-area{flex:1;overflow-y:auto;padding-right:4px}
.scroll-area::-webkit-scrollbar{width:4px}.scroll-area::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
.creature-card{display:flex;align-items:center;gap:10px;padding:10px 12px;margin-bottom:6px;border-radius:10px;background:rgba(30,35,70,0.4);border:1px solid transparent;cursor:pointer;transition:all .2s}
.creature-card:hover{border-color:var(--accent)}
.creature-icon{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
.creature-info{flex:1;min-width:0}.creature-name{font-weight:600;font-size:14px}.creature-level{color:var(--dim);font-size:11px}
.hp-bar{width:100%;height:5px;background:rgba(255,255,255,0.1);border-radius:3px;overflow:hidden;margin-top:3px}
.hp-fill{height:100%;border-radius:3px;transition:width .4s ease}
.ab-item{display:flex;justify-content:space-between;padding:6px 8px;border-radius:6px;background:rgba(255,255,255,0.03);margin-bottom:3px;font-size:12px;cursor:pointer;border:1px solid transparent}
.ab-item:hover{border-color:var(--accent)}
.item-row{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;border-radius:8px;background:rgba(255,255,255,0.03);margin-bottom:4px;font-size:13px}
#floating-dmg{position:absolute;z-index:20;pointer-events:none;font-weight:700;font-size:18px;text-shadow:0 0 10px rgba(0,0,0,0.8)}
.crit-text{color:#ffeb3b !important;font-size:24px}
.shake{animation:shake .3s ease}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-4px)}75%{transform:translateX(4px)}}
.vignette{position:absolute;top:0;left:0;width:100%;height:100%;background:#0a0e1a;display:none;z-index:30;flex-direction:column;align-items:center;justify-content:center}
.vignette.visible{display:flex}
.vignette-text{font-size:clamp(18px,3vw,24px);text-align:center;padding:20px;color:var(--text);max-width:500px;line-height:1.6}
#tutorial-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);display:none;z-index:25;flex-direction:column;align-items:center;justify-content:center}
#tutorial-overlay.visible{display:flex}
.tutorial-step{text-align:center;color:#fff;font-size:16px;line-height:1.6;padding:20px;max-width:400px}
</style>
</head>
<body>
<div id="game">
<canvas id="main-canvas"></canvas>
<canvas class="particle-canvas" id="fx-canvas"></canvas>
<div class="hud" id="zone-hud" style="display:none"><span id="zone-name"></span><div><button onclick="toggleMute()" id="mute-btn">🔊</button><button onclick="togglePause()">☰</button></div></div>
<div class="overlay" id="battle-overlay">
<canvas id="battle-canvas" style="width:100%;height:100%"></canvas>
<div id="battle-hud"><span id="bhp-left"></span><span id="bhp-right"></span></div>
</div>
<div id="dialogue-box"><div id="dialogue-text"></div></div>
<div class="overlay" id="title-screen" style="display:flex">
<div style="text-align:center">
<h1 id="title-text">Aetherium<br><small style="font-size:0.5em;background:none;-webkit-text-fill-color:#6b7194;letter-spacing:3px;text-transform:uppercase">Resonant Bindings</small></h1>
<p id="title-sub">The Aether stirs. Resonants await their Bond.</p>
<button class="btn" onclick="startNew()">New Binding</button>
<button class="btn" id="continue-btn" style="display:none" onclick="continueGame()">Continue</button>
</div></div>
<div class="vignette" id="story-vignette"><div class="vignette-text" id="vignette-text"></div><p style="color:var(--dim);margin-top:16px;font-size:12px">click to continue</p></div>
<div id="pause-menu" class="glass-panel" style="width:100%;height:100%"><h2 class="menu-title">⏸ Paused</h2><button class="btn btn-sm" onclick="closePause()">Resume</button><button class="btn btn-sm" onclick="saveGame();showMsg('Saved!')">Save & Quit</button></div>
<div id="team-screen" class="glass-panel" style="width:100%;height:100%"><h2 class="menu-title">🐾 Team</h2><div class="scroll-area" id="team-list"></div><div style="padding-top:8px"><button class="btn btn-sm" onclick="closeTeam()">Close</button></div></div>
<div id="inventory-screen" class="glass-panel" style="width:100%;height:100%"><h2 class="menu-title">🎒 Inventory</h2><div class="scroll-area" id="inv-list"></div><div style="padding-top:8px"><button class="btn btn-sm" onclick="closeInv()">Close</button></div></div>
<div id="ency-screen" class="glass-panel" style="width:100%;height:100%"><h2 class="menu-title">📖 Codex <span id="ency-count"></span></h2><div class="scroll-area" id="ency-list"></div><div style="padding-top:8px"><button class="btn btn-sm" onclick="closeEncy()">Close</button></div></div>
<div id="map-screen" class="glass-panel glass-panel" style="width:90%;max-width:500px;height:auto;max-height:70vh;padding:20px"><h2 class="menu-title">🗺 Aetherium Map</h2><p id="map-info" style="font-size:13px;color:var(--dim);margin-bottom:16px"></p><div id="zone-nav"></div><button class="btn btn-sm" onclick="closeMap()">Close</button></div>
<div id="shop-screen" class="glass-panel glass-panel" style="width:90%;max-width:500px;height:auto;max-height:70vh;padding:20px"><h2 class="menu-title">🏪 Shrine Market</h2><p id="gold-display" style="font-size:13px;color:var(--accent3);margin-bottom:16px"></p><div class="scroll-area" id="shop-list"></div><button class="btn btn-sm" onclick="closeShop()">Leave Shop</button></div>
<div id="capture-screen" class="glass-panel glass-panel" style="width:90%;max-width:450px;height:auto;padding:20px"><h2 class="menu-title">✨ Attempting Resonance</h2><canvas id="rhythm-canvas" width="360" height="120"></canvas><p id="capture-status" style="text-align:center;margin-top:8px;font-size:13px"></p></div>
<div class="overlay" id="tutorial-overlay"><div class="glass-panel" style="padding:24px;max-width:450px;width:90%"><div class="tutorial-step" id="tutorial-text">Welcome, Binder. Your journey through the Aetherium begins.</div><button class="btn btn-sm" onclick="nextTutorial()">Continue</button></div></div>
<div id="touch-controls"><div id="touch-dpad"><div class="touch-btn dpad-up" data-dir="up">▲</div><div class="touch-btn dpad-down" data-dir="down">▼</div><div class="touch-btn dpad-left" data-dir="left">◀</div><div class="touch-btn dpad-right" data-dir="right">▶</div></div><button class="btn btn-sm" style="position:absolute;bottom:10px;right:20px;width:56px;height:56px;border-radius:50%;font-size:14px" onclick="toggleInteract()">⚔</button></div>
<div id="floating-dmg"></div>
</div>
<script>
"use strict";

// ═══════════════════════════════════════
// AETHERIUM: RESONANT BINDINGS
// ═══════════════════════════════════════

const TILE=32,MAP_W=40,MAP_H=30;
const CANVAS=document.getElementById("main-canvas");
const ctx=CANVAS.getContext("2d");
const FX=document.getElementById("fx-canvas");
const fxCtx=FX.getContext("2d");
let W,H;

function resize(){
  const dpr=Math.min(window.devicePixelRatio||1,2);
  W=window.innerWidth;H=window.innerHeight;
  CANVAS.width=W*dpr;CANVAS.height=H*dpr;CANVAS.style.width=W+"px";CANVAS.style.height=H+"px";ctx.setTransform(dpr,0,0,dpr,0,0);
  FX.width=W*dpr;FX.height=H*dpr;FX.style.width=W+"px";FX.style.height=H+"px";fxCtx.setTransform(dpr,0,0,dpr,0,0);
}
resize();window.addEventListener("resize",resize);

// ═══════ TYPES & CHART ═══════
const TYPES={IGNIS:"Ignis",UNDA:"Unda",VIRIDIA:"Viridia",UMBRRA:"Umbra",TEMPESTA:"Tempesta",FERRUM:"Ferrum",PRISM:"Prism"};
const TYPE_COLORS={"Ignis":"#ff6b35","Unda":"#2196f3","Viridia":"#4caf50","Umbra":"#7c4dff","Tempesta":"#00e5ff","Ferrum":"#ffd54f","Prism":"#e040fb"};
// A>B>C>A, D> A,C; E>D,B; F beats all slightly; G resists all weak to F
const TYPE_ADV={Ignis:"Viridia",Unda:"Ignis",Viridia:"Unda"}; // 3-cycle: Ignis>Viridia>Unda>Ignis - wait the spec says A>B>C>A
// Let me re-read: Type A > B > C > A. So if A=Ignis, B=Unda, C=Viridia: Ignis>Unda, Unda>Viridia, Viridia>Ignis
const CHART={ // attacker -> defender effectiveness multiplier
  "Ignis-Unda":1.5,"Unda-Viridia":1.5,"Viridia-Ignis":1.5, // cycle A>B>C>A
  "Umbra-Ignis":2,"Umbra-Viridia":2, // D beats A & C
  "Tempesta-Umbra":2,"Tempesta-Unda":2, // E beats D & B  
  "Ferrum-Ignis":1.3,"Ferrum-Unda":1.3,"Ferrum-Viridia":1.3,"Ferrum-Umbra":1.3,"Ferrum-Tempesta":1.3,"Ferrum-Ferrum":1,"Ferrum-Prism":1,
  "Ignis-Ferrum":0.5,"Unda-Ferrum":0.5,"Viridia-Ferrum":0.5,"Umbra-Ferrum":0.5,"Tempesta-Ferrum":0.5,"Prism-Ferrum":0.3,
}; // G=Prism resists all, weak to F=Ferrum

function effAtk(atkType,defType){return CHART[atkType+"-"+defType]||1}
// Ferrum vs itself
CHART["Ferrum-Ferrum"]=1;
// ═══════ ABILITIES (40+) ═══════
const ABILITIES=[
{id:"ember_strike",name:"Ember Strike",type:"Ignis",pp:15,cost:0,power:35,acc:95,effect:"burn"},
{id:"flame_lash",name:"Flame Lash",type:"Ignis",pp:12,cost:1,power:45,acc:90,effect:"burn_multi"},
{id:"inferno_blast",name:"Inferno Blast",type:"Ignis",pp:8,cost:2,power:70,acc:80,effect:"burn"},
{id:"scorch_wave",name:"Scorch Wave",type:"Ignis",pp:10,cost:1,power:40,acc:95,effect:"priority"},
{id:"heat_shield",name:"Heat Shield",type:"Ignis",pp:10,cost:0,power:0,acc:100,effect:"barrier"},
{id:"tide_lance",name:"Tide Lance",type:"Unda",pp:15,cost:0,power:35,acc:95,effect:"frost"},
{id:"surge_strike",name:"Surge Strike",type:"Unda",pp:12,cost:1,power:45,acc:90,effect:"shock"},
{id:"tsunami_crash",name:"Tsunami Crash",type:"Unda",pp:8,cost:2,power:70,acc:80,effect:"frost"},
{id:"mist_veil",name:"Mist Veil",type:"Unda",pp:12,cost:1,power:0,acc:100,effect:"regen_self"},
{id:"current_pull",name:"Current Pull",type:"Unda",pp:8,cost:0,power:30,acc:95,effect:"priority"},
{id:"vine_whip",name:"Vine Whip",type:"Viridia",pp:15,cost:0,power:35,acc:95,effect:"multi_hit"},
{id:"thorn_barrage",name:"Thorn Barrage",type:"Viridia",pp:10,cost:2,power:60,acc:85,effect:"multi_hit"},
{id:"photosynthesis",name:"Photosynthesis",type:"Viridia",pp:10,cost:0,power:0,acc:100,effect:"regen_self"},
{id:"wild_growth",name:"Wild Growth",type:"Viridia",pp:8,cost:1,power:50,acc:90,effect:"atk_boost"},
{id:"seed_bomb",name:"Seed Bomb",type:"Viridia",pp:12,cost:1,power:40,acc:90,effect:"frost"},
{id:"shadow_snap",name:"Shadow Snap",type:"Umbra",pp:15,cost:0,power:35,acc:95,effect:"shock"},
{id:"void_drain",name:"Void Drain",type:"Umbra",pp:12,cost:1,power:45,acc:90,effect:"drain"},
{id:"dark_maelstrom",name:"Dark Maelstrom",type:"Umbra",pp:7,cost:3,power:80,acc:75,effect:"shock"},
{id:"eclipse_step",name:"Eclipse Step",type:"Umbra",pp:10,cost:1,power:0,acc:100,effect:"speed_boost"},
{id:"night_shroud",name:"Night Shroud",type:"Umbra",pp:8,cost:1,power:50,acc:85,effect:"barrier"},
{id:"gust_cutter",name:"Gust Cutter",type:"Tempesta",pp:15,cost:0,power:35,acc:95,effect:"speed_boost"},
{id:"tempest_swipe",name:"Tempest Swipe",type:"Tempesta",pp:12,cost:1,power:45,acc:90,effect:"multi_hit"},
{id:"lightning_rod",name:"Lightning Rod",type:"Tempesta",pp:8,cost:2,power:65,acc:85,effect:"shock"},
{id:"frontal_blast",name:"Frontal Blast",type:"Tempesta",pp:10,cost:0,power:30,acc:95,effect:"priority"},
{id:"aero_barrier",name:"Aero Barrier",type:"Tempesta",pp:8,cost:1,power:0,acc:100,effect:"barrier"},
{id:"iron_fist",name:"Iron Fist",type:"Ferrum",pp:20,cost:0,power:35,acc:95,effect:"none"},
{id:"steel_wall",name:"Steel Wall",type:"Ferrum",pp:15,cost:1,power:0,acc:100,effect:"def_boost"},
{id:"meteo_crush",name:"Meteo Crush",type:"Ferrum",pp:8,cost:2,power:65,acc:80,effect:"none"},
{id:"forge_heart",name:"Forge Heart",type:"Ferrum",pp:10,cost:0,power:0,acc:100,effect:"regen_self"},
{id:"titan_grasp",name:"Titan Grasp",type:"Ferrum",pp:6,cost:2,power:80,acc:70,effect:"atk_boost"},
{id:"prism_beam",name:"Prism Beam",type:"Prism",pp:15,cost:0,power:35,acc:95,effect:"none"},
{id:"spectrum_flash",name:"Spectrum Flash",type:"Prism",pp:12,cost:1,power:45,acc:90,effect:"speed_boost"},
{id:"chroma_burst",name:"Chroma Burst",type:"Prism",pp:7,cost:2,power:65,acc:85,effect:"multi_hit"},
{id:"refract_shield",name:"Refract Shield",type:"Prism",pp:10,cost:1,power:0,acc:100,effect:"barrier"},
{id:"prismatic_heal",name:"Prismatic Heal",type:"Prism",pp:12,cost:1,power:0,acc:100,effect:"regen_allies"},
{id:"aurora_rain",name:"Aurora Rain",type:"Ignis",pp:6,cost:3,power:90,acc:70,effect:"burn"},
{id:"tidal_roar",name:"Tidal Roar",type:"Unda",pp:5,cost:3,power:85,acc:72,effect:"frost"},
{id:"bloom_surge",name:"Bloom Surge",type:"Viridia",pp:6,cost:2,power:55,acc:90,effect:"regen_self"},
{id:"abyssal_gaze",name:"Abyssal Gaze",type:"Umbra",pp:4,cost:3,power:75,acc:80,effect:"shock"},
{id:"cyclone_spiral",name:"Cyclone Spiral",type:"Tempesta",pp:5,cost:2,power:60,acc:90,effect:"speed_boost"},
{id:"adamant_wall",name:"Adamant Wall",type:"Ferrum",pp:8,cost:1,power:0,acc:100,effect:"def_boost"},
];

function getAbilities(id){return ABILITIES.filter(a=>a.id===id)}
function creatureMoves(creature){return(creature.moves||[]).slice()}
function learnSet(name,lvl){
  // Returns array of ability IDs learned at given level
  const LS={
    "Pyralisk":["ember_strike","ember_strike","flame_lash","inferno_blast"],
    "Emberkit":["ember_strike","scorch_wave","ember_strike","flame_lash"],
    "Wispling":["heat_shield","ember_strike","flame_lash","inferno_blast"],
  };return LS[name]||["ember_strike"];}

// ═══════ CREATURES (18 base + 9 transforms) ═══════
const CREATURES=[
{id:"pyralisk",name:"Pyralisk",affinity:"Ignis",tier:0,icon:"🔥",lore:"A fledgling flame-wyrm born from cooled lava veins.",baseHp:58,baseAtk:42,baseDef:35,baseSpd:38,moves:["ember_strike","scorch_wave","flame_lash","heat_shield"],
evo:{lvl:18,name:"Cinderdrake",transform:true},color:"#ff6b35"},
{id:"pyrux",name:"Pyrux",affinity:"Ignis",tier:0,icon:"🦊",lore:"A curious fire-fox whose fur crackles with nascent embers.",baseHp:52,baseAtk:45,baseDef:30,baseSpd:48,moves:["ember_strike","scorch_wave","flame_lash","heat_shield"],
evo:null,color:"#ff9100"},
{id:"tidescale",name:"Tidescale",affinity:"Unda",tier:0,icon:"🐟",lore:"A small aquatic reptile that channels water through its scales.",baseHp:62,baseAtk:35,baseDef:40,baseSpd:32,moves:["tide_lance","surge_strike","mist_veil","current_pull"],
evo:{lvl:18,name:"Maraudra",transform:true},color:"#2196f3"},
{id:"mudlark",name:"Mudlark",affinity:"Unda",tier:0,icon:"🐸",lore:"A stout amphibian that hides in riverbeds and mudflats.",baseHp:70,baseAtk:32,baseDef:45,baseSpd:28,moves:["tide_lance","mist_veil","surge_strike","current_pull"],
evo:null,color:"#0091ea"},
{id:"leafit",name:"Leafit",affinity:"Viridia",tier:0,icon:"🌱",lore:"A sprouting plant-mimic that draws sustenance from stone cracks.",baseHp:60,baseAtk:38,baseDef:38,baseSpd:35,moves:["vine_whip","photosynthesis","thorn_barrage","seed_bomb"],
evo:{lvl:18,name:"Thornweaver",transform:true},color:"#4caf50"},
{id:"mossback",name:"Mossback",affinity:"Viridia",tier:0,icon:"🪨",lore:"A slow-moving stone-shelled creature draped in living moss.",baseHp:72,baseAtk:30,baseDef:50,baseSpd:25,moves:["vine_whip","photosynthesis","wild_growth","seed_bomb"],
evo:null,color:"#2e7d32"},
{id:"gloomite",name:"Gloomite",affinity:"Umbra",tier:0,icon:"👻",lore:"A wisp of condensed shadow that feeds on forgotten places.",baseHp:48,baseAtk:48,baseDef:25,baseSpd:45,moves:["shadow_snap","void_drain","eclipse_step","dark_maelstrom"],
evo:{lvl:18,name:"Phantasmoth",transform:true},color:"#7c4dff"},
{id:"duskrat",name:"Duskrat",affinity:"Umbra",tier:0,icon:"🐱",lore:"A nocturnal rodent with eyes that see through the darkest corners.",baseHp:50,baseAtk:42,baseDef:28,baseSpd:50,moves:["shadow_snap","eclipse_step","void_drain","night_shroud"],
evo:null,color:"#651fff"},
{id:"zephyrat",name:"Zephyrat",affinity:"Tempesta",tier:0,icon:"🕊",lore:"A tiny avian spirit that rides the high winds between peaks.",baseHp:44,baseAtk:42,baseDef:26,baseSpd:55,moves:["gust_cutter","tempest_swipe","lightning_rod","aero_barrier"],
evo:{lvl:18,name:"Stormcrest",transform:true},color:"#00e5ff"},
{id:"stormcrow",name:"Stormcrow",affinity:"Tempesta",tier:0,icon:"🐦‍⬛",lore:"A dark-winged bird that calls lightning from gathering clouds.",baseHp:52,baseAtk:46,baseDef:30,baseSpd:48,moves:["gust_cutter","frontal_blast","tempest_swipe","lightning_rod"],
evo:null,color:"#18ffff"},

// Base creatures 11-18
{id:"ironclaw",name:"Ironclaw",affinity:"Ferrum",tier:0,icon:"🦀",lore:"A metallic arthropod with claws forged in subterranean forges.",baseHp:65,baseAtk:40,baseDef:52,baseSpd:30,moves:["iron_fist","steel_wall","meteo_crush","forge_heart"],
evo:{lvl:18,name:"Bastionclaw",transform:true},color:"#ffd54f"},
{id:"pebbleshell",name:"Pebbleshell",affinity:"Ferrum",tier:0,icon:"🪲",lore:"A slow-shelled invertebrate with armor of compressed ore.",baseHp:78,baseAtk:30,baseDef:58,baseSpd:22,moves:["iron_fist","steel_wall","forge_heart","titan_grasp"],
evo:null,color:"#ffc107"},
{id:"glimmer",name:"Glimmer",affinity:"Prism",tier:0,icon:"✨",lore:"A crystalline entity that refracts ambient light into harmless beams.",baseHp:46,baseAtk:44,baseDef:28,baseSpd:42,moves:["prism_beam","spectrum_flash","chroma_burst","refract_shield"],
evo:{lvl:18,name:"Iridessa",transform:true},color:"#e040fb"},
{id:"mirrorbug",name:"Mirrorbug",affinity:"Prism",tier:0,icon:"🪞",lore:"A beetle with a carapace of living glass that baffles predators.",baseHp:52,baseAtk:38,baseDef:36,baseSpd:44,moves:["prism_beam","spectrum_flash","refract_shield","chroma_burst"],
evo:null,color:"#ce93d8"},

// Transformed forms 1-9
{id:"cinderdrake",name:"Cinderdrake",affinity:"Ignis",tier:1,icon:"🐉",lore:"A full-grown flame drake with wings of living fire.",baseHp:72,baseAtk:52,baseDef:45,baseSpd:42,moves:["inferno_blast","scorch_wave","flame_lash","heat_shield"],
evo:null,color:"#ff3d00"},
{id:"maraudra",name:"Maraudra",affinity:"Unda",tier:1,icon:"🐲",lore:"A massive sea serpentine that commands ocean currents.",baseHp:78,baseAtk:48,baseDef:50,baseSpd:36,moves:["tsunami_crash","mist_veil","surge_strike","tide_lance"],
evo:null,color:"#0277bd"},
{id:"thornweaver",name:"Thornweaver",affinity:"Viridia",tier:1,icon:"🌳",lore:"An ancient treant whose thorns weave between dimensions.",baseHp:75,baseAtk:48,baseDef:52,baseSpd:30,moves:["thorn_barrage","wild_growth","photosynthesis","seed_bomb"],
evo:null,color:"#1b5e20"},
{id:"phantasmoth",name:"Phantasmoth",affinity:"Umbra",tier:1,icon:"🦋",lore:"A shadow-winged butterfly that flits between the material and void planes.",baseHp:60,baseAtk:58,baseDef:32,baseSpd:52,moves:["dark_maelstrom","eclipse_step","void_drain","night_shroud"],
evo:null,color:"#4a148c"},
{id:"stormcrest",name:"Stormcrest",affinity:"Tempesta",tier:1,icon:"⚡",lore:"A towering thunderbird wreathed in perpetual lightning.",baseHp:62,baseAtk:56,baseDef:36,baseSpd:58,moves:["lightning_rod","tempest_swipe","aero_barrier","frontal_blast"],
evo:null,color:"#00b8d4"},
{id:"bastionclaw",name:"Bastionclaw",affinity:"Ferrum",tier:1,icon:"🏰",lore:"A fortress-like golem-beast with unbreakable ore-plates.",baseHp:95,baseAtk:50,baseDef:68,baseSpd:28,moves:["meteo_crush","titan_grasp","steel_wall","forge_heart"],
evo:null,color:"#ffab00"},
{id:"iridessa",name:"Iridessa",affinity:"Prism",tier:1,icon:"🔮",lore:"A radiant prismatrix that bends light into devastating beams.",baseHp:62,baseAtk:54,baseDef:38,baseSpd:48,moves:["chroma_burst","prismatic_heal","spectrum_flash","refract_shield"],
evo:null,color:"#aa00ff"},

// Additional base creatures 15-18
{id:"sparkmote",name:"Sparkmote",affinity:"Ignis",tier:0,icon:"🪲",lore:"A tiny beetle that stores static fire in its abdomen.",baseHp:42,baseAtk:36,baseDef:22,baseSpd:52,moves:["ember_strike","scorch_wave","heat_shield","flame_lash"],
evo:{lvl:18,name:"Infernomantis",transform:true},color:"#ffcc02"},
{id:"frosthare",name:"Frosthare",affinity:"Unda",tier:0,icon:"🐇",lore:"A swift hare that leaves trails of frost in its wake.",baseHp:54,baseAtk:38,baseDef:32,baseSpd:56,moves:["tide_lance","mist_veil","surge_strike","current_pull"],
evo:null,color:"#00b8d4"},
{id:"gloomvine",name:"Gloomvine",affinity:"Viridia",tier:0,icon:"🍄",lore:"A bioluminescent fungus that colonizes the dark places.",baseHp:68,baseAtk:34,baseDef:42,baseSpd:28,moves:["vine_whip","photosynthesis","wild_growth","seed_bomb"],
evo:null,color:"#76ff03"},
{id:"tinwing",name:"Tinwing",affinity:"Ferrum",tier:0,icon:"🦎",lore:"A scaled lizard whose blood is liquid metal.",baseHp:58,baseAtk:42,baseDef:40,baseSpd:40,moves:["iron_fist","steel_wall","forge_heart","meteo_crush"],
evo:null,color:"#ff6f00"},

// Additional transformed 8-9
{id:"embermantis",name:"Embermantis",affinity:"Ignis",tier:1,icon:"⚔️",lore:"A praying mantis wreathed in eternal flame.",baseHp:58,baseAtk:52,baseDef:34,baseSpd:60,moves:["flame_lash","scorch_wave","inferno_blast","heat_shield"],
evo:null,color:"#ff1744"},
{id:"crystalback",name:"Crystallback",affinity:"Prism",tier:1,icon:"💎",lore:"A tortoise with a shell of living crystal that refracts all harm.",baseHp:85,baseAtk:36,baseDef:62,baseSpd:20,moves:["refract_shield","chroma_burst","prismatic_heal","spectrum_flash"],
evo:null,color:"#ea80fc"},
];

function getCreature(id){return CREATURES.find(c=>c.id===id)}
function getBaseName(id){const c=getCreature(id);if(!c)return id;const evo=c.evo;if(evo&&evo.transform){const base=CREATURES.find(b=>b.id===id.replace(/transformed$/,""));if(base)return base.name;}return c.name}
// ═══════ ITEMS ═══════
const ITEMS={
  "resonator":{name:"Resonator",tier:1,type:"capture",power:1,effect:"capture"},
  "resonator_plus":{name:"Resonator+",tier:2,type:"capture",power:2,effect:"capture"},
  "resonator_max":{name:"Resonator Max",tier:3,type:"capture",power:3,effect:"capture"},
  "ether_draught":{name:"Ether Draught",tier:0,type:"heal",power:50,effect:"heal_hp"},
  "crystal_salve":{name:"Crystal Salve",tier:0,type:"heal",power:200,effect:"full_heal"},
  "spark_seed":{name:"Spark Seed",tier:0,type:"catalyst",power:0,effect:"evolve_ignis"},
  "tide_gem":{name:"Tide Gem",tier:0,type:"catalyst",power:0,effect:"evolve_unda"},
  "vine_crystal":{name:"Vine Crystal",tier:0,type:"catalyst",power:0,effect:"evolve_viridia"},
  "void_shard":{name:"Void Shard",tier:0,type:"catalyst",power:0,effect:"evolve_umbra"},
  "gale_feather":{name:"Gale Feather",tier:0,type:"catalyst",power:0,effect:"evolve_tempesta"},
  "iron_core":{name:"Iron Core",tier:0,type:"catalyst",power:0,effect:"evolve_ferrum"},
  "prism_dust":{name:"Prism Dust",tier:0,type:"catalyst",power:0,effect:"evolve_prism"},
  "stamina_tonic":{name:"Stamina Tonic",tier:0,type:"field",power:0,effect:"atk_up"},
  "swift_essence":{name:"Swift Essence",tier:0,type:"field",power:0,effect:"spd_up"},
};

// ═══════ ZONES ═══════
const ZONES=[
{id:0,name:"Ember Hollows",bg:["#1a0800","#3d1500"],particle:"embers",encRate:0.12,bossId:"ember_anchor",bossDefeated:false,story:[
  "The Ember Hollows stretch endlessly before you — rivers of cooled lava carve paths through obsidian rock.",
  "A distant glow pulses from deeper within. Something stirs in the heat.",
],anchoring:true},
{id:1,name:"Tide Warrens",bg:["#001a2e","#003d6b"],particle:"bubbles",encRate:0.14,bossId:"tide_anchor",bossDefeated:false,story:[
  "The Tide Warrens flood with bioluminescent currents. Ancient ruins peer from the depths.",
  "A great leviathan's shadow passes overhead...",
],anchoring:true},
{id:2,name:"Verdant Labyrinth",bg:["#0a1f0a","#1b3d1b"],particle:"leaves",encRate:0.15,bossId:"verdant_anchor",bossDefeated:false,story:[
  "The Verdant Labyrinth is a living maze — walls of thorn shift and close behind you.",
  "At its heart lies the First Thornweaver, guardian of all growth.",
],anchoring:true},
{id:3,name:"Umbra Depths",bg:["#0d001a","#2a0050"],particle:"shadows",encRate:0.16,bossId:"umbra_anchor",bossDefeated:false,story:[
  "The Umbra Depths swallow light itself. Only the faintest resonance guides your way.",
  "A presence watches from everywhere at once...",
],anchoring:true},
{id:4,name:"Tempest Peaks",bg:["#0a1a2e","#1a3d5c"],particle:"lightning",encRate:0.17,bossId:"tempest_anchor",bossDefeated:false,story:[
  "The Tempest Peaks tear through the sky — wind and lightning carve the stone.",
  "At the summit, a storm never breaks...",
],anchoring:true},
{id:5,name:"Ferrum Citadel",bg:["#1a1500","#3d3200"],particle:"sparks",encRate:0.18,bossId:"citadel_anchor",bossDefeated:false,story:[
  "The Ferrum Citadel rises from a sea of molten ore — the forge-heart of Aetherium.",
  "Beyond its gates lies the final secret...",
],anchoring:true},
];

// ═══════ BOSS DATA ═══════
const BOSSES={
"ember_anchor":{name:"Cinder King",affinity:"Ignis",lvl:20,hpMul:3,atkMul:1.8,defMul:1.5,spdMul:1,icon:"👑",color:"#ff6b35",
  moves:["inferno_blast","scorch_wave","flame_lash","heat_shield"],bonus:"Ignis Resonance (+10% Ignis damage)"},
"tide_anchor":{name:"Abyssal Warden",affinity:"Unda",lvl:22,hpMul:3.5,atkMul:1.6,defMul:1.7,spdMul:0.9,icon:"🐙",color:"#2196f3",
  moves:["tsunami_crash","surge_strike","mist_veil","tide_lance"],bonus:"Unda Resonance (+10% Unda damage)"},
"verdant_anchor":{name:"First Thornweaver",affinity:"Viridia",lvl:24,hpMul:4,atkMul:1.7,defMul:1.8,spdMul:0.85,icon:"🌳",color:"#4caf50",
  moves:["thorn_barrage","wild_growth","photosynthesis","seed_bomb"],bonus:"Viridia Resonance (+10% Viridia damage)"},
"umbra_anchor":{name:"The Unseen",affinity:"Umbra",lvl:26,hpMul:3,atkMul:2.2,defMul:1.4,spdMul:1.3,icon:"👁",color:"#7c4dff",
  moves:["dark_maelstrom","void_drain","eclipse_step","night_shroud"],bonus:"Umbra Resonance (+10% Umbra damage)"},
"tempest_anchor":{name:"Storm Sovereign",affinity:"Tempesta",lvl:28,hpMul:3.5,atkMul:2,defMul:1.6,spdMul:1.4,icon:"⚡",color:"#00e5ff",
  moves:["lightning_rod","tempest_swipe","aero_barrier","frontal_blast"],bonus:"Tempesta Resonance (+10% Tempesta damage)"},
"citadel_anchor":{name:"Forge Primarch",affinity:"Ferrum",lvl:32,hpMul:5,atkMul:2.5,defMul:2.2,spdMul:0.8,icon:"🏰",color:"#ffd54f",
  moves:["meteo_crush","titan_grasp","steel_wall","forge_heart"],bonus:"Ferrum Resonance (+10% Ferrum damage)"},
};

// ═══════ WILD SPAWNS ═══════
const WILDS={
0:[{id:"pyralisk",w:4},{id:"pyrux",w:6}],
1:[{id:"tidescale",w:4},{id:"mudlark",w:6}],
2:[{id:"leafit",w:5},{id:"mossback",w:3}],
3:[{id:"gloomite",w:5},{id:"duskrat",w:5}],
4:[{id:"zephyrat",w:4},{id:"stormcrow",w:6}],
5:[{id:"ironclaw",w:5},{id:"pebbleshell",w:5}],
};

// ═══════ TILE MAPS ═══════
// Simple map per zone - 0=walkable,1=wall,2=shrine,3=boss,9=tutorial start
const makeMap=(W,H)=>{let m=[];for(let y=0;y<H;y++){m[y]=[];for(let x=0;x<W;x++)m[y][x]=1;}return m;};

function buildZoneMaps(){
  const maps={};
  for(let z=0;z<6;z++){
    const m=makeMap(MAP_H,MAP_W);
    // carve walkable area
    for(let y=3;y<MAP_H-3;y++)for(let x=3;x<MAP_W-3;x++)m[y][x]=0;
    // random walls inside
    for(let i=0;i<40+ z*5;i++){
      const wx=4+Math.floor(Math.random()*(MAP_W-8));
      const wy=4+Math.floor(Math.random()*(MAP_H-8));
      const len=2+Math.floor(Math.random()*6);
      const dir=Math.random()<0.5;
      for(let j=0;j<len&&dir;j++)if(wy+j<MAP_H-3)m[wy+j][wx]=1;
      for(let j=0;j<len&&!dir;j++)if(wx+j<MAP_W-3)m[wy][wx+j]=1;
    }
    // entrance from bottom center
    m[MAP_H-2][Math.floor(MAP_W/2)]=0;m[MAP_H-3][Math.floor(MAP_W/2)]=0;
    // shrine near top-left area
    m[5][8]=2;
    // boss at far end
    const bx=Math.floor(MAP_W/2),by=4;
    m[by][bx]=3;m[by+1][bx]=0;m[by-1][Math.max(0,bx-1)]=0;m[by-1][Math.min(MAP_W-1,bx+1)]=0;
    maps[z]=m;
  }
  return maps;
}
const ZMAPS=buildZoneMaps();

// ═══════ GAME STATE ═══════
let G={
  phase:"title", // title,overworld,battle,tutorial,vignette,pause
  zone:0,playerX:20,playerY:15,screens:[],
  team:[],inventory:{},gold:200,
  captured:new Set(),defeatedAnchors:new Set(),storyProgress:0,
  tutorialStep:0,tutorialDone:false,postGame:false,
  bonuses:{ignisDmg:0,undaDmg:0,viridiaDmg:0,umbraDmg:0,tempestaDmg:0,ferrumDmg:0},
  totalBattles:0,totalCaptures:0,enemiesDefeated:[],
};

function addGold(n){G.gold+=n}
function spendGold(n){if(G.gold>=n){G.gold-=n;return true;}return false}
function addItem(id,n=1){G.inventory[id]=(G.inventory[id]||0)+n}
function removeItem(id,n=1){if((G.inventory[id]||0)>=n){G.inventory[id]-=n;if(G.inventory[id]<=0)delete G.inventory[id];return true;}return false}
function countItem(id){return G.inventory[id]||0}

// ═══════ CREATURE INSTANCE HELPERS ═══════
function makeCreature(creatureId,level){
  const base=getCreature(creatureId);if(!base)return null;
  return{
    id:creatureId,name:base.name,currentLevel:level,expToNext:level*12+10,experience:0,
    hp:calcStat(base.baseHp,level),maxHp:calcStat(base.baseHp,level),
    atk:calcStat(base.baseAtk,level),def:calcStat(base.baseDef,level),spd:calcStat(base.baseSpd,level),
    affinity:base.affinity,color:base.color,icon:base.icon,
    moves:[...base.moves],status:null,statusTurns:0,bondLevel:1,
  };
}
function calcStat(base,lvl){return Math.floor(base+(lvl-1)*2+Math.floor(lvl/3))}
function getExpToNext(lvl){return lvl*12+10}

// ═══════ SAVE / LOAD ═══════
function saveGame(){
  try{localStorage.setItem("aetherium_save",JSON.stringify(G));}catch(e){console.warn("Save failed");}
}
function loadGame(){
  try{return JSON.parse(localStorage.getItem("aetherium_save"));}catch(e){return null;}
}
function hasSave(){try{return!!localStorage.getItem("aetherium_save");}catch(e){return false;}}

// ═══════ BATTLE STATE ═══════
let BS={players:[],enemies:[],turnOrder:[],currentTurn:0,phase:"select",animating:false,selectedMove:null,focusIdx:0,defendMode:false};

function initBattle(enemyList){
  // enemyList: array of {id,name,affinity,lvl,hpMul,atkMul,defMul,spdMul,color,icon,moves}
  BS.players=G.team.map(c=>({...c,maxHp:c.hp,currentHp:c.hp,status:null,statusTurns:0,wasInBattle:true}));
  BS.enemies=enemyList.map(e=>{
    const base=getCreature(e.id)||{baseHp:50,baseAtk:35,baseDef:30,baseSpd:35,color:"#888",icon:"?",name:e.name};
    const hp=Math.floor(base.baseHp*e.hpMul+(e.lvl-1)*2);
    return{name:e.name,id:e.id||"enemy_"+Math.random().toString(36).substr(2,9),affinity:e.affinity,lvl:e.lvl,hp:hp,maxHp:hp,atk:Math.floor(base.baseAtk*e.atkMul),def:Math.floor(base.baseDef*e.defMul),spd:Math.floor(base.baseSpd*e.spdMul),color:e.color||"#888",icon:e.icon||"?",moves:[...e.moves],isEnemy:true,status:null,statusTurns:0,boss:e.boss||false,bonusText:e.bonusText};
  });
  // add focus idx for targeting
  BS.enemies.forEach((e,i)=>{e.focus=i;});
  BS.turnOrder=[];BS.currentTurn=0;BS.phase="select";BS.animating=false;BS.selectedMove=null;BS.defendMode=false;
  recalcTurnOrder();
}

function tickStatuses(){
  // Burn: DoT + half ATK reduction for next turn
  BS.players.forEach(p=>{
    if(p.status==="burn"){
      p.currentHp=Math.max(0,p.currentHp-Math.floor(p.maxHp*0.1));
      p.statusTurns--;if(p.statusTurns<=0)p.status=null;
    }else if(p.status==="frost"){
      p.spd=Math.floor(p.originalSpd||p.spd*0.5);
      p.statusTurns--;if(p.statusTurns<=0){p.spd=originalSpdCache?.[p.name]||p.spd;p.status=null;}
    }else if(p.status==="shock"){
      if(Math.random()<0.3){showFloatingText("Paralyzed!",W*0.15,60,"#ffeb3b");p.statusTurns--;if(p.statusTurns<=0)p.status=null;return true;} // skip turn
    }
  });
}

const originalSpdCache={};
function recalcTurnOrder(){
  const all=[...BS.players.map((p,i)=>{originalSpdCache[p.name]=p.spd;return({idx:i,type:"player",spd:p.spd,name:p.name});}),
    ...BS.enemies.map((e,i)=>({idx:i,type:"enemy",spd:e.spd,name:e.name}))];
  // SPD + small random factor
  all.forEach(a=>{a.spd=a.spd+Math.random()*15-7.5;});
  all.sort((a,b)=>b.spd-a.spd);
  BS.turnOrder=all.map(a=>({...a}));
  BS.currentTurn=0;BS.phase="select";
}
// ═══════ PARTICLES ═══════
let particles=[];
class Particle{
  constructor(x,y,vx,vy,life,color,size){Object.assign(this,{x,y,vx,vy,life,maxLife:life,color,size})}
  update(){this.x+=this.vx;this.y+=this.vy;this.life--;this.vy+=(Math.random()-0.5)*0.1;}
  draw(c){const a=this.life/this.maxLife;c.globalAlpha=a;c.fillStyle=this.color;c.fillRect(this.x-this.size/2,this.y-this.size/2,this.size*a,this.size*a);c.globalAlpha=1;}
}

function spawnParticles(x,y,color,count=8,spread=3){
  for(let i=0;i<count;i++)particles.push(new Particle(x,y,(Math.random()-0.5)*spread,(Math.random()-0.5)*spread-1,20+Math.random()*20,color,2+Math.random()*4));
}

function ambientParticles(zoneId){
  if(G.phase!=="overworld")return;
  const z=ZONES[zoneId];if(!z)return;
  const cx=W/2,cy=H/2;
  const colors={"embers":["#ff6b35","#ff9100","#ffc107"],"bubbles":["#4dd0e1","#80deea","#b2ebf2"],"leaves":["#4caf50","#81c784","#a5d6a7"],"shadows":["#7c4dff","#b388ff","#ce93d8"],"lightning":["#00e5ff","#80ffff","#ffffff"],"sparks":["#ffd54f","#ffb300","#fff176"]};
  const c=colors[z.particle]||["#fff"];
  if(Math.random()<0.3){
    particles.push(new Particle(cx+(Math.random()-0.5)*W,cy+(Math.random()-0.5)*H,(Math.random()-0.5)*1,Math.random()*-2-0.5,40+Math.random()*60,c[Math.floor(Math.random()*c.length)],1+Math.random()*3));
  }
}

function drawParticles(c){
  particles=particles.filter(p=>p.life>0);
  particles.forEach(p=>{p.update();p.draw(c);});
}

// ═══════ OVERWORLD RENDERING ═══════
let camX=0,camY=0;
// ═══════ MUTE TOGGLE ═══════
function toggleMute(){audioMuted=!audioMuted;if(masterGain)masterGain.gain.value=audioMuted?0:0.15;}

const ZCOL=["#1a0800","#001a2e","#0a1f0a","#0d001a","#0a1a2e","#1a1500"];

function drawOverworld(){
  const z=G.zone;const zone=ZONES[z];
  // background gradient
  const grad=ctx.createLinearGradient(0,0,0,H);
  grad.addColorStop(0,zone.bg[0]);grad.addColorStop(1,zone.bg[1]);
  ctx.fillStyle=grad;ctx.fillRect(0,0,W,H);

  camX=G.playerX*TILE-W/2;camY=G.playerY*TILE-H/2+60;

  const m=ZMAPS[z];
  for(let y=0;y<MAP_H;y++)for(let x=0;x<MAP_W;x++){
    const sx=x*TILE-camX,sy=y*TILE-camY;
    if(sx<-TILE||sy<-TILE||sx>W+TILE||sy>H+TILE)continue;
    const t=m[y][x];
    if(t===1){ctx.fillStyle="rgba(0,0,0,0.3)";ctx.fillRect(sx,sy,TILE,TILE);}
    else{ctx.strokeStyle="rgba(255,255,255,0.04)";ctx.strokeRect(sx,sy,TILE,TILE);
      if(t===2){ctx.fillStyle="#ffd700";ctx.font="20px serif";ctx.fillText("⛩",sx+6,sy+24);}
      else if(t===3){ctx.fillStyle=zone.bossDefeated?"rgba(100,255,100,0.3)":"rgba(255,80,80,0.4)";ctx.beginPath();ctx.arc(sx+TILE/2,sy+TILE/2,TILE*0.4,0,Math.PI*2);ctx.fill();}
    }
  }

  // player
  const px=G.playerX*TILE-camX,py=G.playerY*TILE-camY;
  ctx.fillStyle="#fff";ctx.shadowColor=zone.bossDefeated?"#4caf50":"var(--accent)";ctx.shadowBlur=12;
  ctx.beginPath();ctx.arc(px+TILE/2,py+TILE/2,TILE*0.35,0,Math.PI*2);ctx.fill();
  ctx.shadowBlur=0;
  // direction indicator
  ctx.fillStyle="rgba(123,104,238,0.6)";ctx.font="10px sans-serif";ctx.textAlign="center";
  ctx.fillText("▼",px+TILE/2,py-2);ctx.textAlign="left";

  // interaction prompt near shrines/bosses
  const adj=[m[G.playerY][G.playerX+1],m[G.playerY][Math.max(0,G.playerX-1)],m[G.playerY-1][G.playerX],m[G.playerY+1][G.playerX]];
  let interactable=false;
  for(const t of adj)if(t===2||t===3)interactable=true;

  if(interactable){ctx.fillStyle="rgba(255,255,255,0.8)";ctx.font="11px sans-serif";ctx.textAlign="center";ctx.fillText("Press ⚔ to interact",px+TILE/2,py+TILE*0.9);ctx.textAlign="left";}
}

// ═══════ PLAYER MOVEMENT ═══════
const DIRS={up:{dx:0,dy:-1},down:{dx:0,dy:1},left:{dx:-1,dy:0},right:{dx:1,dy:0}};
let moveCooldown=0;
function tryMove(dir){
  if(G.phase!=="overworld")return;
  const d=DIRS[dir];if(!d)return;
  G.playerX+=d.dx;G.playerY+=d.dy;
  // check bounds + walls
  if(G.playerX<0||G.playerX>=MAP_W||G.playerY<0||G.playerY>=MAP_H){G.playerX-=d.dx;G.playerY-=d.dy;return;}
  const m=ZMAPS[G.zone];
  if(m[G.playerY][G.playerX]===1){G.playerX-=d.dx;G.playerY-=d.dy;return;}

  // encounter check
  if(Math.random()<ZONES[G.zone].encRate&&!m[G.playerY][G.playerX])startWildEncounter();
}
function toggleInteract(){
  if(G.phase==="overworld")doInteract();
  else if(G.phase==="battle"&&BS.phase==="select")showBattleMenu();
}

// ═══════ INTERACTION ═══════
function doInteract(){
  const m=ZMAPS[G.zone];const y=G.playerY,x=G.playerX;
  // check adjacent tiles for shrine(2) or boss(3)
  for(const dy of[-1,0,1])for(const dx of[-1,0,1]){
    if(dy===0&&dx===0)continue;
    const ny=y+dy,nx=x+dx;if(ny<0||ny>=MAP_H||nx<0||nx>=MAP_W)continue;
    if(m[ny][nx]===2){openShrine();return;}
    if(m[ny][nx]===3){startBossBattle(zones[G.zone].bossId);return;}
  }
}

function openShrine(){
  // heal party, show save/shop
  G.team.forEach(c=>{c.hp=c.maxHp;c.status=null;});
  showDialogue("The shrine's light washes over your team. All Resonants are fully restored.",()=>{
    showShop();saveGame();
  });
}

// ═══════ SHOP ═══════
function showShop(){
  G.phase="shop";
  document.getElementById("gold-display").textContent=`Gold: ${G.gold}`;
  const list=document.getElementById("shop-list");list.innerHTML="";
  const shopItems=["ether_draught","crystal_salve","resonator","resonator_plus","spark_seed","tide_gem","vine_crystal","void_shard","gale_feather","iron_core","prism_dust"];
  const prices={"ether_draught":20,"crystal_salve":80,"resonator":30,"resonator_plus":100,"spark_seed":50,"tide_gem":50,"vine_crystal":50,"void_shard":50,"gale_feather":50,"iron_core":50,"prism_dust":50};
  shopItems.forEach(id=>{
    const item=ITEMS[id];if(!item)return;
    const row=document.createElement("div");row.className="item-row";
    row.innerHTML=`<span>${item.name} <span style="color:var(--dim)">×${countItem(id)}</span></span><button class="btn btn-sm" onclick="buyItem('${id}',${prices[id]||50})">Buy (${prices[id]}g)</button>`;
    list.appendChild(row);
  });
  document.getElementById("shop-screen").classList.add("visible");G.screens.push("shop");
}

function buyItem(id,price){
  if(spendGold(price)){addItem(id);document.getElementById("gold-display").textContent=`Gold: ${G.gold}`;}
  else{showMsg("Not enough gold!");}
}
function closeShop(){document.getElementById("shop-screen").classList.remove("visible");popScreen();}

// ═══════ WILD ENCOUNTERS ═══════
function startWildEncounter(){
  const z=G.zone;const wilds=WILDS[z];if(!wilds||!wilds.length)return;
  // weighted random pick
  let totalW=wilds.reduce((s,w)=>s+w.w,0);let r=Math.random()*totalW;
  let chosen=wilds[0];for(const w of wilds){r-=w.w;if(r<=0){chosen=w;break;}}
  const base=getCreature(chosen.id);if(!base)return;
  // random level scaling with zone
  const lvl=3+Math.floor(Math.random()*5)+z*4;
  const hp=Math.floor(base.baseHp+(lvl-1)*2+Math.floor(lvl/3));
  const enemy={id:chosen.id,name:base.name,affinity:base.affinity,lvl,hp,maxHp:hp,atk:calcStat(base.baseAtk,lvl),def:calcStat(base.baseDef,lvl),spd:calcStat(base.baseSpd,lvl),color:base.color,icon:base.icon,moves:[...base.moves],boss:false};
  G.team.forEach(c=>{c.currentHp=c.hp;}); // heal party slightly between wilds
  enterBattle([enemy]);
}

// ═══════ BOSS BATTLE ═══════
function startBossBattle(bossKey){
  const bossData=BOSSES[bossKey];if(!bossData)return;
  // Find a creature of the same affinity for stat reference
  const refCreature=CREATURES.find(c=>c.affinity===bossData.affinity)||CREATURES[0];
  const hp=Math.floor(refCreature.baseHp*bossData.hpMul+(bossData.lvl-1)*3);
  G.team.forEach(c=>{if(c.currentHp!==undefined)c.currentHp=c.maxHp;else c.hp=c.maxHp;});
  enterBattle([{id:"boss_"+bossKey,name:bossData.name,affinity:bossData.affinity,lvl:bossData.lvl,hp,maxHp:hp,atk:Math.floor(refCreature.baseAtk*bossData.atkMul),def:Math.floor(refCreature.baseDef*bossData.defMul),spd:Math.floor(refCreature.baseSpd*bossData.spdMul),color:bossData.color,icon:bossData.icon,moves:[...bossData.moves],boss:true,hpMul:bossData.hpMul,atkMul:bossData.atkMul,defMul:bossData.defMul,spdMul:bossData.spdMul,bonusText:bossData.bonus}]);
  BS._bossKey=bossKey;
}

// ═══════ BATTLE SYSTEM ═══════
function enterBattle(enemies){
  G.phase="battle";BS.animating=false;
  initBattle(enemies);
  document.getElementById("battle-overlay").classList.add("active");
  // clear menus
  document.querySelectorAll(".overlay:not(#battle-overlay),#dialogue-box,#pause-menu,#team-screen,#inventory-screen,#ency-screen,#map-screen,#shop-screen,#capture-screen").forEach(el=>el.classList.remove("visible"));
}

function showBattleMenu(){BS.phase="select";}

// Battle menu rendering on canvas
let battleAnim=null;
function drawBattleScene(){
  if(G.phase!=="battle")return;
  const c=ctx;c.clearRect(0,0,W,H);

  // background
  const bgGrad=c.createLinearGradient(0,0,0,H);bgGrad.addColorStop(0,"#0a1628");bgGrad.addColorStop(1,"#1a237e");c.fillStyle=bgGrad;c.fillRect(0,0,W,H);

  // draw player creatures (left side)
  BS.players.forEach((p,i)=>{
    const px=W*0.15+(i%3)*60,py=H*0.6+i*20;
    drawCreatureCard(c,p.id,px+30,py+30,80,40,"player",BS.turnOrder[BS.currentTurn]?.idx===i&&BS.turnOrder[BS.currentTurn]?.type==="player");
  });

  // draw enemy creatures (right side)
  BS.enemies.forEach((e,i)=>{
    const ex=W*0.75-(i%2)*80,ey=H*0.1+i*35;
    if(e.hp<=0)return;
    drawCreatureCard(c,e.name,ex+40,ey+20,90,50,"enemy",false);
  });

  // dialogue / choice area at bottom
  drawBattleChoices(c);
}

function drawCreatureCard(c,name,x,y,w,h,type,highlight){
  c.fillStyle=highlight?"rgba(123,104,238,0.4)":"rgba(15,20,40,0.7)";
  if(highlight){c.shadowColor="var(--accent)";c.shadowBlur=8;}
  roundRect(c,x,y,w,h,8);c.fill();if(highlight){c.shadowBlur=0;}
  c.strokeStyle="rgba(123,104,238,0.5)";roundRect(c,x,y,w,h,8);c.stroke();

  // name
  c.fillStyle="#fff";c.font="bold 11px sans-serif";c.textAlign="left";
  c.fillText(name.length>10?name.slice(0,10):name,x+6,y+14);

  if(type==="player"){
    const p=G.team.find(t=>t.name===name)||BS.players[BS.turnOrder[BS.currentTurn]?.idx];
    if(p){
      c.fillStyle="#ccc";c.font="9px sans-serif";c.fillText(`Lv.${p.currentLevel} HP:${p.currentHp}/${p.maxHp}`,x+6,y+27);
      const hpPct=p.maxHp>0?p.currentHp/p.maxHp:0;
      c.fillStyle="rgba(255,255,255,0.1)";roundRect(c,x+6,y+30,w-12,4,2);c.fill();
      c.fillStyle=hpPct>0.5?"#00e676":hpPct>0.2?"#ffc107":"#ff1744";roundRect(c,x+6,y+30,(w-12)*hpPct,4,2);c.fill();
    }
  }else{
    const e=BS.enemies.find(en=>en.name===name);
    if(e){
      c.fillStyle="#ccc";c.font="9px sans-serif";
      c.fillText(`Lv.${e.lvl} HP:${e.hp}/${e.maxHp}`,x+6,y+27);
      const hpPct=e.maxHp>0?e.hp/e.maxHp:0;
      c.fillStyle="rgba(255,255,255,0.1)";roundRect(c,x+6,y+30,w-12,4,2);c.fill();
      c.fillStyle=hpPct>0.5?"#ff1744":hpPct>0.2?"#ffc107":"#ff1744";roundRect(c,x+6,y+30,(w-12)*hpPct,4,2);c.fill();
    }
  }
}

function roundRect(c,x,y,w,h,r){c.beginPath();c.moveTo(x+r,y);c.lineTo(x+w-r,y);c.quadraticCurveTo(x+w,y,x+w,y+r);c.lineTo(x+w,y+h-r);c.quadraticCurveTo(x+w,y+h,x+w-r,y+h);c.lineTo(x+r,y+h);c.quadraticCurveTo(x,y+h,x,y+h-r);c.lineTo(x,y+r);c.quadraticCurveTo(x,y,x+r,y);}

function drawBattleChoices(c){
  if(BS.phase!=="select")return;
  const current=G.team[BS.turnOrder[BS.currentTurn]?.idx];if(!current)return;
  
  // show move options
  c.fillStyle="rgba(0,0,0,0.7)";c.fillRect(0,H-140,W,140);
  c.strokeStyle="var(--border)";c.strokeRect(0,H-140,W,140);

  const moves=current.moves||[];
  const btnW=Math.min(140,(W-20)/Math.max(moves.length+1,1))-8;
  
  // FOCUS button
  c.fillStyle="#7b68ee";c.font="bold 13px sans-serif";c.textAlign="center";
  roundRect(c,W/2-btnW/2,H-50,btnW,36,8);c.fill();
  c.fillStyle="#fff";c.fillText("⚔ Focus",W/2,H-27);

  // MOVE button (opens sub-menu)
  const moveBtnX=W*0.15;
  roundRect(c,moveBtnX,H-50,btnW,36,8);c.fillStyle="#4caf50";c.fill();
  c.fillStyle="#fff";c.fillText("⚡ Move",moveBtnX+btnW/2,H-27);

  // Team button
  const teamBtnX=W*0.15+(btnW+8)*2;
  roundRect(c,teamBtnX,H-50,btnW,36,8);c.fillStyle="#ff9100";c.fill();
  c.fillStyle="#fff";c.fillText("🐾 Team",teamBtnX+btnW/2,H-27);

  // BAG button
  const bagBtnX=W*0.15+(btnW+8)*3;
  if(btnW*4<W*0.8){roundRect(c,bagBtnX,H-50,btnW,36,8);c.fillStyle="#e91e63";c.fill();c.fillStyle="#fff";c.fillText("🎒 Bag",bagBtnX+btnW/2,H-27);}

  // Show moves if MOVE selected
  if(BS.selectedMove){
    c.fillStyle="rgba(0,0,0,0.8)";c.fillRect(W*0.05,H-140,W*0.9,140);
    c.strokeStyle="#4caf50";c.strokeRect(W*0.05,H-140,W*0.9,140);
    moves.forEach((m,i)=>{
      const ab=ABILITIES.find(a=>a.id===m)||{name:m,power:35,type:"Ignis",pp:10};
      c.fillStyle=i===BS.focusIdx?"rgba(76,175,80,0.4)":"transparent";
      roundRect(c,W*0.08,H-120+i*((moves.length>4?100/moves.length:30)),W*0.84,Math.max(25,100/moves.length),6);c.fill();
      c.fillStyle="#fff";c.font="12px sans-serif";c.textAlign="left";
      c.fillText(`${ab.name} [${ab.type}] PP:${ab.pp}/${ab.pp}`,W*0.1,H-95+i*Math.max(28,100/moves.length));
    });
  }

  // Turn indicator
  c.fillStyle="#7b68ee";c.font="bold 14px sans-serif";c.textAlign="center";
  c.fillText(`Turn: ${current.name}`,W/2,H-152);

  // Enemy HP summary (top right)
  BS.enemies.forEach((e,i)=>{if(e.hp>0){
    const hpPct=e.maxHp>0?e.hp/e.maxHp:0;
    c.fillStyle="rgba(255,0,0,0.3)";c.fillRect(W-160,H*0.08+i*20,100,8);
    c.fillStyle=hpPct>0.5?"#ff1744":hpPct>0.2?"#ffc107":"#ff1744";c.fillRect(W-160,H*0.08+i*20,100*hpPct,8);
    c.fillStyle="#fff";c.font="9px sans-serif";c.textAlign="right";c.fillText(`${e.name} Lv.${e.lvl}`,W-30,H*0.08+i*20+7);
  }});
  c.textAlign="left";

  // Status effects indicator + tick processing
  BS.players.forEach((p,i)=>{if(p.status){
    const colors={burn:"🔥",frost:"❄️",shock:"⚡",barrier:"🛡"};
    c.fillStyle="#fff";c.font="10px sans-serif";c.textAlign="left";
    c.fillText(`${colors[p.status]||""} ${p.status}`,W*0.15+i*80,H-4);
  }});
}

// ═══════ BATTLE ACTIONS ═══════
function useMove(moveId,enemyIdx){
  // Status tick at start of turn
  if(BS.players.length>0){const currentPlayer=BS.players[BS.turnOrder[BS.currentTurn]?.idx];if(currentPlayer&&currentPlayer.status==="shock"){
    if(Math.random()<0.35){showFloatingText("Paralyzed! Turn lost!",W*0.2,H/3,"#ffeb3b");currentPlayer.statusTurns--;if(currentPlayer.statusTurns<=0)currentPlayer.status=null;setTimeout(()=>enemyTurn(),600);return;}
  }}

  const attacker=G.team[BS.turnOrder[BS.currentTurn]?.idx];if(!attacker)return;
  BS.phase="animating";BS.animating=true;

  const ability=ABILITIES.find(a=>a.id===moveId);if(!ability)return;
  
  // decrease PP
  const pIdx=G.team.findIndex(t=>t.id===attacker.id);
  if(pIdx>=0&&pIdx<BS.players.length){}
  G.team[pIdx].moves=G.team[pIdx].moves.filter(m=>m!==moveId)||[];
  // Actually just decrement: find the move index and reduce PP - for simplicity we'll handle this in a basic way
  // For now, let's not track individual PP per instance to keep code shorter

  const defender=BS.enemies[enemyIdx];if(!defender||defender.hp<=0)return;

  // damage calc
  let mult=effAtk(ability.type,attacker.affinity);
  // STAB
  if(ability.type===attacker.affinity)mult*=1.25;
  
  // status from ability effect
  const baseDmg=Math.max(1,Math.floor(0.4*attacker.currentLevel*attacker.atk/defender.def*mult)+Math.floor(Math.random()*5-2));
  let dmg=baseDmg;

  // critical hit (10% chance)
  if(Math.random()<0.1){dmg=Math.floor(dmg*1.5);showFloatingText("CRIT!",W/2,H/3,"#ffeb3b",true);}

  defender.hp=Math.max(0,defender.hp-dmg);
  showDamageNumber(defender,dmg);
  
  // effect application
  applyEffect(ability.effect,attacker,defender);

  // screen shake on hit
  if(dmg>25)shakeScreen();
  spawnParticles(W*0.75+(enemyIdx%2)*30,H*0.15+enemyIdx*40,TYPE_COLORS[ability.type]||"#fff",8,4);

  // check if enemy defeated
  setTimeout(()=>{
    if(defender.hp<=0){
      showFloatingText(`${defender.name} vanquished!`,W/2,H/3,"#fff");
      spawnParticles(W*0.75+(enemyIdx%2)*30,H*0.15+enemyIdx*40,"#ffffff",12,6);
    }
    
    // check if all enemies defeated
    const alive=BS.enemies.filter(e=>e.hp>0);
    if(alive.length===0){
      playCaptureSound();setTimeout(()=>endBattle(true),800);return;
    }

    // enemy turn (simplified)
    setTimeout(()=>enemyTurn(),600);
  },500);
}

function applyEffect(effect,attacker,defender){
  switch(effect){
    case "burn":defender.status="burn";defender.statusTurns=3;break;
    case "frost":defender.status="frost";defender.statusTurns=3;break;
    case "shock":if(Math.random()<0.4)defender.status="shock";break;
    case "barrier":attacker._barrier=true;break;
    case "regen_self":attacker.currentHp=Math.min(attacker.maxHp,attacker.currentHp+Math.floor(attacker.maxHp*0.25));break;
    case "drain":defender.hp-=15;attacker.currentHp=Math.min(attacker.maxHp,attacker.currentHp+15);break;
    case "speed_boost":attacker.spd=Math.floor(attacker.spd*1.2);break;
    case "atk_boost":attacker.atk=Math.floor(attacker.atk*1.15);break;
  }
}

function enemyTurn(){
  // Enemy AI: each living enemy picks a random move and targets the player with lowest HP%
  const living=BS.enemies.filter(e=>e.hp>0);
  let delay=0;
  for(const enemy of living){
    setTimeout(()=>{
      if(G.phase!=="battle")return;
      // pick random valid move
      const moves=enemy.moves||["ember_strike"];
      const moveId=moves[Math.floor(Math.random()*moves.length)];
      const ability=ABILITIES.find(a=>a.id===moveId)||ABILITIES[0];
      
      // find target with lowest HP% among players
      let bestTarget=null,bestPct=1;
      BS.players.forEach((p,i)=>{
        if(p.currentHp<=0)return;
        const pct=p.currentHp/p.maxHp;if(pct<bestPct){bestPct=pct;bestTarget=i;}
      });
      if(bestTarget===null||bestTarget>=BS.players.length)return;
      
      // check barrier
      if(BS.players[bestTarget]._barrier){
        BS.players[bestTarget]._barrier=false;
        spawnParticles(W*0.2,45,"#7b68ee",6,3);
        showFloatingText("Blocked!",W*0.15+bestTarget*60,80,"#7b68ee");
        return;
      }

      let mult=effAtk(ability.type,enemy.affinity);
      if(ability.type===enemy.affinity)mult*=1.25;
      const baseDmg=Math.max(1,Math.floor(0.4*enemy.lvl*enemy.atk/BS.players[bestTarget].def*mult)+Math.floor(Math.random()*4));
      let dmg=baseDmg;
      
      if(Math.random()<0.08){dmg=Math.floor(dmg*1.5);showFloatingText("CRIT!",W*0.2,80,"#ffeb3b");}
      playHitSound();
      
      BS.players[bestTarget].currentHp=Math.max(0,BS.players[bestTarget].currentHp-dmg);
      showDamageNumber(BS.players[bestTarget],dmg);
      applyEffect(ability.effect,enemy,BS.players[bestTarget]);

      // check if player defeated
      setTimeout(()=>{
        const alive=BS.enemies.filter(e=>e.hp>0);
        recalcTurnOrder();
        if(alive.length===0){setTimeout(()=>endBattle(true),600);return;}
        // soft-lock prevention: if all players fainted, auto-revive at 1 HP
        const playerAlive=BS.players.filter(p=>p.currentHp>0);
        if(playerAlive.length===0&&BS.enemies.every(e=>e.hp<=0)){endBattle(true);}
      },400);
    },delay);
    delay+=500;
  }
}

function endBattle(won){
  G.team.forEach(c=>{c.currentHp=c.hp;c.status=null;});
  document.getElementById("battle-overlay").classList.remove("active");
  
  if(won){
    // EXP distribution
    const totalExp=BS.enemies.reduce((s,e)=>s+e.lvl*8,0);
    G.team.forEach(c=>{c.experience+=totalExp;checkLevelUp(c);});
    
    // Gold reward
    addGold(BS.enemies.length*15+Math.floor(Math.random()*20));
    
    G.totalBattles++;

    // check boss defeat
    if(BS._bossKey&&BS.enemies.every(e=>e.hp<=0)){
      const z=G.zone;ZONES[z].bossDefeated=true;G.defeatedAnchors.add(BS._bossKey);
      showFloatingText("Anchor Defeated!",W/2,H/3,"#ffd700");
      // apply bonus
      if(BOSSES[BS._bossKey])applyZoneBonus(BOSSES[BS._bossKey].affinity);
    }

    // check for capture opportunity on first wild encounter in a zone
    if(G.defeatedAnchors.size>0&&G.team.length<6){
      const wilds=WILDS[G.zone];if(wilds&&wilds.length){
        setTimeout(()=>attemptCapture(),1000);return;
      }
    }

    // story progression check
    if(G.defeatedAnchors.size===6&&!G.postGame){
      G.postGame=true;showStoryVignette("The Forge Primarch crumbles. The Aetherium quakes... and then, silence.",()=>{
        G.phase="overworld";showMsg("All Anchors defeated! Free roam unlocked — harder wilds now appear.");
      });
    }else if(G.defeatedAnchors.size===1&&G.zone<5){
      // after first anchor, unlock next zone
      const nextZone=Math.min(5,G.zone+1);
      G.zone=nextZone;
    }else if(G.defeatedAnchors.size>=2&&!G.postGame){
      // progressive zone unlocking based on anchors defeated
      const unlockedZones=Math.min(5,Math.floor(G.defeatedAnchors.size/1.5));
      if(unlockedZones>G.zone)G.zone=unlockedZones;
    }

    // post-game: harder wilds
    if(G.postGame){
      ZONES[G.zone].encRate*=1.8;
    }

    // check encyclopedia updates
    BS.enemies.forEach(e=>{if(!G.captured.has(e.id))G.captured.add(e.id);});
  }else{
    G.team.forEach(c=>{c.currentHp=Math.max(1,Math.floor(c.hp*0.2));});
  }

  saveGame();
  G.phase="overworld";BS._bossKey=null;
}

function checkLevelUp(c){
  while(c.experience>=getExpToNext(c.currentLevel)&&c.currentLevel<50){
    c.experience-=getExpToNext(c.currentLevel);
    c.currentLevel++;
    // stat growth
    const base=getCreature(c.id)||{baseHp:50,baseAtk:35,baseDef:30,baseSpd:35};
    c.maxHp=calcStat(base.baseHp,c.currentLevel);c.hp=c.maxHp;
    c.atk=calcStat(base.baseAtk,c.currentLevel);
    c.def=calcStat(base.baseDef,c.currentLevel);
    c.spd=calcStat(base.baseSpd,c.currentLevel);
  }
}

function applyZoneBonus(affinity){
  const key=affinity.toLowerCase()+"Dmg";if(G.bonuses[key])G.bonuses[key]+=0.1;
}

// ═══════ CAPTURE SYSTEM ═══════
function attemptCapture(){
  if(!BS.enemies||BS.enemies.length===0)return;
  const target=BS.enemies.find(e=>e.hp>0);if(!target)return;
  
  // Rhythm mini-game
  G.phase="capture";
  document.getElementById("capture-screen").classList.add("visible");
  document.getElementById("capture-status").textContent="Tap the circle when it aligns!";

  let rhythmX=0,rhythmDir=1,rhythmSpeed=3;
  const rCanvas=document.getElementById("rhythm-canvas");
  const rcCtx=rCanvas.getContext("2d");
  let hits=0,misses=0,maxAttempts=5,attempt=0;
  
  function drawRhythm(){
    if(G.phase!=="capture")return;
    rcCtx.clearRect(0,0,rCanvas.width,rCanvas.height);
    // target zone
    const rhythmW=rCanvas.width,rhythmH=rCanvas.height;
    rcCtx.fillStyle="rgba(123,104,238,0.3)";rcCtx.fillRect(rhythmW/2-30,20,60,rhythmH-40);
    // moving indicator (vertical motion on canvas)
    rhythmX+=rhythmDir*rhythmSpeed;
    if(rhythmX>rhythmH-20||rhythmX<20)rhythmDir*=-1;
    rcCtx.fillStyle="#7b68ee";rcCtx.beginPath();rcCtx.arc(rhythmW/2,rhythmX,15,0,Math.PI*2);rcCtx.fill();
    
    // success zone (center of canvas)
    const hitY=rhythmH/2;
    rcCtx.strokeStyle="#4caf50";rcCtx.lineWidth=3;rcCtx.strokeRect(rhythmW/2-30,hitY-18,60,36);
    requestAnimationFrame(drawRhythm);
  }
  
  document.getElementById("rhythm-canvas").onclick=(e)=>{
    attempt++;
    const rect=e.target.getBoundingClientRect();
    const clickY=e.clientY-rect.top;
    const hitZoneY=rCanvas.height/2;
    if(Math.abs(clickY-hitZoneY)<40){hits++;document.getElementById("capture-status").textContent=`Hit! ${hits}/${attempt}`;}
    else{misses++;document.getElementById("capture-status").textContent=`Miss... ${hits}h/${attempt}t`;}
    
    if(attempt>=maxAttempts){
      const successChance=Math.min(0.9,hits/maxAttempts*1.2+(target.maxHp>0?(1-target.hp/target.maxHp)*0.5:0));
      if(Math.random()<successChance){
        document.getElementById("capture-status").textContent=`✓ ${target.name} bound!`;
        const newC=createInstance(target.id||target.name,target.lvl);if(newC)G.team.push(newC);
        G.captured.add(target.id||"wild_creature");
      }else{document.getElementById("capture-status").textContent="✗ Resonance failed...";spawnParticles(W/2,H/2,"#ff1744",6,3);}
      setTimeout(()=>document.getElementById("capture-screen").classList.remove("visible"),1500);
    }
  };

  drawRhythm();
}

// ═══════ DIALOGUE / VIGNETTE ═══════
function showDialogue(text,callback){
  G.phase="dialogue";
  const box=document.getElementById("dialogue-box");box.style.display="block";
  document.getElementById("dialogue-text").textContent=text;
  setTimeout(()=>{box.style.display="none";G.phase="overworld";if(callback)callback();},2500);
}

function showStoryVignette(text,callback){
  G.phase="vignette";
  const v=document.getElementById("story-vignette");v.classList.add("visible");
  document.getElementById("vignette-text").textContent=text;
  v.onclick=()=>{v.classList.remove("visible");G.phase="overworld";if(callback)callback();};
}

function showMsg(text){const box=document.getElementById("dialogue-box");box.style.display="block";document.getElementById("dialogue-text").textContent=text;setTimeout(()=>{box.style.display="none"},1500);}

// ═══════ FLOATING DAMAGE ═══════
let floatingTexts=[];
function showDamageNumber(target,dmg){
  const el=document.getElementById("floating-dmg");
  el.textContent=`-${dmg}`;el.style.left=(W*0.7+(target.focus||0)*30)+"px";el.style.top=H*0.2+"px";
  el.className=dmg>40?"crit-text":"";
  el.style.display="block";setTimeout(()=>{el.style.display="none";},800);
}

function showFloatingText(text,x,y,color,isCrit=false){
  const el=document.createElement("div");el.textContent=text;el.style.position="absolute";el.style.left=x+"px";el.style.top=y+"px";
  el.style.color=color||"#fff";el.style.fontWeight="bold";el.style.fontSize=isCrit?"24px":"16px";el.style.textShadow="0 0 10px rgba(0,0,0,0.8)";el.style.pointerEvents="none";el.style.zIndex=30;
  document.getElementById("game").appendChild(el);
  el.animate([{opacity:1,transform:"translateY(0)"},{opacity:0,transform:"translateY(-40px)"}],{duration:1200,easing:"ease-out"}).onfinish=()=>el.remove();
}

function shakeScreen(){document.getElementById("game").classList.add("shake");setTimeout(()=>document.getElementById("game").classList.remove("shake"),300);}

// ═══════ SCREENS (Pause, Team, Inventory, Encyclopedia) ═══════
function togglePause(){if(G.phase==="pause"){closePause();}else{G.phase="pause";document.getElementById("pause-menu").classList.add("visible");}}
function closePause(){document.getElementById("pause-menu").classList.remove("visible");G.phase=G.phase==="battle"?"battle":"overworld";}

// team screen
let screensOpen=[];
function popScreen(){if(screensOpen.length)G.screens.pop();}

function showTeam(){
  G.phase="team";screensOpen.push("team");document.getElementById("team-screen").classList.add("visible");
  const list=document.getElementById("team-list");list.innerHTML="";
  G.team.forEach((c,i)=>{
    const card=document.createElement("div");card.className="creature-card";
    card.onclick=()=>showCreatureDetail(i);
    const hpPct=c.maxHp>0?c.currentHp/c.maxHp:0;
    card.innerHTML=`<div class="creature-icon" style="background:${c.color}33;color:${c.color}">${c.icon}</div><div class="creature-info"><div class="creature-name">${c.name}</div><div class="creature-level">Lv.${c.currentLevel} ${c.affinity} | Bond ${c.bondLevel}</div><div class="hp-bar"><div class="hp-fill" style="width:${hpPct*100}%;background:${hpPct>0.5?"#00e676":hpPct>0.2?"#ffc107":"#ff1744"}"></div></div></div>`;
    list.appendChild(card);
  });
}
function showCreatureDetail(idx){
  const c=G.team[idx];if(!c)return;
  const base=getCreature(c.id)||{lore:"",affinity:c.affinity};
  showMsg(`${c.icon} ${c.name} Lv.${c.currentLevel} (${c.affinity}) | HP:${c.hp}/${c.maxHp} ATK:${c.atk} DEF:${c.def} SPD:${c.spd}`);
  const moves=c.moves.map(m=>{const ab=ABILITIES.find(a=>a.id===m)||{name:m,type:"?",power:0};return`${ab.name}[${ab.type}]`;}).join(" | ");
  showMsg(`Moves: ${moves}`);
}

function closeTeam(){document.getElementById("team-screen").classList.remove("visible");G.phase=G.team.length>0?(screensOpen.includes("battle")?"battle":"overworld"):"overworld";}

// inventory screen
function showInv(){
  G.phase="inv";screensOpen.push("inv");document.getElementById("inventory-screen").classList.add("visible");
  const list=document.getElementById("inv-list");list.innerHTML="";
  Object.entries(G.inventory).forEach(([id,count])=>{
    const item=ITEMS[id];if(!item)return;
    const row=document.createElement("div");row.className="item-row";
    row.innerHTML=`<span>${item.name} ×${count}</span><button class="btn btn-sm" onclick="useItem('${id}')">Use</button>`;
    list.appendChild(row);
  });
  if(!Object.keys(G.inventory).length)list.innerHTML='<p style="color:var(--dim);text-align:center;padding:20px">Empty</p>';
}
function closeInv(){document.getElementById("inventory-screen").classList.remove("visible");G.phase=G.team.length>0?(screensOpen.includes("battle")?"battle":"overworld"):"overworld";}

// encyclopedia screen
function showEncy(){
  G.phase="ency";screensOpen.push("ency");document.getElementById("ency-screen").classList.add("visible");
  document.getElementById("ency-count").textContent=`(${G.captured.size}/27)`;
  const list=document.getElementById("ency-list");list.innerHTML="";
  CREATURES.forEach(c=>{
    if(!G.captured.has(c.id))return;
    const card=document.createElement("div");card.className="creature-card";
    card.innerHTML=`<div class="creature-icon" style="background:${c.color}33;color:${c.color}">${c.icon}</div><div class="creature-info"><div class="creature-name">${c.name}</div><div class="creature-level" style="font-size:10px;color:var(--dim)">${c.affinity} | ${c.tier?"Transformed":"Base"}</div></div>`;
    list.appendChild(card);
  });
}
function closeEncy(){document.getElementById("ency-screen").classList.remove("visible");G.phase=G.team.length>0?(screensOpen.includes("battle")?"battle":"overworld"):"overworld";}

// map screen
function showMap(){
  G.phase="map";screensOpen.push("map");document.getElementById("map-screen").classList.add("visible");
  document.getElementById("map-info").textContent=`Anchors defeated: ${G.defeatedAnchors.size}/6`;
  const nav=document.getElementById("zone-nav");nav.innerHTML="";
  ZONES.forEach((z,i)=>{const unlocked=i<=Math.min(5,G.zone+1);
    const btn=document.createElement("button");btn.className="btn btn-sm";btn.textContent=z.name+(unlocked?" ✓":" 🔒");btn.disabled=!unlocked;btn.onclick=()=>{G.zone=i;closeMap();};nav.appendChild(btn);});
}
function closeMap(){document.getElementById("map-screen").classList.remove("visible");popScreen();}

// ═══════ ITEM USE ═══════
function useItem(id){
  const item=ITEMS[id];if(!item)return;
  if(item.type==="heal"){G.team.forEach(c=>{c.currentHp=item.power>=50?c.maxHp:c.hp+item.power;});showMsg("Used!");closeInv();}
  else if(item.type==="capture"){} // capture uses in battle context
  else if(item.effect&&item.effect.startsWith("evolve_")){tryEvolve(id);}
  showMsg(`Used ${item.name}`);
}

function tryEvolve(itemId){
  const catalyst=itemId;const cat=ITEMS[catalyst];if(!cat)return;
  G.team.forEach(c=>{
    if(cat.effect==="evolve_ignis"&&c.affinity==="Ignis"){
      const base=getCreature(c.id);if(base&&base.evo&&base.evo.transform){
        c.id=base.evo.name.toLowerCase().replace(/[^a-z]/g,"");c.name=base.evo.name;c.color=base.color||c.color;
        addItem(itemId,-1);showMsg(`Evolved into ${c.name}!`);return true;}
    }});
  showMsg("No eligible creature.");return false;
}

// ═══════ TUTORIAL ═══════
const TUTORIAL_STEPS=[
  {type:"text",msg:"Welcome, Binder. You awaken in the Ember Hollows — the first realm of Aetherium."},
  {type:"text",msg:"Use WASD or Arrow Keys to move your Resonant across the overworld.",action:null},
  {type:"encounter",msg:"A wild Pyralisk stirs! In battle, choose a Move and strike!",name:"Pyralisk",lvl:3},
  {type:"text",msg:"Well done! Defeating enemies earns EXP and Gold. Use Resonators to Bind new creatures.",action:null},
  {type:"text",msg:"The Shrine (⛩) restores your team. Walk near it and press ⚔ or E."}
];

function startTutorial(){
  G.tutorialStep=0;G.phase="tutorial";document.getElementById("tutorial-overlay").classList.add("visible");showTutStep();
}
function showTutStep(){
  const step=TUTORIAL_STEPS[G.tutorialStep]||null;if(!step)return;
  document.getElementById("tutorial-text").textContent=step.msg;
  if(step.type==="encounter"){
    const base=getCreature("pyralisk");if(base){
      G.phase="overworld";document.getElementById("tutorial-overlay").classList.remove("visible");
      setTimeout(()=>{
        enterBattle([{id:"pyralisk",name:"Pyralisk",affinity:"Ignis",lvl:step.lvl,hp:calcStat(base.baseHp,step.lvl),maxHp:calcStat(base.baseHp,step.lvl),atk:calcStat(base.baseAtk,step.lvl),def:calcStat(base.baseDef,step.lvl),spd:calcStat(base.baseSpd,step.lvl),color:"#ff6b35",icon:"🔥",moves:["ember_strike","scorch_wave","flame_lash","heat_shield"],boss:false}]);
      },200);
    }
  }
}
function nextTutorial(){if(G.tutorialStep<TUTORIAL_STEPS.length-1){G.tutorialStep++;showTutStep();}else{document.getElementById("tutorial-overlay").classList.remove("visible");G.phase="overworld";}}

// Override battle end for tutorial: after first tutorial battle, continue tutorial
let _origEndBattle=endBattle;
endBattle=function(won){
  if(G.tutorialStep===2&&won){G.tutorialStep=3;showTutStep();return;} // skip to next tutorial step
  return _origEndBattle.call(null,won);
};

// ═══════ KEYBOARD / TOUCH ═══════
const keysDown={};
document.addEventListener("keydown",e=>{if(keysDown[e.key])return;keysDown[e.key]=true;
  if(G.phase==="title"){if(e.key==="Enter"||e.key===" ")startNew();return;}
  const map={"ArrowUp":"up","ArrowDown":"down","ArrowLeft":"left","ArrowRight":"right",w:"up",a:"left",s:"down",d:"right"};
  if(map[e.key]&&G.phase==="overworld"&&!keysDown["interact"]){tryMove(map[e.key]);return;}
  if(e.key==="e"||e.key==="Enter"){if(G.phase==="overworld")doInteract();else if(G.phase==="battle")toggleInteract();}
  if(e.key==="Escape"){togglePause();}
  // battle move selection with number keys: 1-4 = moves, q/w/e/r = enemy targets
  if(G.phase==="battle"&&BS.phase==="select"){
    const cur=G.team[BS.turnOrder[BS.currentTurn]?.idx];if(cur){const moves=cur.moves;const idx=parseInt(e.key)-1;if(idx>=0&&idx<moves.length)useMove(moves[idx],Math.min(BS.enemies.length-1,G._targetEnemy||0));}
    if(e.key==="q"||e.key==="1"){G._targetEnemy=0;}
    else if(e.key==="w"||e.key==="2"&&BS.enemies.length>1){G._targetEnemy=1;}
    else if(e.key==="e"&&BS.enemies.length>2){G._targetEnemy=2;}
  }
});
document.addEventListener("keyup",e=>{keysDown[e.key]=false;});

// Show touch controls on small screens / touch devices
if('ontouchstart' in window||W<768)document.getElementById("touch-controls").style.display="block";

// Touch controls
document.querySelectorAll(".touch-btn").forEach(btn=>{
  btn.addEventListener("touchstart",e=>{e.preventDefault();const dir=btn.dataset.dir;if(dir&&G.phase==="overworld")tryMove(dir);if(dir==="up"||dir==="down"||dir==="left"||dir==="right"){setTimeout(()=>keysDown[dir]=false,200);}keysDown[dir]=true;});
  btn.addEventListener("touchend",()=>{const dir=btn.dataset.dir;keysDown[dir]=false;});
});

// ═══════ MAIN GAME LOOP ═══════
let lastTime=0;
function gameLoop(ts){
  requestAnimationFrame(gameLoop);
  const dt=Math.min(50,ts-lastTime);lastTime=ts;

  if(G.phase==="overworld"){
    drawOverworld();ambientParticles(G.zone);drawParticles(fxCtx);
    // show HUD with zone name
    document.getElementById("zone-hud").style.display="flex";
    document.getElementById("zone-name").textContent=ZONES[G.zone].name;
    const muteBtn=document.getElementById("mute-btn");
    if(muteBtn)muteBtn.textContent=audioMuted?"🔇":"🔊";
    // zone audio transition
    if(lastZone!==G.zone){startAmbientMusic(G.zone);lastZone=G.zone;}
  }else{
    document.getElementById("zone-hud").style.display="none";
  }else if(G.phase==="battle"||G.phase==="capture"){
    ctx.clearRect(0,0,W,H);
    if(G.phase==="battle")drawBattleScene();
    drawParticles(fxCtx);
  }

  if(G.phase==="title")drawTitleBg(ts);

  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

// ═══════ TITLE SCREEN BG ═══════
function drawTitleBg(t){
  const c=ctx;c.clearRect(0,0,W,H);
  const grad=c.createLinearGradient(0,0,0,H);grad.addColorStop(0,"#0a0e1a");grad.addColorStop(1,"#1a0a3a");c.fillStyle=grad;c.fillRect(0,0,W,H);
  // floating particles
  for(let i=0;i<25;i++){
    const x=(Math.sin(t*0.0003+i*1.7)*0.5+0.5)*W;
    const y=(Math.cos(t*0.0004+i*2.3)*0.5+0.5)*H;
    c.fillStyle=`rgba(${100+i*5},${80+i*3},${200-i*3},0.4)`;
    c.beginPath();c.arc(x,y,2+Math.sin(t*0.001+i)*1.5,0,Math.PI*2);c.fill();
  }
}

// ═══════ START / CONTINUE ═══════
function startNew(){
  G={phase:"overworld",zone:0,playerX:20,playerY:15,screens:[],team:[],inventory:{},gold:200,captured:new Set(),defeatedAnchors:new Set(),storyProgress:0,tutorialStep:0,tutorialDone:false,postGame:false,bonuses:{ignisDmg:0,undaDmg:0,viridiaDmg:0,umbraDmg:0,tempestaDmg:0,ferrumDmg:0},totalBattles:0,totalCaptures:0,enemiesDefeated:[]};
  // starter choice - simplified: give a Pyrux (fire-fox)
  G.team.push(createInstance("pyrux",1));
  G.inventory.resonator=3;G.inventory.ether_draught=2;
  
  document.getElementById("title-screen").classList.remove("visible");
  startTutorial();saveGame();
}

function continueGame(){const s=loadGame();if(s){Object.assign(G,s);document.getElementById("title-screen").classList.remove("visible");G.phase="overworld";}}

// ═══════ UTILITY ═══════
function createInstance(id,level){
  const base=getCreature(id);if(!base)return null;
  return{
    id,name:base.name,currentLevel:level,expToNext:getExpToNext(level),experience:0,
    hp:calcStat(base.baseHp,level),maxHp:calcStat(base.baseHp,level),
    atk:calcStat(base.baseAtk,level),def:calcStat(base.baseDef,level),spd:calcStat(base.baseSpd,level),
    affinity:base.affinity,color:base.color,icon:base.icon,moves:[...base.moves],status:null,statusTurns:0,bondLevel:1,
  };
}

// ═══════ AUDIO SYSTEM ═══════
let audioCtx=null,masterGain=null,audioMuted=false,ambientInterval=null;
function initAudio(){
  try{audioCtx=new(window.AudioContext||window.webkitAudioContext)();masterGain=audioCtx.createGain();masterGain.gain.value=0.15;masterGain.connect(audioCtx.destination);}catch(e){}
}
document.addEventListener("click",()=>{if(!audioCtx)initAudio();},{once:true});

function playTone(freq,dur,type="sine",vol=0.3){
  if(!audioCtx||audioMuted)return;
  const osc=audioCtx.createOscillator(),g=audioCtx.createGain();
  osc.type=type;osc.frequency.setValueAtTime(freq,audioCtx.currentTime);
  g.gain.setValueAtTime(vol,audioCtx.currentTime);
  g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+dur);
  osc.connect(g);g.connect(masterGain);osc.start();osc.stop(audioCtx.currentTime+dur);
}

function playHitSound(){playTone(220,0.15,"sawtooth",0.4);}
function playCritSound(){playTone(440,0.3,"square",0.5);setTimeout(()=>playTone(660,0.2,"square",0.3),100);}
function playCaptureSound(){playTone(523,0.1,"sine");setTimeout(()=>playTone(659,0.1,"sine"),100);setTimeout(()=>playTone(784,0.2,"sine"),200);}
function playLevelUpSound(){[523,659,784,1047].forEach((f,i)=>setTimeout(()=>playTone(f,0.2,"sine",0.4),i*120));}
function playMenuBeep(){playTone(880,0.05,"sine",0.1);}

function startAmbientMusic(zoneId){
  if(!audioCtx||audioMuted)return;
  stopAmbient();
  const scales={
    0:[220,261.63,293.66,349.23,392],[1],[220,261.63,293.66,349.23,392],[196,233.08,261.63,311.13,349.23],[
      261.63,329.63,392,440,523.25],[196,246.94,293.66,349.23,392]
  };
  const scale=scales[zoneId]||scales[0];
  ambientInterval=setInterval(()=>{
    if(!audioCtx)return;
    const note=scale[Math.floor(Math.random()*scale.length)];
    playTone(note*(0.8+Math.random()*0.4),1.5,"sine",0.06);
    if(Math.random()<0.3)playTone(note*2,0.8,"triangle",0.03);
  },1500+Math.random()*2000);
}

function stopAmbient(){if(ambientInterval){clearInterval(ambientInterval);ambientInterval=null;}}

// ═══════ ZONE CHANGE HANDLER ═══════
const _origZone=G.zone; // will be patched in game loop via monitoring

// ═══════ GAME LOOP FIX: zone audio ═══════
let lastZone=-1;

</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/ornith-1.0-35b","provider":"lmstudio","persona":"middle","sessionId":"2edf1c84-1810-461b-92ae-0d9dac0f970c","tokensIn":3275798,"tokensOut":43462,"tokensTotal":3319260,"turns":75,"toolCalls":74,"failedToolCalls":6,"timestamp":"2026-07-12T07:58:30.952Z"} -->