← Aethelgard results

Aethelgard

muse spark 1.2 · basic

Initial view of Aethelgard

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

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 53,959 bytes · SHA-256 5b3ece1aece2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aethelgard — Resonant Conductor</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=Press+Start+2P&family=Space+Grotesk:wght@600;700&display=swap" rel="stylesheet">
<script>tailwind.config={theme:{extend:{fontFamily:{pixel:['"Press Start 2P"'],body:['Inter'],display:['Space Grotesk']},colors:{aether:{900:'#0a0a14',800:'#12122a',700:'#1e1b4b'}}}}}</script>
<style>
*{scrollbar-width:thin;scrollbar-color:#7c3aed #0a0a14}
body{background:#070711;color:#e2e8f0;overflow-x:hidden}
.glass{background:rgba(255,255,255,0.07);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,0.12)}
.glass-strong{background:rgba(18,18,42,0.85);backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,0.14)}
.neon-pyro{box-shadow:0 0 20px rgba(249,115,22,0.5),inset 0 0 20px rgba(249,115,22,0.08);border-color:rgba(249,115,22,0.5)}
.neon-hydro{box-shadow:0 0 20px rgba(6,182,212,0.5),inset 0 0 20px rgba(6,182,212,0.08);border-color:rgba(6,182,212,0.5)}
.neon-flora{box-shadow:0 0 20px rgba(34,197,94,0.5),inset 0 0 20px rgba(34,197,94,0.08);border-color:rgba(34,197,94,0.5)}
.neon-volt{box-shadow:0 0 20px rgba(250,204,21,0.6),inset 0 0 20px rgba(250,204,21,0.12);border-color:rgba(250,204,21,0.6)}
.neon-zephyr{box-shadow:0 0 20px rgba(168,85,247,0.5),inset 0 0 20px rgba(168,85,247,0.08);border-color:rgba(168,85,247,0.5)}
.neon-earth{box-shadow:0 0 20px rgba(161,98,7,0.5),inset 0 0 20px rgba(161,98,7,0.08);border-color:rgba(161,98,7,0.5)}
@keyframes bounce-subtle{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
@keyframes shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-8px)}40%{transform:translateX(8px)}60%{transform:translateX(-6px)}80%{transform:translateX(6px)}}
@keyframes flash{0%{filter:brightness(2)}100%{filter:brightness(1)}}
@keyframes drift{0%{transform:translateY(0) opacity:0.8}100%{transform:translateY(-30px) opacity:0}}
.bounce{animation:bounce-subtle 1.6s ease-in-out infinite}
.shake{animation:shake 0.4s ease}
.flash{animation:flash 0.3s ease}
.bg-grid{background-image:radial-gradient(rgba(124,58,237,0.15) 1px, transparent 1px);background-size:24px 24px}
.type-pyro{background:linear-gradient(135deg,#f97316,#ef4444)} .type-hydro{background:linear-gradient(135deg,#06b6d4,#3b82f6)} .type-flora{background:linear-gradient(135deg,#22c55e,#16a34a)} .type-volt{background:linear-gradient(135deg,#facc15,#eab308);color:#422006} .type-zephyr{background:linear-gradient(135deg,#a78bfa,#7c3aed)} .type-earth{background:linear-gradient(135deg,#a16207,#92400e)} .type-cosmos{background:linear-gradient(135deg,#94a3b8,#64748b)}
.hp-green{background:linear-gradient(90deg,#22c55e,#4ade80)} .hp-yellow{background:linear-gradient(90deg,#eab308,#facc15)} .hp-red{background:linear-gradient(90deg,#ef4444,#f87171)}
::-webkit-scrollbar{width:6px;height:6px} ::-webkit-scrollbar-thumb{background:#4c1d95;border-radius:999px}
</style>
</head>
<body class="font-body bg-[#050510] min-h-screen selection:bg-violet-600 selection:text-white">
<div class="fixed inset-0 -z-10 bg-[#050510]">
<div class="absolute inset-0 bg-gradient-to-br from-violet-900/40 via-[#0a0a1a] to-cyan-900/20"></div>
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-[1200px] h-[600px] bg-violet-600/20 blur-[150px] rounded-full"></div>
<div class="absolute bottom-0 right-0 w-[800px] h-[500px] bg-cyan-500/10 blur-[120px] rounded-full"></div>
<div class="absolute inset-0 bg-grid opacity-40"></div>
</div>

<!-- TOP NAV -->
<nav class="sticky top-0 z-50 glass-strong border-b border-white/10">
<div class="max-w-[1100px] mx-auto px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-600 to-cyan-500 flex items-center justify-center font-pixel text-[10px]">Æ</div>
<div>
<div class="font-display font-bold leading-none tracking-wide">AETHELGARD</div>
<div class="text-[10px] tracking-[0.2em] text-violet-300">RESONANT CONDUCTOR v2.4</div>
</div>
</div>
<div class="flex items-center gap-2">
<div class="hidden md:flex items-center gap-2 glass rounded-full px-3 py-1.5">
<span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
<span class="text-xs font-bold" id="nav-shards">0 ◇ Shards</span>
</div>
<button onclick="switchScreen('overworld')" class="navbtn px-3 py-2 rounded-lg text-xs font-bold hover:bg-white/10 transition">MAP</button>
<button onclick="switchScreen('dex')" class="navbtn px-3 py-2 rounded-lg text-xs font-bold hover:bg-white/10 transition">DEX</button>
<button onclick="openShop()" class="px-3 py-2 rounded-lg text-xs font-bold bg-violet-600 hover:bg-violet-500 transition">SHOP</button>
<button onclick="saveGame()" class="hidden sm:block px-2 py-2 rounded-lg text-xs bg-white/10">💾</button>
</div>
</div>
</nav>

<div id="app" class="max-w-[1100px] mx-auto px-4 py-6">

<!-- MAIN MENU / STARTER -->
<div id="screen-menu" class="">
<div class="grid lg:grid-cols-[1.1fr_0.9fr] gap-6 items-center min-h-[70vh]">
<div class="glass rounded-[28px] p-8 md:p-10 relative overflow-hidden">
<div class="absolute -top-20 -right-20 w-64 h-64 bg-violet-600/30 blur-[60px] rounded-full"></div>
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-violet-600/20 border border-violet-500/30 text-[11px] tracking-widest">● RESONANCE STABLE — AETHER 97%</div>
<h1 class="font-display text-4xl md:text-5xl font-bold leading-[0.95] mt-4">TUNE THE<br><span class="bg-gradient-to-r from-violet-400 to-cyan-400 bg-clip-text text-transparent">FREQUENCY.</span><br>COMMAND THE WILD.</h1>
<p class="text-slate-300 mt-4 text-sm leading-relaxed max-w-lg">Aethelgard vibrates. Resonants — living elemental frequencies — roam free. As a Conductor, harmonize them with Aether Prisms and ascend the Apex Pillar.</p>
<div class="flex gap-3 mt-6">
<button onclick="continueGame()" id="btn-continue" class="hidden px-6 py-3 rounded-xl bg-white text-slate-900 font-bold text-sm">CONTINUE →</button>
<button onclick="showStarterPick()" class="px-6 py-3 rounded-xl bg-violet-600 hover:bg-violet-500 font-bold text-sm transition">NEW HARMONIC →</button>
</div>
<div class="grid grid-cols-3 gap-3 mt-8">
<div class="glass rounded-2xl p-3 text-center"><div class="text-lg">◈</div><div class="text-[11px] font-bold">18 RESONANTS</div><div class="text-[10px] text-slate-400">6 Families</div></div>
<div class="glass rounded-2xl p-3 text-center"><div class="text-lg">⬢</div><div class="text-[11px] font-bold">4 ZONES</div><div class="text-[10px] text-slate-400">Node Trail</div></div>
<div class="glass rounded-2xl p-3 text-center"><div class="text-lg">⚡</div><div class="text-[11px] font-bold">TURN TACTICS</div><div class="text-[10px] text-slate-400">Type Synergy</div></div>
</div>
</div>
<div class="space-y-4">
<div class="glass rounded-[24px] p-6">
<h3 class="font-display font-bold">Choose your First Resonance</h3>
<p class="text-xs text-slate-400 mt-1">This choice will shape your early journey in the Verdant Canopy.</p>
<div class="grid grid-cols-1 gap-3 mt-4" id="starter-grid"></div>
<div class="flex gap-2 mt-4">
<button onclick="resetSave()" class="text-[11px] px-3 py-2 rounded-lg bg-white/5 border border-white/10">Reset Progress</button>
<span class="text-[11px] text-slate-500 py-2">Local save • Auto-saves every action</span>
</div>
</div>
<div class="glass rounded-2xl p-4 flex items-center justify-between">
<div class="flex -space-x-2" id="preview-party"></div>
<div class="text-right"><div class="text-xs font-bold" id="menu-party-label">No team yet</div><div class="text-[11px] text-slate-400">Up to 3 active Resonants</div></div>
</div>
</div>
</div>
</div>

<!-- OVERWORLD -->
<div id="screen-overworld" class="hidden">
<div class="flex flex-wrap gap-2 mb-4" id="zone-tabs"></div>
<div class="grid lg:grid-cols-[1.7fr_0.8fr] gap-6">
<div class="glass rounded-[24px] p-5 md:p-6 relative overflow-hidden">
<div class="flex items-center justify-between">
<div>
<h2 class="font-display text-xl font-bold flex items-center gap-2"><span id="zone-icon">🌿</span> <span id="zone-name">Verdant Canopy</span></h2>
<p class="text-xs text-slate-400" id="zone-desc">Levels 1–5 • Flora / Zephyr</p>
</div>
<div class="text-right"><div class="text-[11px] tracking-widest text-violet-300">PROGRESS</div><div class="text-sm font-bold" id="zone-progress">0 / 7</div></div>
</div>
<div class="mt-3 h-2 rounded-full bg-white/10 overflow-hidden"><div id="zone-bar" class="h-full bg-gradient-to-r from-violet-600 to-cyan-400 transition-all" style="width:0%"></div></div>
<!-- Node Map -->
<div class="relative mt-6 bg-[#0b0b1e]/60 rounded-2xl border border-white/10 p-4 overflow-hidden">
<svg class="absolute inset-0 w-full h-full opacity-30" viewBox="0 0 800 260" preserveAspectRatio="none"><path id="trail-path" d="M40 180 Q200 40 360 150 T680 120" fill="none" stroke="rgba(124,58,237,0.6)" stroke-width="4" stroke-dasharray="8 10" stroke-linecap="round"/></svg>
<div id="node-map" class="relative grid grid-cols-7 gap-2 md:gap-3 py-8"></div>
</div>
<div class="flex gap-2 mt-4 text-[11px]">
<span class="px-2 py-1 rounded-full bg-emerald-500/20 border border-emerald-500/30">🌿 Wild</span>
<span class="px-2 py-1 rounded-full bg-amber-500/20 border border-amber-500/30">📦 Cache</span>
<span class="px-2 py-1 rounded-full bg-red-500/20 border border-red-500/30">⚔ Trainer</span>
<span class="px-2 py-1 rounded-full bg-cyan-500/20 border border-cyan-500/30">♨ Heal</span>
<span class="px-2 py-1 rounded-full bg-violet-600 text-white">♛ Boss</span>
</div>
</div>
<div class="space-y-4">
<div class="glass rounded-2xl p-4">
<h3 class="font-bold text-sm">Active Party</h3>
<div id="party-list" class="space-y-2 mt-3"></div>
</div>
<div class="glass rounded-2xl p-4">
<h3 class="font-bold text-sm flex justify-between">Inventory <button onclick="openShop()" class="text-xs text-violet-400">Shop →</button></h3>
<div id="inventory-list" class="grid grid-cols-2 gap-2 mt-3 text-xs"></div>
</div>
<div class="glass rounded-2xl p-4">
<div class="text-xs font-bold tracking-widest text-slate-400">CONDUCTOR LOG</div>
<div id="log" class="mt-2 text-xs leading-relaxed text-slate-300 max-h-[120px] overflow-auto">Welcome, Conductor. Choose a node to begin your harmonic trail.</div>
</div>
</div>
</div>
</div>

<!-- BATTLE -->
<div id="screen-battle" class="hidden">
<div class="glass rounded-[24px] overflow-hidden border border-white/15">
<div class="bg-gradient-to-r from-violet-950/60 to-slate-900/60 px-4 py-3 flex items-center justify-between border-b border-white/10">
<div class="flex items-center gap-2 text-xs"><span class="w-2 h-2 rounded-full bg-red-500 animate-pulse"></span> BATTLE — <span id="battle-type">Wild Encounter</span></div>
<div class="text-xs" id="battle-zone-label">Verdant Canopy</div>
</div>
<div id="battle-arena" class="relative bg-gradient-to-b from-[#1a1440] via-[#0f1230] to-[#0a1a22] p-4 md:p-6 min-h-[420px] overflow-hidden">
<div class="absolute inset-0 opacity-20" style="background-image:radial-gradient(rgba(255,255,255,0.12) 1px, transparent 1px);background-size:18px 18px"></div>
<!-- Enemy -->
<div class="relative flex justify-between items-start gap-4">
<div class="glass rounded-2xl p-3 w-[48%] max-w-[340px]">
<div class="flex justify-between items-center"><span class="text-[11px] tracking-widest text-slate-400">FOE</span><span id="enemy-level" class="text-xs px-2 py-0.5 rounded-full bg-white/10">Lv.5</span></div>
<div class="font-bold flex items-center gap-2 mt-1"><span id="enemy-type-badge" class="w-2 h-2 rounded-full"></span><span id="enemy-name">Thornback</span></div>
<div class="mt-2 h-2.5 rounded-full bg-black/40 overflow-hidden"><div id="enemy-hp" class="h-full hp-green transition-all" style="width:100%"></div></div>
<div class="text-[11px] flex justify-between mt-1"><span id="enemy-hp-text" class="text-slate-300">55 / 55</span><span id="enemy-status" class="text-amber-300"></span></div>
</div>
<div id="enemy-sprite" class="w-[160px] h-[140px] bounce flex items-center justify-center"></div>
</div>
<div id="battle-message" class="relative mt-2 text-center text-sm font-bold text-amber-200 min-h-[20px]"></div>
<!-- Player -->
<div class="relative flex justify-between items-end gap-4 mt-4">
<div id="player-sprite" class="w-[160px] h-[140px] flex items-center justify-center"></div>
<div class="glass rounded-2xl p-3 w-[48%] max-w-[340px]">
<div class="flex justify-between items-center"><span class="text-[11px] tracking-widest text-slate-400">YOU</span><span id="player-level" class="text-xs px-2 py-0.5 rounded-full bg-violet-600">Lv.5</span></div>
<div class="font-bold flex items-center gap-2 mt-1"><span id="player-type-badge" class="w-2 h-2 rounded-full"></span><span id="player-name">Emberkit</span></div>
<div class="mt-2 h-2.5 rounded-full bg-black/40 overflow-hidden"><div id="player-hp" class="h-full hp-green transition-all" style="width:100%"></div></div>
<div class="text-[11px] flex justify-between mt-1"><span id="player-hp-text">44 /44</span><span id="player-status"></span></div>
<div class="mt-2 h-1.5 rounded-full bg-black/40 overflow-hidden"><div id="player-xp" class="h-full bg-gradient-to-r from-cyan-400 to-violet-500" style="width:30%"></div></div>
<div class="text-[10px] text-slate-400 text-right" id="player-xp-text">XP 30/100</div>
</div>
</div>
<div id="battle-flash" class="pointer-events-none absolute inset-0 hidden bg-white/30"></div>
</div>
<!-- Controls -->
<div class="grid md:grid-cols-[1.2fr_0.8fr] gap-3 p-3 bg-[#0a0a18] border-t border-white/10">
<div id="move-grid" class="grid grid-cols-2 gap-2"></div>
<div class="grid grid-cols-3 gap-2">
<button onclick="battleAction('prism')" class="glass rounded-xl py-3 text-xs font-bold hover:bg-white/10">⬢ PRISM<br><span class="text-[10px] text-slate-400" id="prism-count">x5</span></button>
<button onclick="openExchange()" class="glass rounded-xl py-3 text-xs font-bold hover:bg-white/10">⇄ EXCHANGE</button>
<button onclick="useElixir()" class="glass rounded-xl py-3 text-xs font-bold hover:bg-white/10">⚗ ELIXIR<br><span class="text-[10px] text-slate-400" id="elixir-count">x1</span></button>
<button onclick="fleeBattle()" class="col-span-3 py-2 rounded-xl bg-white/5 border border-white/10 text-xs">FLEE (lose 10 ◇)</button>
</div>
</div>
</div>
</div>

<!-- DEX -->
<div id="screen-dex" class="hidden">
<div class="flex items-center justify-between">
<h2 class="font-display text-2xl font-bold">Conductor's Dex</h2>
<div class="text-xs glass rounded-full px-3 py-1"><span id="dex-count">0</span> / 18 Harmonized</div>
</div>
<div id="dex-grid" class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4 mt-4"></div>
</div>

</div>

<!-- SHOP MODAL -->
<div id="shop-modal" class="fixed inset-0 z-[60] hidden">
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" onclick="closeShop()"></div>
<div class="relative max-w-[720px] mx-auto mt-10 glass-strong rounded-[20px] p-5 m-4">
<div class="flex justify-between items-center"><h3 class="font-display font-bold text-lg">Camp Shop — Aether Exchange</h3><button onclick="closeShop()" class="w-8 h-8 rounded-full bg-white/10">✕</button></div>
<p class="text-xs text-slate-400">Spend Aether Shards earned from battles. Prices scale with conductorship.</p>
<div id="shop-grid" class="grid sm:grid-cols-2 gap-3 mt-4"></div>
<div class="mt-4 flex justify-between items-center text-sm"><span class="font-bold">Your Shards: <span id="shop-shards">0</span> ◇</span><span class="text-xs text-slate-400">Auto-saves on purchase</span></div>
</div>
</div>

<!-- EXCHANGE MODAL -->
<div id="exchange-modal" class="fixed inset-0 z-[60] hidden">
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" onclick="closeExchange()"></div>
<div class="relative max-w-[520px] mx-auto mt-20 glass-strong rounded-2xl p-5 m-4">
<h3 class="font-bold">Exchange Resonant</h3>
<div id="exchange-list" class="space-y-2 mt-3"></div>
<button onclick="closeExchange()" class="mt-3 w-full py-2 rounded-xl bg-white/10 text-sm">Cancel</button>
</div>
</div>

<!-- TOAST -->
<div id="toast" class="fixed bottom-6 left-1/2 -translate-x-1/2 glass-strong px-4 py-2 rounded-full text-sm font-bold hidden"></div>

<script>
// ——— Synth ———
class Synth{
 constructor(){this.ctx=null}
 ensure(){if(!this.ctx) this.ctx=new (window.AudioContext||window.webkitAudioContext)()}
 tone(f,d,type='sine',v=0.15,slide=0){this.ensure();const o=this.ctx.createOscillator(),g=this.ctx.createGain();o.type=type;o.frequency.value=f; if(slide){o.frequency.linearRampToValueAtTime(slide,this.ctx.currentTime+d)} g.gain.value=v; g.gain.exponentialRampToValueAtTime(0.001,this.ctx.currentTime+d); o.connect(g).connect(this.ctx.destination);o.start();o.stop(this.ctx.currentTime+d)}
 hover(){this.tone(600,0.08,'square',0.06)}
 select(){this.tone(800,0.12,'sine',0.12,1200)}
 hit(){this.tone(180,0.15,'square',0.18,80)} 
 superHit(){this.tone(440,0.25,'sawtooth',0.18,880); setTimeout(()=>this.tone(660,0.2,'sine',0.12),80)}
 levelUp(){[523,659,784,1046].forEach((f,i)=>setTimeout(()=>this.tone(f,0.25,'sine',0.12),i*90))}
 capture(){[400,600,800,1000].forEach((f,i)=>setTimeout(()=>this.tone(f,0.18,'sine',0.14),i*110))}
 heal(){this.tone(500,0.3,'sine',0.12,700)}
}
const synth=new Synth();
document.addEventListener('mouseover',e=>{if(e.target.closest('button')) synth.hover()})

// ——— Database ———
const RESONANTS=[
 {id:1,name:'Emberkit',type:'Pyro',evo:8,next:'Pyrowolf',base:{hp:44,atk:52,def:43,spd:65},moves:[{name:'Ember',power:40,acc:100,type:'Pyro',lv:1},{name:'Scratch',power:35,acc:100,type:'Cosmos',lv:1},{name:'Flame Wheel',power:60,acc:100,type:'Pyro',lv:6},{name:'Inferno Claw',power:75,acc:95,type:'Pyro',lv:10},{name:'Solar Eruption',power:95,acc:90,type:'Pyro',lv:14}]},
 {id:2,name:'Pyrowolf',type:'Pyro',evo:15,next:'Ignisaur',base:{hp:58,atk:75,def:60,spd:80},moves:[{name:'Ember',power:40,acc:100,type:'Pyro',lv:1},{name:'Bite',power:55,acc:100,type:'Cosmos',lv:1},{name:'Flame Wheel',power:60,acc:100,type:'Pyro',lv:6},{name:'Inferno Claw',power:75,acc:95,type:'Pyro',lv:10},{name:'Solar Eruption',power:95,acc:90,type:'Pyro',lv:14}]},
 {id:3,name:'Ignisaur',type:'Pyro',evo:null,next:null,base:{hp:78,atk:105,def:78,spd:95},moves:[{name:'Ember',power:40,acc:100,type:'Pyro',lv:1},{name:'Bite',power:55,acc:100,type:'Cosmos',lv:1},{name:'Flame Wheel',power:60,acc:100,type:'Pyro',lv:6},{name:'Inferno Claw',power:75,acc:95,type:'Pyro',lv:10},{name:'Solar Eruption',power:95,acc:90,type:'Pyro',lv:14}]},
 {id:4,name:'Droplet',type:'Hydro',evo:8,next:'Geoxus',base:{hp:50,atk:48,def:62,spd:43},moves:[{name:'Bubble',power:40,acc:100,type:'Hydro',lv:1},{name:'Tackle',power:35,acc:100,type:'Cosmos',lv:1},{name:'Aqua Jet',power:60,acc:100,type:'Hydro',lv:6},{name:'Tidal Crush',power:75,acc:95,type:'Hydro',lv:10},{name:'Abyssal Surge',power:95,acc:90,type:'Hydro',lv:14}]},
 {id:5,name:'Geoxus',type:'Hydro',evo:15,next:'Neptunian',base:{hp:65,atk:65,def:80,spd:52},moves:[{name:'Bubble',power:40,acc:100,type:'Hydro',lv:1},{name:'Aqua Jet',power:60,acc:100,type:'Hydro',lv:1},{name:'Tidal Crush',power:75,acc:95,type:'Hydro',lv:10},{name:'Abyssal Surge',power:95,acc:90,type:'Hydro',lv:14}]},
 {id:6,name:'Neptunian',type:'Hydro',evo:null,next:null,base:{hp:84,atk:85,def:102,spd:68},moves:[{name:'Bubble',power:40,acc:100,type:'Hydro',lv:1},{name:'Aqua Jet',power:60,acc:100,type:'Hydro',lv:1},{name:'Tidal Crush',power:75,acc:95,type:'Hydro',lv:10},{name:'Abyssal Surge',power:95,acc:90,type:'Hydro',lv:14}]},
 {id:7,name:'Seedling',type:'Flora',evo:8,next:'Thornback',base:{hp:55,atk:49,def:55,spd:45},moves:[{name:'Vine Whip',power:40,acc:100,type:'Flora',lv:1},{name:'Peck',power:35,acc:100,type:'Cosmos',lv:1},{name:'Razor Leaf',power:60,acc:100,type:'Flora',lv:6},{name:'Thorn Barrage',power:75,acc:95,type:'Flora',lv:10},{name:'Bloom Cataclysm',power:95,acc:90,type:'Flora',lv:14}]},
 {id:8,name:'Thornback',type:'Flora',evo:15,next:'Bloomgoliath',base:{hp:68,atk:68,def:72,spd:60},moves:[{name:'Vine Whip',power:40,acc:100,type:'Flora',lv:1},{name:'Razor Leaf',power:60,acc:100,type:'Flora',lv:1},{name:'Thorn Barrage',power:75,acc:95,type:'Flora',lv:10},{name:'Bloom Cataclysm',power:95,acc:90,type:'Flora',lv:14}]},
 {id:9,name:'Bloomgoliath',type:'Flora',evo:null,next:null,base:{hp:88,atk:92,def:95,spd:72},moves:[{name:'Razor Leaf',power:60,acc:100,type:'Flora',lv:1},{name:'Thorn Barrage',power:75,acc:95,type:'Flora',lv:1},{name:'Bloom Cataclysm',power:95,acc:90,type:'Flora',lv:14},{name:'Giga Drain',power:75,acc:100,type:'Flora',lv:12}]},
 {id:10,name:'Sparky',type:'Volt',evo:8,next:'Boltsaber',base:{hp:38,atk:55,def:40,spd:90},moves:[{name:'Spark',power:40,acc:100,type:'Volt',lv:1},{name:'Quick Jab',power:35,acc:100,type:'Cosmos',lv:1},{name:'Thunder Fang',power:60,acc:100,type:'Volt',lv:6},{name:'Volt Rush',power:75,acc:95,type:'Volt',lv:10},{name:'Stormlord Wrath',power:95,acc:90,type:'Volt',lv:14}]},
 {id:11,name:'Boltsaber',type:'Volt',evo:15,next:'Stormlord',base:{hp:52,atk:75,def:55,spd:105},moves:[{name:'Spark',power:40,acc:100,type:'Volt',lv:1},{name:'Thunder Fang',power:60,acc:100,type:'Volt',lv:1},{name:'Volt Rush',power:75,acc:95,type:'Volt',lv:10},{name:'Stormlord Wrath',power:95,acc:90,type:'Volt',lv:14}]},
 {id:12,name:'Stormlord',type:'Volt',evo:null,next:null,base:{hp:68,atk:95,def:70,spd:125},moves:[{name:'Thunder Fang',power:60,acc:100,type:'Volt',lv:1},{name:'Volt Rush',power:75,acc:95,type:'Volt',lv:1},{name:'Stormlord Wrath',power:95,acc:90,type:'Volt',lv:14},{name:'Plasma Guillotine',power:100,acc:85,type:'Volt',lv:16}]},
 {id:13,name:'Gusty',type:'Zephyr',evo:8,next:'Slipstream',base:{hp:42,atk:45,def:42,spd:85},moves:[{name:'Gust',power:40,acc:100,type:'Zephyr',lv:1},{name:'Wing Slap',power:35,acc:100,type:'Cosmos',lv:1},{name:'Aerial Ace',power:60,acc:100,type:'Zephyr',lv:6},{name:'Cyclone Dive',power:75,acc:95,type:'Zephyr',lv:10},{name:'Zephyr Tempest',power:95,acc:90,type:'Zephyr',lv:14}]},
 {id:14,name:'Slipstream',type:'Zephyr',evo:15,next:'Zephyrus',base:{hp:58,atk:62,def:58,spd:100},moves:[{name:'Gust',power:40,acc:100,type:'Zephyr',lv:1},{name:'Aerial Ace',power:60,acc:100,type:'Zephyr',lv:1},{name:'Cyclone Dive',power:75,acc:95,type:'Zephyr',lv:10},{name:'Zephyr Tempest',power:95,acc:90,type:'Zephyr',lv:14}]},
 {id:15,name:'Zephyrus',type:'Zephyr',evo:null,next:null,base:{hp:75,atk:80,def:75,spd:115},moves:[{name:'Aerial Ace',power:60,acc:100,type:'Zephyr',lv:1},{name:'Cyclone Dive',power:75,acc:95,type:'Zephyr',lv:1},{name:'Zephyr Tempest',power:95,acc:90,type:'Zephyr',lv:14},{name:'Sky Sunder',power:100,acc:85,type:'Zephyr',lv:16}]},
 {id:16,name:'Pebble',type:'Earth',evo:8,next:'Obsidian',base:{hp:60,atk:50,def:75,spd:30},moves:[{name:'Pebble Toss',power:40,acc:100,type:'Earth',lv:1},{name:'Tackle',power:35,acc:100,type:'Cosmos',lv:1},{name:'Rock Slide',power:60,acc:100,type:'Earth',lv:6},{name:'Quake',power:75,acc:95,type:'Earth',lv:10},{name:'Terrasunder',power:95,acc:90,type:'Earth',lv:14}]},
 {id:17,name:'Obsidian',type:'Earth',evo:15,next:'Terrasunder',base:{hp:78,atk:70,def:98,spd:45},moves:[{name:'Pebble Toss',power:40,acc:100,type:'Earth',lv:1},{name:'Rock Slide',power:60,acc:100,type:'Earth',lv:1},{name:'Quake',power:75,acc:95,type:'Earth',lv:10},{name:'Terrasunder',power:95,acc:90,type:'Earth',lv:14}]},
 {id:18,name:'Terrasunder',type:'Earth',evo:null,next:null,base:{hp:98,atk:98,def:122,spd:56},moves:[{name:'Rock Slide',power:60,acc:100,type:'Earth',lv:1},{name:'Quake',power:75,acc:95,type:'Earth',lv:1},{name:'Terrasunder',power:95,acc:90,type:'Earth',lv:14},{name:'Continental Crush',power:100,acc:85,type:'Earth',lv:16}]},
];
const typeColor={Pyro:'type-pyro',Hydro:'type-hydro',Flora:'type-flora',Volt:'type-volt',Zephyr:'type-zephyr',Earth:'type-earth',Cosmos:'type-cosmos'};

function getResByName(n){return RESONANTS.find(r=>r.name===n)}
function getResById(id){return RESONANTS.find(r=>r.id===id)}

const ZONES=[
 {id:0,name:'Verdant Canopy',icon:'🌿',desc:'Levels 1–5 • Flora / Zephyr',color:'emerald',pool:['Seedling','Gusty','Emberkit','Pebble'],boss:{name:'Sylva, Canopy Keeper',team:[{name:'Bloomgoliath',lv:7},{name:'Zephyrus',lv:7}],dialog:'The canopy tests your harmony. Pass and the grotto will weep for you.'}},
 {id:1,name:'Sunken Grotto',icon:'💧',desc:'Levels 5–10 • Hydro / Earth',color:'cyan',pool:['Droplet','Pebble','Seedling','Geoxus'],boss:{name:'Nerissa, Grotto Warden',team:[{name:'Neptunian',lv:11},{name:'Terrasunder',lv:11}],dialog:'Dive deep, Conductor. Pressure reveals true resonance.'}},
 {id:2,name:'Obsidian Crags',icon:'🔥',desc:'Levels 10–15 • Pyro / Volt',color:'orange',pool:['Emberkit','Sparky','Pebble','Pyrowolf'],boss:{name:'Magmus, Crag Tyrant',team:[{name:'Ignisaur',lv:14},{name:'Stormlord',lv:14}],dialog:'Ash and lightning — the crags spare no weak frequency.'}},
 {id:3,name:'Apex Pillar',icon:'⬢',desc:'Level 15+ • Final Gauntlet',color:'violet',pool:['Ignisaur','Neptunian','Stormlord','Zephyrus','Terrasunder','Bloomgoliath'],boss:{name:'AURELIA, Apex Sovereign',team:[{name:'Ignisaur',lv:17},{name:'Neptunian',lv:17},{name:'Stormlord',lv:18}],dialog:'You stand where frequencies converge. Prove you are the tuning fork of this world.'}},
];

const NodeTypes=['wild','cache','trainer','wild','cache','heal','boss'];

// SVG sprite
function spriteSVG(type, name, size=120){
 const grad={Pyro:['#ff6b35','#ef4444'],Hydro:['#06b6d4','#2563eb'],Flora:['#22c55e','#15803d'],Volt:['#fde047','#eab308'],Zephyr:['#c4b5fd','#7c3aed'],Earth:['#a16207','#78350f'],Cosmos:['#94a3b8','#475569']}[type]||['#999','#333'];
 const face = `<ellipse cx="60" cy="55" rx="46" ry="40" fill="url(#g)" stroke="rgba(255,255,255,0.9)" stroke-width="2"/><circle cx="42" cy="50" r="7" fill="#0a0a14"/><circle cx="78" cy="50" r="7" fill="#0a0a14"/><circle cx="45" cy="47" r="2.5" fill="white"/><circle cx="81" cy="47" r="2.5" fill="white"/><path d="M52 70 Q60 78 68 70" stroke="#0a0a14" stroke-width="2" fill="none" stroke-linecap="round"/>`;
 let deco='';
 if(type==='Pyro') deco='<path d="M60 8 L68 22 L60 18 L52 22 Z" fill="#fbbf24" opacity="0.9"/><path d="M20 90 Q60 115 100 90 Q60 100 20 90" fill="#f97316" opacity="0.5"/>';
 if(type==='Hydro') deco='<path d="M60 10 Q70 18 60 26 Q50 18 60 10" fill="#38bdf8"/><ellipse cx="60" cy="100" rx="34" ry="6" fill="rgba(56,189,248,0.3)"/>';
 if(type==='Flora') deco='<path d="M60 12 Q54 20 48 12 Q54 8 60 12" fill="#4ade80"/><path d="M72 14 Q66 22 60 14" fill="#86efac"/>';
 if(type==='Volt') deco='<path d="M58 6 L66 20 L60 20 L64 34 L52 20 L58 20 Z" fill="#facc15" stroke="#92400e"/>';
 if(type==='Zephyr') deco='<path d="M14 46 Q30 36 44 46 Q30 40 14 46 M76 46 Q90 36 106 46 Q90 40 76 46" fill="white" opacity="0.85"/>';
 if(type==='Earth') deco='<path d="M26 92 L34 78 L44 92 Z" fill="#78350f" opacity="0.8"/><path d="M86 92 L94 78 104 92 Z" fill="#78350f" opacity="0.8"/>';
 return `<svg viewBox="0 0 120 110" width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="${grad[0]}"/><stop offset="100%" stop-color="${grad[1]}"/></linearGradient></defs>${deco}${face}<text x="60" y="104" text-anchor="middle" font-size="7" font-weight="800" fill="white" opacity="0.9" style="font-family:Space Grotesk">${name.toUpperCase()}</text></svg>`
}

// State
let player = null;
let currentZone =0;
let battle=null;
let zoneProgress=[];

function createMon(name, lv){
 const base=getResByName(name);
 const s=JSON.parse(JSON.stringify(base));
 const hp = Math.floor((s.base.hp*2*lv)/100 + lv +10);
 const atk=Math.floor((s.base.atk*2*lv)/100+5);
 const def=Math.floor((s.base.def*2*lv)/100+5);
 const spd=Math.floor((s.base.spd*2*lv)/100+5);
 const moves=s.moves.filter(m=>m.lv<=lv).slice(-4);
 return {uid:Date.now()+Math.random(),name:s.name,type:s.type,level:lv,xp:0,xpTo: lv*40+40, maxHp:hp, hp:hp, atk,def,spd, moves, baseId:s.id, evo:s.evo, next:s.next}
}
function defaultPlayer(starter){
 return {shards:200, party:[createMon(starter,5)], box:[], inventory:{prism:5, hyper:1, elixir:2, cleanser:1}, unlocked:[0], completedBoss:[], dex:new Set([starter])}
}
function load(){
 const d=localStorage.getItem('aethelgard_save');
 if(d){
  try{const p=JSON.parse(d); p.dex=new Set(p.dex||[]); player=p; zoneProgress=JSON.parse(localStorage.getItem('aethelgard_progress')||'null')||ZONES.map(()=>Array(7).fill(false)); return true}catch(e){}
 }
 return false
}
function saveGame(){
 if(!player) return;
 localStorage.setItem('aethelgard_save', JSON.stringify({...player, dex:[...player.dex]}));
 localStorage.setItem('aethelgard_progress', JSON.stringify(zoneProgress));
 showToast('Progress saved ◇');
 updateNav();
}
function autoSave(){ if(player) {localStorage.setItem('aethelgard_save', JSON.stringify({...player, dex:[...player.dex]})); localStorage.setItem('aethelgard_progress', JSON.stringify(zoneProgress)); updateNav();}}

function switchScreen(name){
 document.querySelectorAll('#screen-menu,#screen-overworld,#screen-battle,#screen-dex').forEach(s=>s.classList.add('hidden'));
 document.getElementById('screen-'+name).classList.remove('hidden');
 if(name==='overworld') renderOverworld();
 if(name==='dex') renderDex();
 if(name==='menu') renderMenu();
 window.scrollTo({top:0,behavior:'smooth'})
 synth.select()
}
function showToast(t){
 const el=document.getElementById('toast'); el.textContent=t; el.classList.remove('hidden'); setTimeout(()=>el.classList.add('hidden'),2200)
}

// Menu rendering
function renderMenu(){
 const starters=['Emberkit','Droplet','Seedling'];
 const grid=document.getElementById('starter-grid');
 grid.innerHTML=starters.map(n=>{
  const r=getResByName(n);
  return `<button onclick="startWith('${n}')" class="flex items-center gap-3 p-3 rounded-2xl glass hover:bg-white/10 text-left border border-white/10">
   <div class="w-[64px] h-[64px] rounded-xl flex items-center justify-center ${typeColor[r.type]}">${spriteSVG(r.type,n,52)}</div>
   <div><div class="font-bold text-sm">${n} <span class="text-[10px] px-1.5 py-0.5 rounded-full bg-white/15">${r.type}</span></div><div class="text-[11px] text-slate-400">HP ${r.base.hp} • ATK ${r.base.atk} • SPD ${r.base.spd}</div></div>
  </button>`
 }).join('');
 const hasSave=!!localStorage.getItem('aethelgard_save');
 document.getElementById('btn-continue').classList.toggle('hidden',!hasSave);
 updatePreview();
}
function updatePreview(){
 const c=document.getElementById('preview-party'); const label=document.getElementById('menu-party-label');
 if(!player||!player.party.length){c.innerHTML='<div class="w-10 h-10 rounded-full bg-white/10"></div>'; label.textContent='No team yet'; return;}
 c.innerHTML=player.party.map(m=>`<div class="w-10 h-10 rounded-full ${typeColor[m.type]} p-1 border border-white/20" title="${m.name} Lv${m.level}">${spriteSVG(m.type,m.name,36)}</div>`).join('');
 label.textContent=player.party.map(m=>m.name+' Lv'+m.level).join(' • ');
}
function startWith(name){
 player=defaultPlayer(name); zoneProgress=ZONES.map(()=>Array(7).fill(false)); autoSave(); renderOverworld(); switchScreen('overworld'); showToast(name+' harmonized! ◈'); synth.levelUp();
}
function continueGame(){ if(load()){switchScreen('overworld')} else showToast('No save found')}
function showStarterPick(){ document.getElementById('starter-grid').scrollIntoView({behavior:'smooth'})}
function resetSave(){ localStorage.removeItem('aethelgard_save'); localStorage.removeItem('aethelgard_progress'); player=null; zoneProgress=[]; renderMenu(); showToast('Progress reset'); }

// Overworld
function renderOverworld(){
 if(!player){ switchScreen('menu'); return}
 // tabs
 document.getElementById('zone-tabs').innerHTML=ZONES.map((z,i)=>{
  const unlocked=player.unlocked.includes(i);
  return `<button onclick="selectZone(${i})" class="px-4 py-2 rounded-full text-xs font-bold border ${currentZone===i?'bg-violet-600 border-violet-500 text-white':'glass border-white/10'} ${!unlocked?'opacity-40 cursor-not-allowed':''}" ${!unlocked?'disabled':''}>${z.icon} ${z.name.toUpperCase()} ${!unlocked?'🔒':''}</button>`
 }).join('');
 const z=ZONES[currentZone];
 document.getElementById('zone-name').textContent=z.name;
 document.getElementById('zone-icon').textContent=z.icon;
 document.getElementById('zone-desc').textContent=z.desc;
 if(!zoneProgress[currentZone]) zoneProgress[currentZone]=Array(7).fill(false);
 const done=zoneProgress[currentZone].filter(Boolean).length;
 document.getElementById('zone-progress').textContent=done+' / 7';
 document.getElementById('zone-bar').style.width=(done/7*100)+'%';
 // nodes
 const map=document.getElementById('node-map');
 map.innerHTML=NodeTypes.map((type,idx)=>{
  const isDone=zoneProgress[currentZone][idx];
  const isBoss=type==='boss';
  const accessible = idx===0 || zoneProgress[currentZone][idx-1] || isDone;
  let icon='🌿', label='Wild', cls='bg-emerald-500';
  if(type==='cache'){icon='📦';label='Cache';cls='bg-amber-500'}
  if(type==='trainer'){icon='⚔';label='Duel';cls='bg-red-500'}
  if(type==='heal'){icon='♨';label='Spring';cls='bg-cyan-500'}
  if(type==='boss'){icon='♛';label='Gatekeeper';cls='bg-violet-600'}
  return `<button onclick="clickNode(${idx})" ${!accessible?'disabled':''} class="relative flex flex-col items-center gap-1 ${!accessible?'opacity-40':''}">
   <div class="w-[56px] h-[56px] md:w-[64px] md:h-[64px] rounded-2xl ${isDone?'bg-white/10 border border-white/20':'glass border border-white/15'} flex items-center justify-center text-xl ${isDone?'grayscale opacity-60':''} ${isBoss?'ring-2 ring-violet-400 ring-offset-2 ring-offset-[#0b0b1e]':''} hover:scale-105 transition">
    <span class="w-8 h-8 rounded-full ${cls} flex items-center justify-center text-white text-sm">${icon}</span>
    ${isDone?'<span class="absolute -top-1 -right-1 w-5 h-5 bg-emerald-500 rounded-full flex items-center justify-center text-[10px]">✓</span>':''}
   </div>
   <span class="text-[10px] font-bold tracking-widest">${label}</span>
   <span class="text-[10px] text-slate-500">${idx+1}</span>
  </button>`
 }).join('');
 renderParty(); renderInventory(); updateNav();
}
function selectZone(i){
 if(!player.unlocked.includes(i)){showToast('Zone locked — defeat the Gatekeeper!'); return}
 currentZone=i; renderOverworld(); synth.select();
}
function clickNode(idx){
 const type=NodeTypes[idx];
 if(type==='heal'){ player.party.forEach(m=>m.hp=m.maxHp); zoneProgress[currentZone][idx]=true; autoSave(); renderOverworld(); log('Healing Spring restores your party to full harmonic resonance. ♨'); synth.heal(); showToast('Party healed!'); return}
 if(type==='cache'){ const reward= 40+ Math.floor(Math.random()*60); const itemRoll=Math.random(); player.shards+=reward; let txt=`Cache opened: +${reward} ◇ Shards!`;
  if(itemRoll<0.5){player.inventory.prism++; txt+=' +1 Prism'} else if(itemRoll<0.75){player.inventory.elixir++; txt+=' +1 Elixir'} else {player.inventory.hyper++; txt+=' +1 Hyper Prism'}
  zoneProgress[currentZone][idx]=true; autoSave(); renderOverworld(); log(txt); showToast(txt); synth.select(); return}
 if(type==='trainer'){ const tLv= 4+ currentZone*4 + idx; const pool=ZONES[currentZone].pool; const enemyName=pool[Math.floor(Math.random()*pool.length)]; startBattle('trainer', enemyName, tLv, ZONES[currentZone].boss.name); return}
 if(type==='wild'){ const pool=ZONES[currentZone].pool; const name=pool[Math.floor(Math.random()*pool.length)]; const lv= 2+ currentZone*4 + Math.floor(Math.random()*3)+ idx; startBattle('wild', name, lv); return}
 if(type==='boss'){ const b=ZONES[currentZone].boss; log(`Gatekeeper ${b.name} blocks the path: "${b.dialog}"`); // start boss battle with first team member
  const foe=b.team[0]; startBattle('boss', foe.name, foe.lv, b.name, b.team); return}
}

function log(t){ const el=document.getElementById('log'); el.innerHTML= `<div>› ${t}</div>` + el.innerHTML; }

function renderParty(){
 const c=document.getElementById('party-list');
 c.innerHTML=player.party.map((m,i)=>`
  <div class="flex items-center gap-3 p-2 rounded-xl ${i===0?'bg-violet-600/20 border border-violet-500/30':'bg-white/5 border border-white/5'}">
   <div class="w-10 h-10 rounded-xl ${typeColor[m.type]} flex items-center justify-center">${spriteSVG(m.type,m.name,36)}</div>
   <div class="flex-1">
    <div class="text-xs font-bold">${m.name} <span class="text-[10px] opacity-70">Lv${m.level}</span> <span class="text-[10px] px-1 rounded bg-white/15">${m.type}</span></div>
    <div class="h-1.5 rounded-full bg-black/30 mt-1 overflow-hidden"><div class="h-full ${m.hp/m.maxHp>0.5?'hp-green':m.hp/m.maxHp>0.2?'hp-yellow':'hp-red'}" style="width:${Math.max(0,m.hp/m.maxHp*100)}%"></div></div>
    <div class="text-[10px] text-slate-400">${m.hp}/${m.maxHp} HP • ${m.xp}/${m.xpTo} XP</div>
   </div>
  </div>`).join('') || '<div class="text-xs text-slate-500">No Resonants</div>';
}
function renderInventory(){
 const inv=player.inventory;
 document.getElementById('inventory-list').innerHTML=`
  <div class="glass rounded-xl p-2 flex justify-between">⬢ Prism <b>x${inv.prism}</b></div>
  <div class="glass rounded-xl p-2 flex justify-between">⬣ Hyper <b>x${inv.hyper}</b></div>
  <div class="glass rounded-xl p-2 flex justify-between">⚗ Elixir <b>x${inv.elixir}</b></div>
  <div class="glass rounded-xl p-2 flex justify-between">✦ Cleanser <b>x${inv.cleanser}</b></div>
 `;
}
function updateNav(){
 if(!player) return;
 document.getElementById('nav-shards').textContent=player.shards+' ◇ Shards';
 const s=document.getElementById('shop-shards'); if(s) s.textContent=player.shards;
}

// ——— Battle Engine ———
function startBattle(kind, enemyName, lv, trainerName=null, bossTeam=null){
 const enemyMon=createMon(enemyName, lv);
 // scale boss hard
 if(kind==='boss'){enemyMon.atk=Math.floor(enemyMon.atk*1.15); enemyMon.def=Math.floor(enemyMon.def*1.15); enemyMon.maxHp=Math.floor(enemyMon.maxHp*1.2); enemyMon.hp=enemyMon.maxHp}
 // ensure player has alive mon
 let activeIdx=player.party.findIndex(m=>m.hp>0);
 if(activeIdx===-1){ showToast('All Resonants fainted — visit Healing Spring!'); return}
 battle={kind, trainerName, bossTeam, bossIdx:0, enemy:enemyMon, playerIdx:activeIdx, turn:'player'};
 document.getElementById('battle-type').textContent= kind==='wild'?'Wild Encounter': kind==='trainer'?'Conductor Duel':'Gatekeeper Battle';
 document.getElementById('battle-zone-label').textContent=ZONES[currentZone].name;
 switchScreen('battle');
 renderBattle();
 log(`Encounter! ${kind==='wild'?'Wild':trainerName||'Trainer'} ${enemyName} Lv${lv} appears!`);
}
function renderBattle(){
 if(!battle) return;
 const p=player.party[battle.playerIdx];
 const e=battle.enemy;
 // player
 document.getElementById('player-name').textContent=p.name;
 document.getElementById('player-level').textContent='Lv.'+p.level;
 document.getElementById('player-type-badge').className='w-2 h-2 rounded-full '+typeColor[p.type].split(' ')[0];
 document.getElementById('player-hp').style.width=Math.max(0, p.hp/p.maxHp*100)+'%';
 document.getElementById('player-hp').className='h-full transition-all '+(p.hp/p.maxHp>0.5?'hp-green':p.hp/p.maxHp>0.2?'hp-yellow':'hp-red');
 document.getElementById('player-hp-text').textContent=p.hp+' / '+p.maxHp;
 document.getElementById('player-xp').style.width=(p.xp/p.xpTo*100)+'%';
 document.getElementById('player-xp-text').textContent='XP '+p.xp+'/'+p.xpTo;
 document.getElementById('player-sprite').innerHTML=spriteSVG(p.type,p.name,140);
 // enemy
 document.getElementById('enemy-name').textContent=e.name;
 document.getElementById('enemy-level').textContent='Lv.'+e.level;
 document.getElementById('enemy-type-badge').className='w-2 h-2 rounded-full '+typeColor[e.type].split(' ')[0];
 document.getElementById('enemy-hp').style.width=Math.max(0,e.hp/e.maxHp*100)+'%';
 document.getElementById('enemy-hp').className='h-full transition-all '+(e.hp/e.maxHp>0.5?'hp-green':e.hp/e.maxHp>0.2?'hp-yellow':'hp-red');
 document.getElementById('enemy-hp-text').textContent=e.hp+' / '+e.maxHp;
 document.getElementById('enemy-sprite').innerHTML=spriteSVG(e.type,e.name,140);
 // moves
 const grid=document.getElementById('move-grid');
 grid.innerHTML=p.moves.map(m=>`<button onclick="playerMove('${m.name}')" class="glass rounded-xl p-2.5 text-left hover:bg-white/10 border border-white/10">
  <div class="text-xs font-bold flex justify-between">${m.name} <span class="text-[10px] px-1 rounded ${typeColor[m.type]}">${m.type}</span></div>
  <div class="text-[11px] text-slate-400">PWR ${m.power} • ACC ${m.acc}%</div>
 </button>`).join('');
 document.getElementById('prism-count').textContent='x'+(player.inventory.prism+player.inventory.hyper);
 document.getElementById('elixir-count').textContent='x'+player.inventory.elixir;
}
function typeMod(atkType, defType){
 const strong={Pyro:['Flora'], Hydro:['Pyro'], Flora:['Hydro'], Zephyr:['Earth'], Earth:['Volt'], Volt:['Zephyr']};
 const weak={Pyro:['Hydro'], Hydro:['Flora'], Flora:['Pyro'], Zephyr:['Volt'], Earth:['Zephyr'], Volt:['Earth']};
 if(strong[atkType]?.includes(defType)) return 1.6;
 if(weak[atkType]?.includes(defType)) return 0.6;
 return 1;
}
function calcDamage(attacker, defender, move){
 const lvl=attacker.level;
 const mod=typeMod(move.type, defender.type);
 const rand=0.85+Math.random()*0.15;
 const crit=Math.random()<0.10?1.5:1;
 const base=(((2*lvl/5+2)*move.power*(attacker.atk/defender.def))/50+2);
 const dmg=Math.floor(base*mod*rand*crit);
 return {dmg, mod, crit:crit>1}
}
function flashBattle(){ const f=document.getElementById('battle-flash'); f.classList.remove('hidden'); f.classList.add('flash'); setTimeout(()=>{f.classList.add('hidden'); f.classList.remove('flash')},280)}
function shakeBattle(){ const a=document.getElementById('battle-arena'); a.classList.add('shake'); setTimeout(()=>a.classList.remove('shake'),400)}
function setMsg(t){ document.getElementById('battle-message').textContent=t; }

async function playerMove(moveName){
 if(!battle||battle.turn!=='player') return;
 const p=player.party[battle.playerIdx];
 const e=battle.enemy;
 const move=p.moves.find(m=>m.name===moveName);
 if(!move) return;
 // accuracy
 if(Math.random()*100>move.acc){ setMsg(`${p.name}'s ${move.name} missed!`); synth.hit(); battle.turn='enemy'; setTimeout(enemyTurn,700); renderBattle(); return}
 const res=calcDamage(p,e,move);
 e.hp=Math.max(0,e.hp-res.dmg);
 let msg=`${p.name} used ${move.name}!`;
 if(res.mod>1) msg+=' Super effective!'; else if(res.mod<1) msg+=' Not very effective...';
 if(res.crit) msg+=' Critical hit!';
 setMsg(msg); flashBattle(); if(res.mod>1) synth.superHit(); else synth.hit();
 renderBattle();
 if(e.hp<=0){ await handleVictory(); return}
 battle.turn='enemy'; setTimeout(enemyTurn,700);
}
async function enemyTurn(){
 if(!battle) return;
 const p=player.party[battle.playerIdx];
 const e=battle.enemy;
 const move=e.moves[Math.floor(Math.random()*e.moves.length)];
 if(Math.random()*100>move.acc){ setMsg(`${e.name}'s ${move.name} missed!`); battle.turn='player'; return}
 const res=calcDamage(e,p,move);
 p.hp=Math.max(0,p.hp-res.dmg);
 let msg=`Foe ${e.name} used ${move.name}!`;
 if(res.mod>1) msg+=' Super effective!'; else if(res.mod<1) msg+=' Not very effective.';
 if(res.crit) msg+=' Critical!';
 setMsg(msg); shakeBattle(); synth.hit(); renderBattle();
 if(p.hp<=0){
  p.hp=0;
  const alive=player.party.findIndex((m,i)=>i!==battle.playerIdx && m.hp>0);
  if(alive===-1){ // wipe
   setMsg('All your Resonants fainted! You scurry back.');
   player.shards=Math.max(0,player.shards-10);
   setTimeout(()=>{ switchScreen('overworld'); battle=null; autoSave(); renderOverworld();},1400);
  } else {
   setMsg(`${p.name} fainted! Choose next Resonant.`);
   openExchange(true);
  }
 } else battle.turn='player';
}
async function handleVictory(){
 const p=player.party[battle.playerIdx];
 const e=battle.enemy;
 const xpGain= 12 + e.level*6 + (battle.kind==='boss'?30: battle.kind==='trainer'?20:0);
 const shardGain= 14 + e.level*3 + (battle.kind==='boss'?80:0);
 player.shards+=shardGain;
 p.xp+=xpGain;
 player.dex.add(e.name);
 setMsg(`Foe ${e.name} harmonized! +${xpGain} XP +${shardGain} ◇`);
 synth.levelUp();
 // level up check
 while(p.xp>=p.xpTo){
  p.xp-=p.xpTo; p.level++; p.xpTo=Math.floor(p.xpTo*1.18);
  p.maxHp+= 6+Math.floor(Math.random()*4); p.atk+=3; p.def+=3; p.spd+=2; p.hp=p.maxHp;
  // unlock moves may already filtered but check evo
  const base=getResByName(p.name);
  if(base && base.evo && p.level>=base.evo){
   const nextName=base.next;
   const nextBase=getResByName(nextName);
   p.name=nextName; p.type=nextBase.type; p.baseId=nextBase.id; p.evo=nextBase.evo; p.next=nextBase.next;
   // recalc moves
   p.moves=nextBase.moves.filter(m=>m.lv<=p.level).slice(-4);
   setMsg(`✨ ${base.name} evolved into ${nextName}!`);
   synth.capture();
  } else {
   // refresh moves
   const curBase=getResByName(p.name);
   p.moves=curBase.moves.filter(m=>m.lv<=p.level).slice(-4);
  }
  showToast(`Lv Up! ${p.name} is now Lv${p.level}`);
 }
 // boss progression
 if(battle.kind==='boss' && battle.bossTeam){
  if(battle.bossIdx < battle.bossTeam.length-1){
   battle.bossIdx++;
   const nxt=battle.bossTeam[battle.bossIdx];
   battle.enemy=createMon(nxt.name, nxt.lv);
   battle.turn='player';
   setTimeout(()=>{setMsg(`Gatekeeper sends out ${nxt.name} Lv${nxt.lv}!`); renderBattle();},900);
   autoSave(); renderBattle(); return;
  } else {
   // completed zone boss
   zoneProgress[currentZone][6]=true;
   player.completedBoss.push(currentZone);
   if(currentZone < ZONES.length-1 && !player.unlocked.includes(currentZone+1)) player.unlocked.push(currentZone+1);
   showToast('Gatekeeper defeated! Next zone unlocked ⬢');
  }
 } else if(battle.kind==='trainer'){
  // mark current trainer node as done (first incomplete trainer)
  const idx=zoneProgress[currentZone].findIndex((v,i)=>!v && NodeTypes[i]==='trainer'); if(idx!==-1) zoneProgress[currentZone][idx]=true;
 } else if(battle.kind==='wild'){
  const idx=zoneProgress[currentZone].findIndex((v,i)=>!v && NodeTypes[i]==='wild'); if(idx!==-1) zoneProgress[currentZone][idx]=true;
 }
 autoSave();
 setTimeout(()=>{ battle=null; switchScreen('overworld'); renderOverworld();},1400);
 renderBattle();
}

function battleAction(a){
 if(!battle) return;
 if(a==='prism'){
  if(battle.kind!=='wild'){ setMsg('Can only harmonize wild Resonants!'); return}
  const hasHyper=player.inventory.hyper>0; const hasPrism=player.inventory.prism>0;
  if(!hasHyper && !hasPrism){ setMsg('No Prisms left! Visit Camp Shop.'); return}
  const useHyper = hasHyper && (battle.enemy.hp/battle.enemy.maxHp <0.4 || !hasPrism);
  const tier = useHyper?'hyper':'prism';
  if(tier==='hyper') player.inventory.hyper--; else player.inventory.prism--;
  const hpPct=battle.enemy.hp/battle.enemy.maxHp;
  let rate = (1-hpPct)*0.65 + 0.15;
  if(tier==='hyper') rate+=0.30;
  if(rate>0.95) rate=0.95;
  const success=Math.random()<rate;
  setMsg(`Threw ${tier==='hyper'?'Hyper Prism ⬣':'Prism ⬢'}...`);
  synth.select();
  setTimeout(()=>{
   if(success){
    // capture
    const caught=createMon(battle.enemy.name, battle.enemy.level);
    player.dex.add(caught.name);
    if(player.party.length<3){ player.party.push(caught); showToast(`Captured ${caught.name} Lv${caught.level}! Added to party.`)}
    else { player.box.push(caught); showToast(`Captured ${caught.name}! Sent to Box (party full).`)}
    // mark node done
    const idx=zoneProgress[currentZone].findIndex((v,i)=>!v && NodeTypes[i]==='wild'); if(idx!==-1) zoneProgress[currentZone][idx]=true;
    synth.capture(); autoSave();
    setTimeout(()=>{battle=null; switchScreen('overworld'); renderOverworld();},800);
   } else {
    setMsg('The Resonant broke free!'); battle.turn='enemy'; setTimeout(enemyTurn,700);
   }
   renderBattle(); autoSave();
  },700);
 }
}
function openExchange(forced=false){
 if(!battle && !forced) return;
 document.getElementById('exchange-modal').classList.remove('hidden');
 const list=document.getElementById('exchange-list');
 const opts=player.party.map((m,i)=>`
  <button onclick="doExchange(${i})" class="w-full flex items-center gap-3 p-2 rounded-xl glass border ${i===battle?.playerIdx?'border-violet-500 bg-violet-600/20':'border-white/10'} ${m.hp<=0?'opacity-40 cursor-not-allowed':''}" ${m.hp<=0?'disabled':''}>
   <div class="w-10 h-10 rounded-xl ${typeColor[m.type]} flex items-center justify-center">${spriteSVG(m.type,m.name,32)}</div>
   <div class="text-left"><div class="text-xs font-bold">${m.name} Lv${m.level} ${m.hp<=0?'(Fainted)':''}</div><div class="text-[11px] text-slate-400">${m.hp}/${m.maxHp} HP</div></div>
   <span class="ml-auto text-xs">${i===battle?.playerIdx?'Active':''}</span>
  </button>`).join('');
 list.innerHTML=opts;
}
function closeExchange(){document.getElementById('exchange-modal').classList.add('hidden')}
function doExchange(i){
 if(player.party[i].hp<=0) return;
 battle.playerIdx=i; closeExchange(); setMsg(`Go, ${player.party[i].name}!`); synth.select(); battle.turn='enemy'; renderBattle(); setTimeout(enemyTurn,700);
}
function useElixir(){
 if(!battle) return;
 if(player.inventory.elixir<=0){setMsg('No Elixirs!'); return}
 const p=player.party[battle.playerIdx];
 const heal=Math.floor(p.maxHp*0.5);
 p.hp=Math.min(p.maxHp, p.hp+heal); player.inventory.elixir--; setMsg(`${p.name} restored ${heal} HP!`); synth.heal(); renderBattle(); autoSave();
 battle.turn='enemy'; setTimeout(enemyTurn,700);
}
function fleeBattle(){
 if(!battle) return;
 player.shards=Math.max(0,player.shards-10);
 setMsg('You fled... -10 ◇');
 setTimeout(()=>{battle=null; switchScreen('overworld'); renderOverworld(); autoSave();},600);
}

// Shop
const SHOP=[
 {id:'prism',name:'Harmonizer Prism',desc:'Standard capture matrix',price:100,icon:'⬢'},
 {id:'hyper',name:'Hyper-Conductive Prism',desc:'High capture rate (+30%)',price:350,icon:'⬣'},
 {id:'elixir',name:'Core Restorer',desc:'Restores 50% HP in battle',price:150,icon:'⚗'},
 {id:'cleanser',name:'Resonance Elixir',desc:'Cleanses status (future)',price:120,icon:'✦'},
];
function openShop(){
 document.getElementById('shop-modal').classList.remove('hidden');
 document.getElementById('shop-grid').innerHTML=SHOP.map(s=>`
  <div class="glass rounded-2xl p-3 flex gap-3 items-center">
   <div class="w-12 h-12 rounded-xl bg-gradient-to-br from-violet-600 to-cyan-500 flex items-center justify-center text-xl">${s.icon}</div>
   <div class="flex-1"><div class="text-sm font-bold">${s.name}</div><div class="text-[11px] text-slate-400">${s.desc}</div><div class="text-xs font-bold mt-1">${s.price} ◇</div></div>
   <button onclick="buy('${s.id}')" class="px-3 py-2 rounded-xl bg-violet-600 text-xs font-bold">Buy</button>
  </div>`).join('');
 updateNav();
}
function closeShop(){document.getElementById('shop-modal').classList.add('hidden')}
function buy(id){
 const item=SHOP.find(s=>s.id===id);
 if(player.shards<item.price){showToast('Not enough Shards'); return}
 player.shards-=item.price;
 if(id==='prism') player.inventory.prism++;
 if(id==='hyper') player.inventory.hyper++;
 if(id==='elixir') player.inventory.elixir++;
 if(id==='cleanser') player.inventory.cleanser++;
 autoSave(); renderInventory(); updateNav(); showToast(`Purchased ${item.name}!`); synth.select();
}

// Dex
function renderDex(){
 const grid=document.getElementById('dex-grid');
 document.getElementById('dex-count').textContent=player?player.dex.size:0;
 grid.innerHTML=RESONANTS.map(r=>{
  const owned=player && player.dex.has(r.name);
  return `<div class="glass rounded-2xl p-4 border ${owned?'border-white/15':'border-white/5 opacity-60'}">
   <div class="flex gap-3">
    <div class="w-[72px] h-[72px] rounded-xl ${typeColor[r.type]} flex items-center justify-center ${!owned?'grayscale':''}">${spriteSVG(r.type,r.name,60)}</div>
    <div><div class="font-bold text-sm">${r.id.toString().padStart(2,'0')} ${r.name} <span class="text-[10px] px-1.5 py-0.5 rounded-full bg-white/15">${r.type}</span></div><div class="text-[11px] text-slate-400">Evo: ${r.next? r.next+' @ Lv'+r.evo : 'Final'}</div><div class="text-[11px] text-slate-300 mt-1">HP ${r.base.hp} ATK ${r.base.atk} DEF ${r.base.def} SPD ${r.base.spd}</div></div>
   </div>
   <div class="mt-3 flex flex-wrap gap-1">${r.moves.map(m=>`<span class="text-[10px] px-2 py-1 rounded-full ${typeColor[m.type]} ${m.type==='Volt'?'text-slate-900':''}">${m.name} · ${m.power}</span>`).join('')}</div>
   ${owned?'<div class="text-[11px] text-emerald-400 mt-2">● Harmonized</div>':'<div class="text-[11px] text-slate-500 mt-2">○ Unseen</div>'}
  </div>`
 }).join('');
}

// Init
(function init(){
 if(load()){ currentZone=player.unlocked[player.unlocked.length-1]||0; switchScreen('overworld'); }
 else { switchScreen('menu'); renderMenu(); }
 updateNav();
})();

// expose
window.switchScreen=switchScreen; window.startWith=startWith; window.continueGame=continueGame; window.showStarterPick=showStarterPick; window.resetSave=resetSave; window.selectZone=selectZone; window.clickNode=clickNode; window.saveGame=saveGame; window.openShop=openShop; window.closeShop=closeShop; window.buy=buy; window.playerMove=playerMove; window.battleAction=battleAction; window.openExchange=openExchange; window.closeExchange=closeExchange; window.doExchange=doExchange; window.useElixir=useElixir; window.fleeBattle=fleeBattle;
</script>
</body>
</html>
<!-- agent-meta {"model":"meta/muse-spark-1.2","provider":"openrouter","persona":"basic","sessionId":"4e29b0a8-578b-49f2-9e6f-37bac8fffe1e","tokensIn":1797,"tokensOut":18651,"tokensTotal":20448,"cost":0.0813887,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-08-06T15:35:10.787Z"} -->