← Aethelgard results

Aethelgard

longcat 2.0 · full

Initial view of Aethelgard

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

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 121,957 bytes · SHA-256 9af9ecdfc04c
<!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=Press+Start+2P&family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
/* ========== RESET & BASE ========== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; font-family: 'Inter', sans-serif; background: #0a0a14; color: #e2e8f0; }
.font-pixel { font-family: 'Press Start 2P', monospace; }

/* ========== BACKGROUND ========== */
.bg-game {
  background: radial-gradient(ellipse at 20% 50%, rgba(88, 28, 135, 0.25) 0%, transparent 60%),
              radial-gradient(ellipse at 80% 20%, rgba(15, 118, 110, 0.2) 0%, transparent 50%),
              radial-gradient(ellipse at 50% 80%, rgba(124, 58, 237, 0.15) 0%, transparent 60%),
              #0a0a14;
}
.bg-stars {
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
}
.bg-stars::before, .bg-stars::after {
  content: ''; position: absolute; inset: 0;
  background-image:
    radial-gradient(1px 1px at 10% 20%, rgba(255,255,255,0.6) 0%, transparent 100%),
    radial-gradient(1px 1px at 30% 70%, rgba(255,255,255,0.4) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 50% 10%, rgba(255,255,255,0.5) 0%, transparent 100%),
    radial-gradient(1px 1px at 70% 50%, rgba(255,255,255,0.3) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 90% 80%, rgba(255,255,255,0.5) 0%, transparent 100%),
    radial-gradient(1px 1px at 15% 90%, rgba(255,255,255,0.3) 0%, transparent 100%),
    radial-gradient(1px 1px at 85% 30%, rgba(255,255,255,0.4) 0%, transparent 100%),
    radial-gradient(1px 1px at 45% 45%, rgba(255,255,255,0.2) 0%, transparent 100%);
}
.bg-stars::after { animation: twinkle 4s ease-in-out infinite alternate; }
@keyframes twinkle { 0% { opacity: 0.5; } 100% { opacity: 1; } }

/* ========== GLASSMORPHISM ========== */
.glass {
  background: rgba(15, 15, 30, 0.7);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 16px;
}
.glass-light {
  background: rgba(30, 30, 60, 0.5);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
}

/* ========== ELEMENT BORDERS & GLOW ========== */
.el-pyro { --el: rgba(255,107,53,0.7); border-color: var(--el) !important; box-shadow: 0 0 15px rgba(255,107,53,0.3), inset 0 0 15px rgba(255,107,53,0.1); }
.el-hydro { --el: rgba(0,188,212,0.7); border-color: var(--el) !important; box-shadow: 0 0 15px rgba(0,188,212,0.3), inset 0 0 15px rgba(0,188,212,0.1); }
.el-flora { --el: rgba(76,175,80,0.7); border-color: var(--el) !important; box-shadow: 0 0 15px rgba(76,175,80,0.3), inset 0 0 15px rgba(76,175,80,0.1); }
.el-zephyr { --el: rgba(129,212,250,0.7); border-color: var(--el) !important; box-shadow: 0 0 15px rgba(129,212,250,0.3), inset 0 0 15px rgba(129,212,250,0.1); }
.el-earth { --el: rgba(141,110,99,0.7); border-color: var(--el) !important; box-shadow: 0 0 15px rgba(141,110,99,0.3), inset 0 0 15px rgba(141,110,99,0.1); }
.el-volt { --el: rgba(255,235,59,0.7); border-color: var(--el) !important; box-shadow: 0 0 15px rgba(255,235,59,0.3), inset 0 0 15px rgba(255,235,59,0.1); }
.el-cosmos { --el: rgba(206,147,216,0.7); border-color: var(--el) !important; box-shadow: 0 0 15px rgba(206,147,216,0.3), inset 0 0 15px rgba(206,147,216,0.1); }

.glow-pyro { box-shadow: 0 0 20px rgba(255,107,53,0.5), 0 0 40px rgba(255,107,53,0.2); }
.glow-hydro { box-shadow: 0 0 20px rgba(0,188,212,0.5), 0 0 40px rgba(0,188,212,0.2); }
.glow-flora { box-shadow: 0 0 20px rgba(76,175,80,0.5), 0 0 40px rgba(76,175,80,0.2); }
.glow-zephyr { box-shadow: 0 0 20px rgba(129,212,250,0.5), 0 0 40px rgba(129,212,250,0.2); }
.glow-earth { box-shadow: 0 0 20px rgba(141,110,99,0.5), 0 0 40px rgba(141,110,99,0.2); }
.glow-volt { box-shadow: 0 0 20px rgba(255,235,59,0.5), 0 0 40px rgba(255,235,59,0.2); }
.glow-cosmos { box-shadow: 0 0 20px rgba(206,147,216,0.5), 0 0 40px rgba(206,147,216,0.2); }

.text-pyro { color: #FF6B35; } .text-hydro { color: #00BCD4; } .text-flora { color: #4CAF50; }
.text-zephyr { color: #81D4FA; } .text-earth { color: #A1887F; } .text-volt { color: #FFEB3B; } .text-cosmos { color: #CE93D8; }

/* ========== ANIMATIONS ========== */
@keyframes bounce-mon {
  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(-5px); }
  80% { transform: translateX(5px); }
}
@keyframes flash {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-60px); } to { opacity: 1; transform: translateX(0); } }
@keyframes slideInRight { from { opacity: 0; transform: translateX(60px); } to { opacity: 1; transform: translateX(0); } }
@keyframes slideInUp { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes pulse-glow {
  0%, 100% { box-shadow: 0 0 20px var(--glow, rgba(168,85,247,0.5)); }
  50% { box-shadow: 0 0 40px var(--glow, rgba(168,85,247,0.8)); }
}
@keyframes bar-fill { from { width: 0%; } }
@keyframes float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-15px) rotate(5deg); }
}
@keyframes particle-rise {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to { opacity: 0; transform: translateY(-80px) scale(0.3); }
}
@keyframes levelUp {
  0% { transform: scale(1); filter: brightness(1); }
  50% { transform: scale(1.3); filter: brightness(2); }
  100% { transform: scale(1); filter: brightness(1); }
}
@keyframes capture-shake {
  0%, 100% { transform: rotate(0deg); }
  25% { transform: rotate(-15deg); }
  75% { transform: rotate(15deg); }
}
@keyframes capture-glow {
  0%, 100% { opacity: 0.5; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.1); }
}

.anim-bounce { animation: bounce-mon 2s ease-in-out infinite; }
.anim-shake { animation: shake 0.4s ease-in-out; }
.anim-flash { animation: flash 0.3s ease-in-out 2; }
.anim-slide-left { animation: slideInLeft 0.5s ease-out; }
.anim-slide-right { animation: slideInRight 0.5s ease-out; }
.anim-slide-up { animation: slideInUp 0.4s ease-out; }
.anim-fade { animation: fadeIn 0.4s ease-out; }
.anim-float { animation: float 4s ease-in-out infinite; }
.anim-pulse-glow { animation: pulse-glow 2s ease-in-out infinite; }
.anim-levelup { animation: levelUp 0.8s ease-out; }
.anim-capture { animation: capture-shake 0.6s ease-in-out infinite; }
.anim-capture-glow { animation: capture-glow 1.2s ease-in-out infinite; }

/* ========== TRANSITIONS ========== */
.screen { display: none; opacity: 0; transition: opacity 0.4s ease, transform 0.4s ease; transform: scale(0.97); }
.screen.active { display: flex; opacity: 1; transform: scale(1); }
.screen.slide-out-left { animation: slideOutLeft 0.3s forwards; }
.screen.slide-out-right { animation: slideOutRight 0.3s forwards; }
@keyframes slideOutLeft { to { opacity: 0; transform: translateX(-100px); } }
@keyframes slideOutRight { to { opacity: 0; transform: translateX(100px); } }

/* ========== BUTTONS ========== */
.btn-game {
  position: relative; padding: 10px 24px; border: 1px solid rgba(255,255,255,0.15);
  border-radius: 10px; font-family: 'Inter', sans-serif; font-weight: 600;
  font-size: 13px; cursor: pointer; transition: all 0.2s;
  background: rgba(30,30,60,0.6); color: #e2e8f0;
  display: inline-flex; align-items: center; gap: 8px;
}
.btn-game:hover { background: rgba(50,50,100,0.7); border-color: rgba(255,255,255,0.3); transform: translateY(-1px); }
.btn-game:active { transform: translateY(1px); }
.btn-game:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary { background: linear-gradient(135deg, rgba(139,92,246,0.5), rgba(168,85,247,0.3)); border-color: rgba(168,85,257,0.5); }
.btn-primary:hover { background: linear-gradient(135deg, rgba(139,92,246,0.7), rgba(168,85,247,0.5)); }
.btn-danger { background: linear-gradient(135deg, rgba(239,68,68,0.4), rgba(220,38,38,0.2)); border-color: rgba(239,68,68,0.4); }
.btn-success { background: linear-gradient(135deg, rgba(34,197,94,0.4), rgba(16,185,129,0.2)); border-color: rgba(34,197,94,0.4); }

/* ========== HP BAR ========== */
.hp-bar-track { height: 10px; border-radius: 5px; background: rgba(0,0,0,0.4); overflow: hidden; position: relative; border: 1px solid rgba(255,255,255,0.1); }
.hp-bar-fill { height: 100%; border-radius: 4px; transition: width 0.6s ease, background-color 0.3s; background: linear-gradient(90deg, #4CAF50, #8BC34A); }
.hp-bar-fill.warn { background: linear-gradient(90deg, #FDD835, #FFB300); }
.hp-bar-fill.danger { background: linear-gradient(90deg, #EF5350, #E53935); }
.xp-bar-track { height: 6px; border-radius: 3px; background: rgba(0,0,0,0.4); overflow: hidden; }
.xp-bar-fill { height: 100%; background: linear-gradient(90deg, #2196F3, #00BCD4); border-radius: 2px; transition: width 0.6s ease; }

/* ========== NODE MAP ========== */
.node {
  width: 56px; height: 56px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: all 0.3s; border: 2px solid rgba(255,255,255,0.2);
  background: rgba(20,20,45,0.8); position: relative; z-index: 2;
}
.node:hover:not(.locked):not(.completed) { transform: scale(1.15); border-color: rgba(168,85,247,0.7); }
.node.completed { border-color: #4CAF50; background: rgba(76,175,80,0.2); }
.node.locked { opacity: 0.3; cursor: not-allowed; border-color: rgba(255,255,255,0.1); }
.node.current { border-color: #A855F7; box-shadow: 0 0 20px rgba(168,85,247,0.5); transform: scale(1.1); }
.node.boss-node { width: 64px; height: 64px; }
.node svg { width: 28px; height: 28px; }

.node-connector {
  position: absolute; background: rgba(168,85,247,0.3); height: 3px; z-index: 1;
  transform-origin: left center;
}
.node-connector.active { background: rgba(76,175,80,0.6); }

/* ========== BATTLE BG ========== */
.battle-bg {
  background: radial-gradient(ellipse at 50% 100%, rgba(168,85,247,0.15) 0%, transparent 60%),
              radial-gradient(ellipse at 30% 30%, rgba(15,118,110,0.2) 0%, transparent 50%),
              linear-gradient(180deg, #1a1a2e 0%, #0f0f1a 100%);
}

/* ========== PARTICLES ========== */
.particle {
  position: absolute; pointer-events: none; animation: particle-rise 1.2s ease-out forwards;
  border-radius: 50%;
}

/* ========== TOOLTIP ========== */
.tooltip {
  position: absolute; background: rgba(10,10,25,0.95); border: 1px solid rgba(255,255,255,0.15);
  border-radius: 8px; padding: 8px 12px; font-size: 12px; z-index: 100;
  pointer-events: none; white-space: nowrap; backdrop-filter: blur(8px);
}

/* ========== SCROLLBAR ========== */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); }
::-webkit-scrollbar-thumb { background: rgba(168,85,247,0.4); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(168,85,247,0.6); }

/* ========== DEX GRID ========== */
.dex-card { transition: all 0.3s; }
.dex-card:hover { transform: translateY(-4px); }
.dex-card.undiscovered { filter: brightness(0.2); }

/* ========== NOTIFICATION ========== */
.notification { animation: slideInUp 0.4s ease, fadeIn 0.4s ease; }

/* ========== BADGES ========== */
.badge {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 2px 8px; border-radius: 6px; font-size: 10px; font-weight: 700; letter-spacing: 0.5px;
}
.badge-pyro { background: rgba(255,107,53,0.2); color: #FF6B35; border: 1px solid rgba(255,107,53,0.4); }
.badge-hydro { background: rgba(0,188,212,0.2); color: #00BCD4; border: 1px solid rgba(0,188,212,0.4); }
.badge-flora { background: rgba(76,175,80,0.2); color: #4CAF50; border: 1px solid rgba(76,175,80,0.4); }
.badge-zephyr { background: rgba(129,212,250,0.2); color: #81D4FA; border: 1px solid rgba(129,212,250,0.4); }
.badge-earth { background: rgba(141,110,99,0.2); color: #A1887F; border: 1px solid rgba(141,110,99,0.4); }
.badge-volt { background: rgba(255,235,59,0.2); color: #FFEB3B; border: 1px solid rgba(255,235,59,0.4); }
.badge-cosmos { background: rgba(206,147,216,0.2); color: #CE93D8; border: 1px solid rgba(206,147,216,0.4); }

/* ========== STATUS ICON ========== */
.status-icon { width: 18px; height: 18px; border-radius: 4px; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; }
.status-burn { background: rgba(255,107,53,0.3); color: #FF6B35; }
.status-wet { background: rgba(0,188,212,0.3); color: #00BCD4; }
.status-spore { background: rgba(76,175,80,0.3); color: #4CAF50; }
.status-stun { background: rgba(255,235,59,0.3); color: #FFEB3B; }
</style>
</head>
<body class="bg-game min-h-screen flex items-center justify-center">

<div class="bg-stars"></div>

<!-- ==================== APP CONTAINER ==================== -->
<div id="app" class="relative w-full max-w-[1200px] h-[95vh] mx-auto flex flex-col" style="z-index:1;">

  <!-- TOP BAR (visible during game) -->
  <div id="topbar" class="hidden flex items-center justify-between px-5 py-3 mb-2 fade-in">
    <div class="flex items-center gap-3">
      <button onclick="goHome()" class="btn-game !px-3 !py-2 text-xs">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/></svg>
        Camp
      </button>
      <span id="zone-label" class="font-pixel text-[10px] text-purple-300"></span>
    </div>
    <div class="flex items-center gap-4">
      <div class="flex items-center gap-2 glass-light px-3 py-1.5 rounded-lg">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#A855F7" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
        <span id="shards-display" class="font-pixel text-[10px] text-purple-300">0</span>
      </div>
      <button onclick="openShop()" class="btn-game !px-3 !py-2 text-xs">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>
        Shop
      </button>
      <button onclick="openDex()" class="btn-game !px-3 !py-2 text-xs">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
        Dex
      </button>
      <button onclick="openParty()" class="btn-game !px-3 !py-2 text-xs">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4-4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
        Party
      </button>
    </div>
  </div>

  <!-- ==================== MAIN MENU ==================== -->
  <div id="screen-menu" class="screen active flex-col items-center justify-center gap-8 absolute inset-0">
    <div class="text-center">
      <h1 class="font-pixel text-3xl md:text-4xl mb-3" style="color: #A855F7; text-shadow: 0 0 30px rgba(168,85,247,0.6);">AETHELGARD</h1>
      <p class="font-pixel text-[10px] text-purple-300 tracking-widest">RESONANT CONDUCTOR</p>
    </div>
    <div class="anim-float flex justify-center my-4">
      <svg width="180" height="180" viewBox="0 0 180 180">
        <defs>
          <radialGradient id="logo-bg" cx="50%" cy="50%" r="50%">
            <stop offset="0%" style="stop-color:rgba(168,85,247,0.4)"/>
            <stop offset="100%" style="stop-color:rgba(139,92,246,0)"/>
          </radialGradient>
          <linearGradient id="logo-outline" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" style="stop-color:#A855F7"/>
            <stop offset="100%" style="stop-color:#00BCD4"/>
          </linearGradient>
        </defs>
        <circle cx="90" cy="90" r="70" fill="url(#logo-bg)"/>
        <polygon points="90,25 135,50 135,110 90,150 45,110 45,50" fill="none" stroke="url(#logo-outline)" stroke-width="2.5" opacity="0.9"/>
        <polygon points="90,40 120,58 120,102 90,128 60,102 60,58" fill="rgba(168,85,247,0.15)" stroke="url(#logo-outline)" stroke-width="1.5" opacity="0.7"/>
        <circle cx="90" cy="80" r="15" fill="rgba(168,85,247,0.3)" stroke="#A855F7" stroke-width="1"/>
        <circle cx="90" cy="80" r="6" fill="#A855F7"/>
        <line x1="90" y1="25" x2="90" y2="55" stroke="url(#logo-outline)" stroke-width="1.5" opacity="0.6"/>
        <line x1="135" y1="50" x2="115" y2="65" stroke="url(#logo-outline)" stroke-width="1.5" opacity="0.6"/>
        <line x1="45" y1="50" x2="65" y2="65" stroke="url(#logo-outline)" stroke-width="1.5" opacity="0.6"/>
      </svg>
    </div>
    <div class="flex flex-col gap-3 w-64">
      <button onclick="startNewGame()" class="btn-game btn-primary w-full justify-center py-3 font-pixel text-[10px]">NEW JOURNEY</button>
      <button onclick="continueGame()" id="btn-continue" class="btn-game w-full justify-center py-3 font-pixel text-[10px] opacity-40" disabled>CONTINUE</button>
      <button onclick="openSettings()" class="btn-game w-full justify-center py-3 font-pixel text-[10px]">OPTIONS</button>
    </div>
    <p class="text-xs text-gray-500 mt-4">A world of elemental resonance awaits</p>
  </div>

  <!-- ==================== ZONE SELECT ==================== -->
  <div id="screen-zoneselect" class="screen flex-col items-center justify-center px-6 absolute inset-0">
    <h2 class="font-pixel text-lg text-purple-300 mb-2 anim-slide-up">SELECT ZONE</h2>
    <p class="text-sm text-gray-400 mb-6">Choose your path, Conductor.</p>
    <div id="zone-grid" class="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full max-w-2xl"></div>
    <button onclick="showScreen('screen-menu')" class="btn-game mt-6"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> Back</button>
  </div>

  <!-- ==================== OVERWORLD MAP ==================== -->
  <div id="screen-overworld" class="screen flex-col items-center px-4 py-2 absolute inset-0">
    <div class="w-full max-w-3xl flex items-center justify-between mb-4">
      <button onclick="showScreen('screen-zoneselect')" class="btn-game !px-3 !py-2 text-xs">← Zones</button>
      <h3 id="map-title" class="font-pixel text-[11px] text-purple-300"></h3>
      <div class="text-xs text-gray-400" id="map-progress"></div>
    </div>
    <div id="node-map" class="glass flex-1 w-full max-w-3xl relative overflow-hidden rounded-2xl border border-white/10"></div>
  </div>

  <!-- ==================== BATTLE SCREEN ==================== -->
  <div id="screen-battle" class="screen flex-col absolute inset-0">
    <div class="battle-bg w-full flex-1 flex flex-col items-center justify-between px-4 py-3 relative overflow-hidden">
      <!-- Battle field -->
      <div class="w-full max-w-3xl flex justify-between items-start mt-2">
        <!-- Player's monster -->
        <div class="anim-slide-left flex items-center gap-3" id="player-mon-side">
          <div id="player-sprite-wrap" class="anim-bounce"></div>
          <div class="glass-light p-3 rounded-xl min-w-[200px]">
            <div class="flex items-center justify-between mb-1">
              <span id="player-mon-name" class="font-semibold text-sm"></span>
              <span id="player-mon-level" class="text-xs text-gray-400">Lv.1</span>
            </div>
            <div class="hp-bar-track mb-1">
              <div id="player-mon-hp-bar" class="hp-bar-fill"></div>
            </div>
            <div class="flex justify-between text-[10px] text-gray-400 mb-2">
              <span id="player-mon-hp-text">HP: 0/0</span>
            </div>
            <div class="xp-bar-track">
              <div id="player-mon-xp-bar" class="xp-bar-fill"></div>
            </div>
            <div id="player-mon-status" class="flex gap-1 mt-1"></div>
          </div>
        </div>
        <!-- Enemy's monster -->
        <div class="anim-slide-right flex items-center gap-3" id="enemy-mon-side">
          <div class="glass-light p-3 rounded-xl min-w-[200px]">
            <div class="flex items-center justify-between mb-1">
              <span id="enemy-mon-name" class="font-semibold text-sm"></span>
              <span id="enemy-mon-level" class="text-xs text-gray-400">Lv.1</span>
            </div>
            <div class="hp-bar-track mb-1">
              <div id="enemy-mon-hp-bar" class="hp-bar-fill"></div>
            </div>
            <div class="flex justify-between text-[10px] text-gray-400 mb-1">
              <span id="enemy-mon-hp-text">HP: 0/0</span>
            </div>
            <div id="enemy-mon-status" class="flex gap-1 mt-1"></div>
          </div>
          <div id="enemy-sprite-wrap" class="anim-bounce"></div>
        </div>
      </div>

      <!-- Particle layer -->
      <div id="battle-particles" class="absolute inset-0 pointer-events-none z-10"></div>

      <!-- Battle log / bottom UI -->
      <div class="w-full max-w-3xl glass rounded-xl p-4 mb-2 relative z-20">
        <div id="battle-log" class="text-sm text-gray-300 min-h-[40px] mb-3 px-2"></div>
        <div id="battle-actions" class="grid grid-cols-2 md:grid-cols-4 gap-2"></div>
      </div>
    </div>
  </div>

  <!-- ==================== PARTY SCREEN ==================== -->
  <div id="screen-party" class="screen flex-col items-center px-6 py-4 absolute inset-0 overflow-y-auto">
    <div class="w-full max-w-3xl">
      <div class="flex items-center justify-between mb-6">
        <button onclick="goHome()" class="btn-game !px-3 !py-2 text-xs"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> Back</button>
        <h2 class="font-pixel text-sm text-purple-300">YOUR PARTY</h2>
        <div></div>
      </div>
      <div id="party-grid" class="grid grid-cols-1 md:grid-cols-3 gap-4"></div>
      <div class="mt-6">
        <h3 class="font-pixel text-[11px] text-purple-300 mb-3">STORAGE BOX</h3>
        <div id="storage-grid" class="grid grid-cols-3 md:grid-cols-6 gap-2"></div>
      </div>
    </div>
  </div>

  <!-- ==================== SHOP ==================== -->
  <div id="screen-shop" class="screen flex-col items-center px-6 py-4 absolute inset-0 overflow-y-auto">
    <div class="w-full max-w-2xl">
      <div class="flex items-center justify-between mb-6">
        <button onclick="goHome()" class="btn-game !px-3 !py-2 text-xs"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> Back</button>
        <h2 class="font-pixel text-sm text-purple-300">CAMP SHOP</h2>
        <div class="glass-light px-3 py-1.5 rounded-lg flex items-center gap-2">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#A855F7" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
          <span id="shop-shards" class="font-pixel text-[10px] text-purple-300">0</span>
        </div>
      </div>
      <div id="shop-items" class="grid grid-cols-1 sm:grid-cols-2 gap-4"></div>
    </div>
  </div>

  <!-- ==================== DEX ==================== -->
  <div id="screen-dex" class="screen flex-col items-center px-6 py-4 absolute inset-0 overflow-y-auto">
    <div class="w-full max-w-4xl">
      <div class="flex items-center justify-between mb-6">
        <button onclick="goHome()" class="btn-game !px-3 !py-2 text-xs"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> Back</button>
        <h2 class="font-pixel text-sm text-purple-300">CONDUCTOR'S DEX</h2>
        <div id="dex-count" class="text-xs text-gray-400 font-pixel text-[9px]"></div>
      </div>
      <!-- Dex detail popup -->
      <div id="dex-detail" class="hidden mb-4"></div>
      <div id="dex-grid" class="grid grid-cols-3 md:grid-cols-6 gap-3"></div>
    </div>
  </div>

  <!-- ==================== SETTINGS ==================== -->
  <div id="screen-settings" class="screen flex-col items-center justify-center px-6 absolute inset-0">
    <div class="glass p-8 rounded-2xl max-w-md w-full">
      <h2 class="font-pixel text-sm text-purple-300 mb-6 text-center">OPTIONS</h2>
      <div class="space-y-4">
        <div class="flex items-center justify-between">
          <span class="text-sm">Sound Effects</span>
          <label class="relative inline-flex items-center cursor-pointer">
            <input type="checkbox" id="toggle-sfx" checked class="sr-only peer">
            <div class="w-10 h-5 bg-gray-700 peer-checked:bg-purple-600 rounded-full after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:after:translate-x-full"></div>
          </label>
        </div>
        <div class="flex items-center justify-between">
          <span class="text-sm">Music</span>
          <label class="relative inline-flex items-center cursor-pointer">
            <input type="checkbox" id="toggle-music" checked class="sr-only peer">
            <div class="w-10 h-5 bg-gray-700 peer-checked:bg-purple-600 rounded-full after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:after:translate-x-full"></div>
          </label>
        </div>
        <button onclick="resetProgress()" class="btn-game btn-danger w-full justify-center mt-6 text-xs">RESET ALL PROGRESS</button>
        <button onclick="showScreen('screen-menu')" class="btn-game w-full justify-center text-xs">BACK</button>
      </div>
    </div>
  </div>

  <!-- ==================== NOTIFICATION ==================== -->
  <div id="notification" class="hidden fixed top-4 left-1/2 -translate-x-1/2 z-50 glass px-5 py-3 rounded-xl border border-purple-500/30 text-sm"></div>

  <!-- ==================== LEVEL UP MODAL ==================== -->
  <div id="levelup-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
    <div class="glass p-8 rounded-2xl text-center max-w-sm">
      <div id="levelup-content"></div>
    </div>
  </div>

</div><!-- #app -->

<!-- Pick starter overlay -->
<div id="screen-starter" class="screen flex-col items-center justify-center px-6 absolute inset-0 bg-game" style="z-index:5;">
  <!-- Dynamically populated -->
</div>

<!-- Battle result overlay -->
<div id="battle-result-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
  <div class="glass p-8 rounded-2xl text-center max-w-sm">
    <div id="battle-result-content"></div>
  </div>
</div>
<script>
// ============================================================
// AETHELGARD — RESONANT CONDUCTOR
// Single-file turn-based monster collection RPG
// ============================================================

// ========== AUDIO SYNTH MANAGER ==========
class SynthManager {
  constructor() {
    this.ctx = null;
    this.sfxEnabled = true;
    this.musicEnabled = true;
    this.musicNodes = [];
    this.musicInterval = null;
  }

  init() {
    if (this.ctx) return;
    try {
      this.ctx = new (window.AudioContext || window.webkitAudioContext)();
    } catch (e) {
      console.warn('AudioContext not available');
    }
  }

  resume() {
    if (this.ctx && this.ctx.state === 'suspended') this.ctx.resume();
  }

  _tone(freq, duration, type = 'square', vol = 0.08, slide = 0) {
    if (!this.ctx || !this.sfxEnabled) return;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    osc.type = type;
    osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
    if (slide) osc.frequency.linearRampToValueAtTime(freq + slide, this.ctx.currentTime + duration);
    gain.gain.setValueAtTime(vol, this.ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + duration);
    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.start(this.ctx.currentTime);
    osc.stop(this.ctx.currentTime + duration);
  }

  _noise(duration, vol = 0.05) {
    if (!this.ctx || !this.sfxEnabled) return;
    const bufferSize = this.ctx.sampleRate * duration;
    const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 2);
    const source = this.ctx.createBufferSource();
    source.buffer = buffer;
    const gain = this.ctx.createGain();
    gain.gain.setValueAtTime(vol, this.ctx.currentTime);
    source.connect(gain);
    gain.connect(this.ctx.destination);
    source.start();
  }

  hover() { this._tone(600, 0.06, 'sine', 0.04); }
  select() { this._tone(800, 0.08, 'square', 0.06); this._tone(1000, 0.08, 'square', 0.04); }
  hitPhysical() { this._noise(0.12, 0.08); this._tone(200, 0.1, 'sawtooth', 0.06, -100); }
  hitElemental() { this._tone(400, 0.15, 'sine', 0.07, 200); this._tone(600, 0.15, 'triangle', 0.05, 100); this._noise(0.1, 0.06); }
  hitSuper() { this._tone(300, 0.2, 'sine', 0.08, 400); this._tone(600, 0.2, 'triangle', 0.06, 200); this._noise(0.15, 0.08); }
  miss() { this._tone(150, 0.15, 'sine', 0.04, -50); }
  levelUp() {
    const notes = [523, 659, 784, 1047];
    notes.forEach((n, i) => setTimeout(() => this._tone(n, 0.2, 'square', 0.06), i * 100));
  }
  captureStart() {
    this._tone(800, 0.1, 'triangle', 0.05);
    setTimeout(() => this._tone(1000, 0.1, 'triangle', 0.05), 120);
    setTimeout(() => this._tone(1200, 0.15, 'triangle', 0.05), 240);
  }
  captureSuccess() {
    const notes = [523, 587, 659, 698, 784, 1047];
    notes.forEach((n, i) => setTimeout(() => this._tone(n, 0.25, 'square', 0.06), i * 80));
  }
  captureFail() {
    this._tone(600, 0.2, 'sawtooth', 0.05, -200);
    setTimeout(() => this._tone(300, 0.3, 'sawtooth', 0.04, -100), 200);
  }
  heal() {
    const notes = [440, 554, 659, 554, 659, 880];
    notes.forEach((n, i) => setTimeout(() => this._tone(n, 0.18, 'sine', 0.05), i * 80));
  }
  error() { this._tone(200, 0.15, 'sawtooth', 0.06, -80); }
  victory() {
    const notes = [523, 659, 784, 1047, 784, 1047];
    notes.forEach((n, i) => setTimeout(() => this._tone(n, 0.2, 'square', 0.07), i * 120));
  }
  runAway() { this._tone(600, 0.1, 'sine', 0.05, -300); this._tone(300, 0.15, 'sine', 0.04, -200); }

  startMusic() {
    if (!this.ctx || !this.musicEnabled || this.musicInterval) return;
    const melody = [262, 294, 330, 392, 330, 294, 349, 392, 349, 330, 294, 262, 330, 392, 440, 392];
    let note = 0;
    const beat = () => {
      if (!this.musicEnabled) return;
      const n = melody[note % melody.length];
      this._tone(n, 0.3, 'sine', 0.03);
      this._tone(n / 2, 0.3, 'triangle', 0.02);
      note++;
    };
    beat();
    this.musicInterval = setInterval(beat, 450);
  }

  stopMusic() {
    if (this.musicInterval) { clearInterval(this.musicInterval); this.musicInterval = null; }
  }
}
const synth = new SynthManager();

// ========== TYPE EFFECTIVENESS ==========
const TYPE_CHART = {
  pyro:  { strong: ['flora'], weak: ['hydro'] },
  hydro: { strong: ['pyro'], weak: ['flora'] },
  flora: { strong: ['hydro'], weak: ['pyro'] },
  zephyr: { strong: ['earth'], weak: ['volt'] },
  earth: { strong: ['volt'], weak: ['zephyr'] },
  volt: { strong: ['zephyr'], weak: ['earth'] },
  cosmos: { strong: [], weak: [] }
};

function getTypeMultiplier(attackType, defenderTypes) {
  let mult = 1;
  for (const dt of defenderTypes) {
    if (TYPE_CHART[attackType]?.strong?.includes(dt)) mult *= 2;
    if (TYPE_CHART[attackType]?.weak?.includes(dt)) mult *= 0.5;
  }
  return mult;
}

// Badge class helper
function elBadgeClass(el) { return `badge-${el}`; }
function elBorderClass(el) { return `el-${el}`; }
function elGlowClass(el) { return `glow-${el}`; }
function elTextClass(el) { return `text-${el}`; }

// Color palettes per element for SVG rendering
const EL_COLORS = {
  pyro:  { primary: '#FF6B35', secondary: '#FF3D00', accent: '#FFD54F', glow: '#FF6B35' },
  hydro: { primary: '#00BCD4', secondary: '#0097A7', accent: '#B2EBF2', glow: '#00BCD4' },
  flora: { primary: '#4CAF50', secondary: '#388E3C', accent: '#C8E6C9', glow: '#4CAF50' },
  zephyr: { primary: '#81D4FA', secondary: '#29B6F6', accent: '#E1F5FE', glow: '#81D4FA' },
  earth: { primary: '#A1887F', secondary: '#795548', accent: '#D7CCC8', glow: '#A1887F' },
  volt:  { primary: '#FFEB3B', secondary: '#FBC02D', accent: '#FFF9C4', glow: '#FFEB3B' },
  cosmos: { primary: '#CE93D8', secondary: '#AB47BC', accent: '#F3E5F5', glow: '#CE93D8' }
};

// ============================================================
// RESONANTS DATABASE (18 creatures)
// ============================================================
// Each has: id, name, element, tier, baseStats, moves[], evolution, description

const MOVES_DB = [
  // Universal moves
  { id: 'tackle', name: 'Tackle', power: 35, accuracy: 90, type: 'cosmos', category: 'physical', desc: 'A basic body blow.', effect: null, level: 1 },
  { id: 'rush', name: 'Quick Rush', power: 40, accuracy: 95, type: 'cosmos', category: 'physical', desc: 'A swift tackle attack.', effect: null, level: 2 },
  { id: 'charge', name: 'Power Charge', power: 55, accuracy: 85, type: 'cosmos', category: 'physical', desc: 'Gathers energy for a strong hit.', effect: null, level: 5 },
  { id: 'crush', name: 'Skull Crush', power: 65, accuracy: 80, type: 'cosmos', category: 'physical', desc: 'A devastating close-range slam.', effect: null, level: 10 },
  { id: 'focus', name: 'Focus Energy', power: 0, accuracy: 100, type: 'cosmos', category: 'support', desc: 'Sharply raises attack.', effect: { stat: 'attack', stages: 2 }, level: 4 },
  { id: 'shield', name: 'Iron Guard', power: 0, accuracy: 100, type: 'cosmos', category: 'support', desc: 'Raises defense.', effect: { stat: 'defense', stages: 2 }, level: 4 },

  // Pyro moves
  { id: 'ember', name: 'Ember', power: 40, accuracy: 100, type: 'pyro', category: 'special', desc: 'Tongue of searing flame.', effect: { status: 'burn', chance: 0.3 }, level: 1 },
  { id: 'flame-jet', name: 'Flame Jet', power: 55, accuracy: 90, type: 'pyro', category: 'special', desc: 'A concentrated jet of fire.', effect: { status: 'burn', chance: 0.2 }, level: 5 },
  { id: 'inferno', name: 'Inferno Burst', power: 80, accuracy: 75, type: 'pyro', category: 'special', desc: 'Engulfing infernal flames.', effect: { status: 'burn', chance: 0.4 }, level: 12 },
  { id: 'pyroclasm', name: 'Pyroclasm', power: 95, accuracy: 70, type: 'pyro', category: 'special', desc: 'Volcanic devastation unleashed.', effect: { status: 'burn', chance: 0.5 }, level: 18 },

  // Hydro moves
  { id: 'water-gun', name: 'Aqua Pulse', power: 40, accuracy: 100, type: 'hydro', category: 'special', desc: 'Pressurized water blast.', effect: null, level: 1 },
  { id: 'tidal-wave', name: 'Tidal Wave', power: 60, accuracy: 85, type: 'hydro', category: 'special', desc: 'Cascading tidal force.', effect: { status: 'wet', chance: 0.3 }, level: 5 },
  { id: 'riptide', name: 'Riptide Torrent', power: 75, accuracy: 80, type: 'hydro', category: 'special', desc: 'Pulls enemies under.', effect: { status: 'wet', chance: 0.4 }, level: 12 },
  { id: 'tsunami', name: 'Abyssal Surge', power: 90, accuracy: 70, type: 'hydro', category: 'special', desc: 'Oceanic devastation.', effect: { status: 'wet', chance: 0.5 }, level: 18 },

  // Flora moves
  { id: 'vine-whip', name: 'Vine Whip', power: 35, accuracy: 100, type: 'flora', category: 'physical', desc: 'Lashing thorny tendrils.', effect: null, level: 1 },
  { id: 'spore', name: 'Toxic Spore', power: 30, accuracy: 95, type: 'flora', category: 'special', desc: 'Releases toxic spores.', effect: { status: 'spore', chance: 0.6 }, level: 3 },
  { id: 'thorn-storm', name: 'Thorn Storm', power: 60, accuracy: 85, type: 'flora', category: 'physical', desc: 'Scattering razor thorns.', effect: { status: 'spore', chance: 0.3 }, level: 6 },
  { id: 'giga-drain', name: 'Giga Bloom', power: 70, accuracy: 85, type: 'flora', category: 'special', desc: 'Drains life force.', effect: { heal: 0.5 }, level: 10 },
  { id: 'petal-barrage', name: 'Petal Barrage', power: 85, accuracy: 80, type: 'flora', category: 'special', desc: 'A storm of razor petals.', effect: { status: 'spore', chance: 0.4 }, level: 16 },

  // Volt moves
  { id: 'spark', name: 'Spark', power: 40, accuracy: 100, type: 'volt', category: 'special', desc: 'A jolt of electricity.', effect: { status: 'stun', chance: 0.2 }, level: 1 },
  { id: 'thunderbolt', name: 'Thunderbolt', power: 60, accuracy: 85, type: 'volt', category: 'special', desc: 'Crackling electric strike.', effect: { status: 'stun', chance: 0.3 }, level: 5 },
  { id: 'plasma', name: 'Plasma Arc', power: 75, accuracy: 80, type: 'volt', category: 'special', desc: 'Arcing plasma chains.', effect: { status: 'stun', chance: 0.4 }, level: 11 },
  { id: 'megavolt', name: 'Megavolt Strike', power: 95, accuracy: 70, type: 'volt', category: 'special', desc: 'Cataclysmic lightning burst.', effect: { status: 'stun', chance: 0.5 }, level: 18 },

  // Zephyr moves
  { id: 'gust', name: 'Gust', power: 30, accuracy: 100, type: 'zephyr', category: 'special', desc: 'A swift cutting breeze.', effect: null, level: 1 },
  { id: 'air-blade', name: 'Air Blade', power: 50, accuracy: 95, type: 'zephyr', category: 'special', desc: 'Compressed air slash.', effect: null, level: 4 },
  { id: 'cyclone', name: 'Cyclone Crash', power: 70, accuracy: 80, type: 'zephyr', category: 'special', desc: 'Devastating rotating winds.', effect: null, level: 10 },
  { id: 'maelstrom', name: 'Maelstrom', power: 90, accuracy: 75, type: 'zephyr', category: 'special', desc: 'Category-6 windstorm.', effect: null, level: 17 },

  // Earth moves
  { id: 'pebble', name: 'Stone Fling', power: 35, accuracy: 100, type: 'earth', category: 'physical', desc: 'Hurls sharp rocks.', effect: null, level: 1 },
  { id: 'rockslide', name: 'Rockslide', power: 55, accuracy: 85, type: 'earth', category: 'physical', desc: 'Cascading boulders.', effect: null, level: 5 },
  { id: 'quake', name: 'Earthquake', power: 80, accuracy: 80, type: 'earth', category: 'physical', desc: 'The ground splits apart.', effect: null, level: 12 },
  { id: 'terrain', name: 'Tectonic Fury', power: 95, accuracy: 70, type: 'earth', category: 'physical', desc: 'Continental-scale power.', effect: null, level: 18 },

  // Cosmos moves
  { id: 'void-pulse', name: 'Void Pulse', power: 45, accuracy: 95, type: 'cosmos', category: 'special', desc: 'Dispels nothingness.', effect: null, level: 1 },
  { id: 'starfall', name: 'Starfall', power: 60, accuracy: 90, type: 'cosmos', category: 'special', desc: 'Calls down a star shard.', effect: null, level: 6 },
  { id: 'nova', name: 'Cosmic Nova', power: 80, accuracy: 80, type: 'cosmos', category: 'special', desc: 'Stellar explosion.', effect: null, level: 14 }
];

const MOVE_LOOKUP = {};
MOVES_DB.forEach(m => { MOVE_LOOKUP[m.id] = m; });

const RESONANTS_DB = [
  // ===== PYRO LINE =====
  {
    id: 'emberkit', name: 'Emberkit', element: 'pyro', tier: 1, level: 1,
    baseStats: { hp: 35, attack: 22, defense: 18, speed: 28 },
    moves: ['tackle', 'ember', 'flame-jet', 'inferno'],
    evolvesTo: 'pyrowolf', evolveLevel: 8,
    desc: 'A playful spark-beast that fuels the warmth of young Conductors. Its ears flicker like embers.',
    hosts: ['pyro']
  },
  {
    id: 'pyrowolf', name: 'Pyrowolf', element: 'pyro', tier: 2, level: 8,
    baseStats: { hp: 58, attack: 42, defense: 32, speed: 45 },
    moves: ['rush', 'flame-jet', 'inferno', 'pyroclasm'],
    evolvesTo: 'ignisaur', evolveLevel: 15,
    desc: 'A noble fire-pelt whose howl ignites the air. Commands packs in volcanic highlands.',
    hosts: ['pyro']
  },
  {
    id: 'ignisaur', name: 'Ignisaur', element: 'pyro', tier: 3, level: 15,
    baseStats: { hp: 85, attack: 65, defense: 50, speed: 55 },
    moves: ['crush', 'inferno', 'pyroclasm', 'charge'],
    evolvesTo: null, evolveLevel: null,
    desc: 'An ancient torch-beast whose molten crown dims the sun. Legends say it forged mountain ranges.',
    hosts: ['pyro']
  },

  // ===== HYDRO LINE =====
  {
    id: 'droplet', name: 'Droplet', element: 'hydro', tier: 1, level: 1,
    baseStats: { hp: 40, attack: 18, defense: 25, speed: 22 },
    moves: ['tackle', 'water-gun', 'tidal-wave', 'riptide'],
    evolvesTo: 'geoxus', evolveLevel: 8,
    desc: 'A bubbly sprite of condensed dew. Leaves puddles wherever it hops.',
    hosts: ['hydro']
  },
  {
    id: 'geoxus', name: 'Geoxus', element: 'hydro', tier: 2, level: 8,
    baseStats: { hp: 62, attack: 38, defense: 48, speed: 35 },
    moves: ['rush', 'tidal-wave', 'riptide', 'tsunami'],
    evolvesTo: 'neptunian', evolveLevel: 15,
    desc: 'Shell-armored guardian of grotto depths. Its roar triggers underground springs.',
    hosts: ['hydro']
  },
  {
    id: 'neptunian', name: 'Neptunian', element: 'hydro', tier: 3, level: 15,
    baseStats: { hp: 90, attack: 55, defense: 68, speed: 45 },
    moves: ['crush', 'riptide', 'tsunami', 'charge'],
    evolvesTo: null, evolveLevel: null,
    desc: 'Sovereign of the abyssal tides. Its pearl crown commands ocean currents for miles.',
    hosts: ['hydro']
  },

  // ===== FLORA LINE =====
  {
    id: 'seedling', name: 'Seedling', element: 'flora', tier: 1, level: 1,
    baseStats: { hp: 38, attack: 20, defense: 22, speed: 25 },
    moves: ['tackle', 'vine-whip', 'spore', 'thorn-storm'],
    evolvesTo: 'thornback', evolveLevel: 8,
    desc: 'A tender sapling-sprite. Nurtured sunlight drips from its leafy mane.',
    hosts: ['flora']
  },
  {
    id: 'thornback', name: 'Thornback', element: 'flora', tier: 2, level: 8,
    baseStats: { hp: 60, attack: 40, defense: 42, speed: 38 },
    moves: ['rush', 'vine-whip', 'giga-drain', 'petal-barrage'],
    evolvesTo: 'bloomgoliath', evolveLevel: 15,
    desc: 'A thorn-armored beast that entangles foes in creeping vegetation.',
    hosts: ['flora']
  },
  {
    id: 'bloomgoliath', name: 'Bloomgoliath', element: 'flora', tier: 3, level: 15,
    baseStats: { hp: 85, attack: 58, defense: 60, speed: 40 },
    moves: ['crush', 'giga-drain', 'petal-barrage', 'charge'],
    evolvesTo: null, evolveLevel: null,
    desc: 'A living forest on four legs. Its blooms bring spring and its roots split stone.',
    hosts: ['flora']
  },

  // ===== VOLT LINE =====
  {
    id: 'sparky', name: 'Sparky', element: 'volt', tier: 1, level: 1,
    baseStats: { hp: 32, attack: 28, defense: 15, speed: 35 },
    moves: ['tackle', 'spark', 'thunderbolt', 'plasma'],
    evolvesTo: 'boltsaber', evolveLevel: 8,
    desc: 'A jittery orb of static. Fur stands perpetually on end.',
    hosts: ['volt']
  },
  {
    id: 'boltsaber', name: 'Boltsaber', element: 'volt', tier: 2, level: 8,
    baseStats: { hp: 52, attack: 50, defense: 28, speed: 58 },
    moves: ['rush', 'thunderbolt', 'plasma', 'megavolt'],
    evolvesTo: 'stormlord', evolveLevel: 15,
    desc: 'A sleek fang-beast crackling with electron arcs. Hunts with surgical electric strikes.',
    hosts: ['volt']
  },
  {
    id: 'stormlord', name: 'Stormlord', element: 'volt', tier: 3, level: 15,
    baseStats: { hp: 72, attack: 72, defense: 42, speed: 75 },
    moves: ['crush', 'plasma', 'megavolt', 'charge'],
    evolvesTo: null, evolveLevel: null,
    desc: 'Thunder incarnate. Its wingbeats generate cyclonic lightning storms.',
    hosts: ['volt']
  },

  // ===== ZEPHYR LINE =====
  {
    id: 'gusty', name: 'Gusty', element: 'zephyr', tier: 1, level: 1,
    baseStats: { hp: 34, attack: 20, defense: 18, speed: 32 },
    moves: ['tackle', 'gust', 'air-blade', 'cyclone'],
    evolvesTo: 'slipstream', evolveLevel: 8,
    desc: 'A gentle puff spirit that rides cool breezes. Loves pinwheels.',
    hosts: ['zephyr']
  },
  {
    id: 'slipstream', name: 'Slipstream', element: 'zephyr', tier: 2, level: 8,
    baseStats: { hp: 55, attack: 36, defense: 30, speed: 52 },
    moves: ['rush', 'air-blade', 'cyclone', 'maelstrom'],
    evolvesTo: 'zephyrus', evolveLevel: 15,
    desc: 'A swift wind-dancer who leaves afterimages in brush strokes.',
    hosts: ['zephyr']
  },
  {
    id: 'zephyrus', name: 'Zephyrus', element: 'zephyr', tier: 3, level: 15,
    baseStats: { hp: 78, attack: 55, defense: 48, speed: 80 },
    moves: ['crush', 'cyclone', 'maelstrom', 'charge'],
    evolvesTo: null, evolveLevel: null,
    desc: 'Lord of the eternal gale. Its wingspan measures the horizon.',
    hosts: ['zephyr']
  },

  // ===== EARTH LINE =====
  {
    id: 'pebble', name: 'Pebble', element: 'earth', tier: 1, level: 1,
    baseStats: { hp: 42, attack: 22, defense: 28, speed: 18 },
    moves: ['tackle', 'pebble', 'rockslide', 'quake'],
    evolvesTo: 'obsidian', evolveLevel: 8,
    desc: 'A tiny boulder given life. Roll-tucks into protective shapes.',
    hosts: ['earth']
  },
  {
    id: 'obsidian', name: 'Obsidian', element: 'earth', tier: 2, level: 8,
    baseStats: { hp: 68, attack: 38, defense: 52, speed: 30 },
    moves: ['rush', 'rockslide', 'quake', 'terrain'],
    evolvesTo: 'terrasunder', evolveLevel: 15,
    desc: 'A dark crystalline golem. Jade veins pulse with tectonic beats.',
    hosts: ['earth']
  },
  {
    id: 'terrasunder', name: 'Terrasunder', element: 'earth', tier: 3, level: 15,
    baseStats: { hp: 95, attack: 60, defense: 75, speed: 38 },
    moves: ['crush', 'quake', 'terrain', 'charge'],
    evolvesTo: null, evolveLevel: null,
    desc: "The mountain's wrath made flesh. Its footsteps reshape geography.",
    hosts: ['earth']
  }
];

const RESONANTS_LOOKUP = {};
RESONANTS_DB.forEach(r => { RESONANTS_LOOKUP[r.id] = r; });

// ============================================================
// ZONES & NODE MAPS
// ============================================================
const ZONES = [
  {
    id: 'verdant', name: 'The Verdant Canopy', lvlRange: [1, 5],
    desc: 'Towering bioluminescent trees shrouded in mist. Flora and Zephyr resonants thrive here.',
    bgGradient: ['#1a3a1a', '#0a1a0a'],
    elements: [['flora'], ['flora', 'zephyr'], ['zephyr']],
    nodes: [
      { type: 'heal', label: 'Healing Spring', icon: '💧' },
      { type: 'wild', label: 'Tall Grass', icon: '🌿', levels: [1,3], elements: ['flora'] },
      { type: 'wild', label: 'Mossy Hollow', icon: '🍄', levels: [2,4], elements: ['flora'] },
      { type: 'cache', label: 'Mystery Cache', icon: '📦', reward: 'prisms' },
      { type: 'trainer', label: 'Sage Willow', icon: '🧙', levels: [3,5], elements: ['flora'], dialogue: '"The canopy remembers every footfall. Are you worthy of its knowledge?"' },
      { type: 'wild', label: 'Vine Bridge', icon: '🌿', levels: [3,5], elements: ['flora', 'zephyr'] },
      { type: 'trainer', label: 'Forest Watcher', icon: '🪓', levels: [4,6], elements: ['flora'], dialogue: '"The trees whisper of your approach, stranger. Let us see if you are more than noise."' },
      { type: 'heal', label: 'Healing Spring', icon: '💷' },
      { type: 'boss', label: 'Gatekeeper Verdan', icon: '🌳', level: 6, element: 'flora', dialogue: '"I am Verdan, Rootheart of the Canopy. You seek passage to the deeps? Then face the wrath of ten thousand seasons!"', rewardShards: 100, bossName: 'Verdan, Rootheart' },
    ]
  },
  {
    id: 'sunken', name: 'The Sunken Grotto', lvlRange: [5, 10],
    desc: 'Vast underwater caverns lit by crystal formations. Hydro and Earth resonants dwell here.',
    bgGradient: ['#0a2a4a', '#050f1a'],
    elements: [['hydro'], ['hydro', 'earth'], ['earth']],
    nodes: [
      { type: 'heal', label: 'Healing Spring', icon: '💧' },
      { type: 'wild', label: 'Stalactite Cave', icon: '💎', levels: [5,7], elements: ['earth'] },
      { type: 'wild', label: 'River Bend', icon: '🌊', levels: [6,8], elements: ['hydro'] },
      { type: 'cache', label: 'Sunken Chest', icon: '📦', reward: 'hyper' },
      { type: 'trainer', label: 'Diver Mechis', icon: '🤿', levels: [7,9], elements: ['hydro'], dialogue: '"The grotto doesn\'t forgive hesitation. Neither do I!"' },
      { type: 'wild', label: 'Crystal Pool', icon: '🔮', levels: [7,10], elements: ['hydro', 'earth'] },
      { type: 'trainer', label: 'Cavern Sentinel', icon: '🛡️', levels: [8,10], elements: ['earth'], dialogue: '"My resonants have been forged in the crushing deep. Will yours endure?"' },
      { type: 'heal', label: 'Healing Spring', icon: '💧' },
      { type: 'boss', label: 'Gatekeeper Aquanis', icon: '🐉', level: 11, element: 'hydro', dialogue: '"I am Aquanis, Tide Empress of the Grotto. Your journey ends in drowning waters!"', rewardShards: 200, bossName: 'Aquanis, Tide Empress' },
    ]
  },
  {
    id: 'obsidian', name: 'The Obsidian Crags', lvlRange: [10, 15],
    desc: 'Searing volcanic ridges of black glass. Pyro and Volt resonants dominate the landscape.',
    bgGradient: ['#3a0a0a', '#1a0505'],
    elements: [['pyro'], ['pyro','volt'], ['volt']],
    nodes: [
      { type: 'heal', label: 'Healing Spring', icon: '💧' },
      { type: 'wild', label: 'Lava Flow', icon: '🌋', levels: [10,12], elements: ['pyro'] },
      { type: 'wild', label: 'Storm Perch', icon: '⛈️', levels: [11,13], elements: ['volt'] },
      { type: 'cache', label: 'Molten Cache', icon: '🔥', reward: 'elixir' },
      { type: 'trainer', label: 'Pyromancer Izu', icon: '🔥', levels: [12,14], elements: ['pyro'], dialogue: '"The Crags will test your soul. Only the burning survive!"' },
      { type: 'wild', label: 'Thunder Ridge', icon: '⚡', levels: [12,14], elements: ['volt', 'pyro'] },
      { type: 'trainer', name: 'Voltmaster Kyra', icon: '⚡', levels: [13,15], elements: ['volt'], dialogue: '"Feel that charge in the air? It is my resonants gathering. Prepare for the storm!"' },
      { type: 'heal', label: 'Healing Spring', icon: '💧' },
      { type: 'boss', label: 'Gatekeeper Pyrax', icon: '🐲', level: 16, element: 'pyro', dialogue: '"I am Pyrax, Magma Sovereign. The volcano is my throne and your pyre!"', rewardShards: 400, bossName: 'Pyrax, Magma Sovereign' },
    ]
  },
  {
    id: 'apex', name: 'The Apex Pillar', lvlRange: [15, 25],
    desc: 'A colossal spire piercing the heavens. The ultimate test for any Conductor.',
    bgGradient: ['#2a0a4a', '#0a0520'],
    elements: [['cosmos'], ['pyro','hydro','flora'], ['volt','earth','zephyr']],
    nodes: [
      { type: 'heal', label: 'Healing Spring', icon: '💧' },
      { type: 'wild', label: 'Astral Spire', icon: '✨', levels: [15,18], elements: ['cosmos'] },
      { type: 'wild', label: 'Prism Sanctum', icon: '💎', levels: [16,19], elements: ['cosmos'] },
      { type: 'cache', label: 'Celestial Cache', icon: '🌟', reward: 'max' },
      { type: 'trainer', label: 'Elder Stellan', icon: '🔭', levels: [17,20], elements: ['cosmos'], dialogue: '"You come seeking the peak. Few return unchanged."' },
      { type: 'wild', label: 'Storm Apex', icon: '🌪️', levels: [18,22], elements: ['volt'] },
      { type: 'trainer', label: 'Voidwalker Ere', icon: '🌑', levels: [18,24], elements: ['cosmos'], dialogue: '"The void hears me. Do you wish to be heard... or silenced?"' },
      { type: 'heal', label: 'Healing Spring', icon: '💧' },
      { type: 'boss', label: 'Primordial Nexus', icon: '👑', level: 25, element: 'cosmos', dialogue: '"I am the Nexus, the Core Resonance. All elements obey me. You, Conductor, will submit!"', rewardShards: 1000, bossName: 'The Primordial Nexus', isFinal: true },
    ]
  }
];

// ============================================================
// SVG SPRITE RENDERER (premium vector-style)
// ============================================================
function renderResonantSVG(mon, size = 140, flip = false) {
  const idb = RESONANTS_LOOKUP[mon.baseId] || mon;
  const el = idb.element;
  const tier = idb.tier;
  const colors = EL_COLORS[el];
  const s = size;
  const cx = s / 2, cy = s / 2;
  let svg = `<svg width="${s}" height="${s}" viewBox="0 0 ${s} ${s}" xmlns="http://www.w3.org/2000/svg">`;
  svg += `<defs>
    <radialGradient id="g${mon.uid}" cx="45%" cy="40%" r="55%">
      <stop offset="0%" stop-color="${colors.primary}" stop-opacity="0.9"/>
      <stop offset="100%" stop-color="${colors.secondary}" stop-opacity="0.7"/>
    </radialGradient>
    <filter id="f${mon.uid}"><feGaussianBlur stdDeviation="1.5"/></filter>
    <radialGradient id="g2${mon.uid}" cx="50%" cy="35%" r="50%">
      <stop offset="0%" stop-color="${colors.accent}" stop-opacity="0.3"/>
      <stop offset="100%" stop-color="${colors.glow}" stop-opacity="0"/>
    </radialGradient>
  </defs>`;

  // Element aura
  const auraR = tier === 3 ? 54 : tier === 2 ? 48 : 44;
  svg += `<circle cx="${cx}" cy="${cy}" r="${auraR}" fill="url(#g2${mon.uid})" filter="url(#f${mon.uid})"/>`;

  // Direction indicator
  const dx = flip ? -1 : 1;

  // Body shape - varies by element tier and type
  const bodyW = tier === 3 ? 36 : tier === 2 ? 30 : 26;
  const bodyH = tier === 3 ? 40 : tier === 2 ? 34 : 28;

  if (el === 'pyro') {
    // Flame-like angular body
    svg += `<path d="M${cx},${cy - bodyH} ${cx + bodyW},${cy - bodyH*0.3} ${cx + bodyW*0.7},${cy} ${cx + bodyW},${cy + bodyH*0.7} ${cx},${cy + bodyH} ${cx - bodyW},${cy + bodyH*0.7} ${cx - bodyW*0.7},${cy} ${cx - bodyW},${cy - bodyH*0.3} Z" fill="url(#g${mon.uid})" stroke="${colors.accent}" stroke-width="1"/>`;
    // Flame top spikes
    svg += `<polygon points="${cx - 8},${cy - bodyH + 5} ${cx},${cy - bodyH - 12} ${cx + 8},${cy - bodyH + 5}" fill="${colors.accent}" opacity="0.8"/>`;
    if (tier >= 2) {
      svg += `<polygon points="${cx - 16},${cy - bodyH*0.6} ${cx - 6},${cy - bodyH*0.9 - 5} ${cx - 2},${cy - bodyH*0.6}" fill="${colors.secondary}" opacity="0.6"/>`;
      svg += `<polygon points="${cx + 16},${cy - bodyH*0.6} ${cx + 6},${cy - bodyH*0.9 - 5} ${cx + 2},${cy - bodyH*0.6}" fill="${colors.secondary}" opacity="0.6"/>`;
    }
    if (tier === 3) {
      svg += `<ellipse cx="${cx}" cy="${cy + bodyH + 3}" rx="18" ry="6" fill="${colors.primary}" opacity="0.4"/>`;
    }
  } else if (el === 'hydro') {
    // Droplet/rounded body
    svg += `<ellipse cx="${cx}" cy="${cy + 4}" rx="${bodyW}" ry="${bodyH}" fill="url(#g${mon.uid})" stroke="${colors.accent}" stroke-width="1"/>`;
    svg += `<path d="M${cx},${cy - bodyH - 5} ${cx + 12},${cy - bodyH + 8} ${cx - 12},${cy - bodyH + 8} Z" fill="${colors.accent}" opacity="0.7"/>`;
    // Wave patterns
    svg += `<path d="M${cx - 18},${cy - 4} Q${cx - 10},${cy - 10} ${cx},${cy - 4} Q${cx + 10},${cy + 2} ${cx + 18},${cy - 4}" fill="none" stroke="${colors.accent}" stroke-width="1.5" opacity="0.5"/>`;
    if (tier >= 2) {
      svg += `<path d="M${cx - 14},${cy + 8} Q${cx - 7},${cy + 3} ${cx},${cy + 8} Q${cx + 7},${cy + 13} ${cx + 14},${cy + 8}" fill="none" stroke="${colors.accent}" stroke-width="1" opacity="0.4"/>`;
    }
    // Shell/back features
    if (tier >= 2) {
      svg += `<ellipse cx="${cx}" cy="${cy - 8}" rx="${bodyW*0.5}" ry="${bodyH*0.35}" fill="${colors.secondary}" opacity="0.3"/>`;
    }
  } else if (el === 'flora') {
    // Leaf/plant body
    svg += `<ellipse cx="${cx}" cy="${cy + 2}" rx="${bodyW}" ry="${bodyH*0.9}" fill="url(#g${mon.uid})" stroke="${colors.accent}" stroke-width="1"/>`;
    // Leaves on top
    svg += `<ellipse cx="${cx}" cy="${cy - bodyH}" rx="${bodyW*0.4}" ry="12" fill="${colors.primary}" opacity="0.9" transform="rotate(-10, ${cx}, ${cy - bodyH})"/>`;
    if (tier >= 2) {
      svg += `<ellipse cx="${cx - 14}" cy="${cy - bodyH + 8}" rx="10" ry="6" fill="${colors.secondary}" opacity="0.7" transform="rotate(-30, ${cx - 14}, ${cy - bodyH + 8})"/>`;
      svg += `<ellipse cx="${cx + 14}" cy="${cy - bodyH + 8}" rx="10" ry="6" fill="${colors.secondary}" opacity="0.7" transform="rotate(30, ${cx + 14}, ${cy - bodyH + 8})"/>`;
    }
    if (tier === 3) {
      svg += `<path d="M${cx - 8},${cy + bodyH*0.5} L${cx - 14},${cy + bodyH} L${cx - 4},${cy + bodyH} Z" fill="${colors.accent}" opacity="0.5"/>`;
      svg += `<path d="M${cx + 8},${cy + bodyH*0.5} L${cx + 14},${cy + bodyH} L${cx + 4},${cy + bodyH} Z" fill="${colors.accent}" opacity="0.5"/>`;
    }
    // Vine accents
    svg += `<path d="M${cx - bodyW},${cy} Q${cx - bodyW - 6},${cy - 10} ${cx - bodyW + 4},${cy - 16}" fill="none" stroke="${colors.accent}" stroke-width="1.5" opacity="0.6"/>`;
  } else if (el === 'volt') {
    // Angular/jagged body
    svg += `<polygon points="${cx},${cy - bodyH} ${cx + bodyW},${cy - bodyH*0.4} ${cx + bodyW*1.1},${cy + 4} ${cx + bodyW*0.6},${cy + bodyH} ${cx - bodyW*0.6},${cy + bodyH} ${cx - bodyW*1.1},${cy + 4} ${cx - bodyW},${cy - bodyH*0.4}" fill="url(#g${mon.uid})" stroke="${colors.accent}" stroke-width="1"/>`;
    // Lightning bolt accents
    svg += `<polyline points="${cx},${cy - bodyH - 4} ${cx - 6},${cy - bodyH - 12} ${cx - 1},${cy - bodyH - 12} ${cx - 7},${cy - bodyH - 20}" fill="none" stroke="${colors.accent}" stroke-width="2" opacity="0.7"/>`;
    if (tier >= 2) {
      svg += `<polyline points="${cx + 10},${cy - bodyH*0.4} ${cx + 14},${cy - bodyH*0.4 - 8} ${cx + 8},${cy - bodyH*0.4 - 8} ${cx + 13},${cy - bodyH*0.4 - 16}" fill="none" stroke="${colors.accent}" stroke-width="1.5" opacity="0.5"/>`;
    }
    if (tier === 3) {
      svg += `<circle cx="${cx}" cy="${cy + bodyH + 5}" r="8" fill="${colors.primary}" opacity="0.5"/>`;
      svg += `<circle cx="${cx - 20}" cy="${cy + bodyH + 3}" r="4" fill="${colors.accent}" opacity="0.4"/>`;
      svg += `<circle cx="${cx + 20}" cy="${cy + bodyH + 3}" r="4" fill="${colors.accent}" opacity="0.4"/>`;
    }
  } else if (el === 'zephyr') {
    // Swirl/cloud body
    svg += `<ellipse cx="${cx}" cy="${cy}" rx="${bodyW*1.1}" ry="${bodyH*0.85}" fill="url(#g${mon.uid})" stroke="${colors.accent}" stroke-width="1"/>`;
    // Swirl lines
    svg += `<path d="M${cx - 20},${cy - 4} Q${cx - 10},${cy - 18} ${cx + 4},${cy - 10} Q${cx + 16},${cy - 2} ${cx + 22},${cy - 10}" fill="none" stroke="${colors.accent}" stroke-width="1.5" opacity="0.6"/>`;
    if (tier >= 2) {
      svg += `<path d="M${cx - 14},${cy + 6} Q${cx - 5},${cy + 14} ${cx + 6},${cy + 8} Q${cx + 14},${cy + 4} ${cx + 20},${cy + 10}" fill="none" stroke="${colors.accent}" stroke-width="1" opacity="0.5"/>`;
    }
    // Wing-like accents
    svg += `<path d="M${cx - bodyW},${cy - 6} Q${cx - bodyW - 14},${cy - 16} ${cx - bodyW - 8},${cy + 4}" fill="${colors.primary}" opacity="0.4"/>`;
    svg += `<path d="M${cx + bodyW},${cy - 6} Q${cx + bodyW + 14},${cy - 16} ${cx + bodyW + 8},${cy + 4}" fill="${colors.primary}" opacity="0.4"/>`;
    if (tier === 3) {
      svg += `<ellipse cx="${cx - bodyW - 16}" cy="${cy}" rx="8" ry="12" fill="${colors.accent}" opacity="0.3"/>`;
      svg += `<ellipse cx="${cx + bodyW + 16}" cy="${cy}" rx="8" ry="12" fill="${colors.accent}" opacity="0.3"/>`;
    }
  } else if (el === 'earth') {
    // Boulder/hexagonal body
    const r = bodyW;
    let pts = [];
    for (let i = 0; i < 6; i++) {
      const angle = (i * 60 - 90) * Math.PI / 180;
      pts.push([cx + r * Math.cos(angle), cy + bodyH * 0.6 * Math.sin(angle) + 4]);
    }
    svg += `<polygon points="${pts.map(p => p.join(',')).join(' ')}" fill="url(#g${mon.uid})" stroke="${colors.accent}" stroke-width="1.5"/>`;
    // Crystal facets
    svg += `<line x1="${pts[0][0]}" y1="${pts[0][1]}" x2="${cx}" y2="${cy + 4}" stroke="${colors.accent}" stroke-width="0.8" opacity="0.4"/>`;
    svg += `<line x1="${pts[1][0]}" y1="${pts[1][1]}" x2="${cx}" y2="${cy + 4}" stroke="${colors.accent}" stroke-width="0.8" opacity="0.4"/>`;
    svg += `<line x1="${pts[2][0]}" y1="${pts[2][1]}" x2="${cx}" y2="${cy + 4}" stroke="${colors.accent}" stroke-width="0.8" opacity="0.4"/>`;
    if (tier >= 2) {
      svg += `<line x1="${pts[4][0]}" y1="${pts[4][1]}" x2="${cx}" y2="${cy + 4}" stroke="${colors.accent}" stroke-width="1" opacity="0.3"/>`;
      svg += `<line x1="${pts[5][0]}" y1="${pts[5][1]}" x2="${cx}" y2="${cy + 4}" stroke="${colors.accent}" stroke-width="1" opacity="0.3"/>`;
    }
    if (tier === 3) {
      svg += `<ellipse cx="${cx}" cy="${cy + bodyH + 6}" rx="22" ry="5" fill="${colors.secondary}" opacity="0.4"/>`;
      svg += `<polygon points="${cx - 4},${cy - bodyH - 5} ${cx + 2},${cy - bodyH - 14} ${cx + 8},${cy - bodyH - 5}" fill="${colors.accent}" opacity="0.6"/>`;
    }
  } else {
    // cosmos - star/nebula
    const starR = bodyW * 0.7;
    let starPts = [];
    for (let i = 0; i < 5; i++) {
      const a = (i * 72 - 90) * Math.PI / 180;
      starPts.push([cx + starR * Math.cos(a), cy + starR * Math.sin(a) + 4]);
      const a2 = ((i * 72 + 36) - 90) * Math.PI / 180;
      starPts.push([cx + starR * 0.45 * Math.cos(a2), cy + starR * 0.45 * Math.sin(a2) + 4]);
    }
    svg += `<polygon points="${starPts.map(p => p.join(',')).join(' ')}" fill="url(#g${mon.uid})" stroke="${colors.accent}" stroke-width="1"/>`;
    // Orbit rings
    svg += `<ellipse cx="${cx}" cy="${cy + 4}" rx="${bodyW * 1.2}" ry="${bodyH * 0.5}" fill="none" stroke="${colors.accent}" stroke-width="0.8" opacity="0.5" transform="rotate(-15, ${cx}, ${cy + 4})"/>`;
    if (tier >= 2) {
      svg += `<ellipse cx="${cx}" cy="${cy + 4}" rx="${bodyW * 1.4}" ry="${bodyH * 0.6}" fill="none" stroke="${colors.primary}" stroke-width="0.5" opacity="0.3" transform="rotate(20, ${cx}, ${cy + 4})"/>`;
    }
  }

  // Shared features
  const eyeY = cy + (tier === 3 ? 2 : 0);
  const eyeDX = tier === 3 ? 12 : 10;

  // Eyes
  if (el !== 'pyro' || tier < 2) {
    // Normal eyes
    svg += `<circle cx="${cx - eyeDX}" cy="${eyeY}" r="4" fill="white" opacity="0.9"/>`;
    svg += `<circle cx="${cx + eyeDX}" cy="${eyeY}" r="4" fill="white" opacity="0.9"/>`;
    svg += `<circle cx="${cx - eyeDX}" cy="${eyeY}" r="2" fill="#1a1a2e"/>`;
    svg += `<circle cx="${cx + eyeDX}" cy="${eyeY}" r="2" fill="#1a1a2e"/>`;
  } else {
    // Pyro tier 2+ - cat eyes
    svg += `<path d="M${cx - eyeDX - 4},${eyeY - 1} L${cx - eyeDX},${eyeY - 5} L${cx - eyeDX + 4},${eyeY - 1} Z" fill="white" opacity="0.9"/>`;
    svg += `<path d="M${cx + eyeDX - 4},${eyeY - 1} L${cx + eyeDX},${eyeY - 5} L${cx + eyeDX + 4},${eyeY - 1} Z" fill="white" opacity="0.9"/>`;
    svg += `<circle cx="${cx - eyeDX}" cy="${eyeY + 1}" r="2" fill="#FF6B35"/>`;
    svg += `<circle cx="${cx + eyeDX}" cy="${eyeY + 1}" r="2" fill="#FF6B35"/>`;
  }

  // Mouth
  if (el === 'flora') {
    svg += `<path d="M${cx - 5},${cy + bodyH*0.5} Q${cx},${cy + bodyH*0.5 + 5} ${cx + 5},${cy + bodyH*0.5}" fill="none" stroke="${colors.accent}" stroke-width="1.5"/>`;
  } else if (el === 'volt' || el === 'pyro') {
    svg += `<path d="M${cx - 6},${cy + bodyH*0.55} L${cx},${cy + bodyH*0.55 + 4} L${cx + 6},${cy + bodyH*0.55}" fill="none" stroke="${colors.accent}" stroke-width="1.5"/>`;
  } else if (el === 'earth') {
    svg += `<line x1="${cx - 5}" y1="${cy + bodyH*0.5}" x2="${cx + 5}" y2="${cy + bodyH*0.5}" stroke="${colors.accent}" stroke-width="1.5"/>`;
  } else if (el === 'zephyr') {
    svg += `<ellipse cx="${cx}" cy="${cy + bodyH*0.5 + 2}" rx="4" ry="2" fill="${colors.accent}" opacity="0.6"/>`;
  } else {
    svg += `<path d="M${cx - 4},${cy + bodyH*0.5} Q${cx},${cy + bodyH*0.5 + 8} ${cx + 4},${cy + bodyH*0.5}" fill="none" stroke="${colors.accent}" stroke-width="1.2"/>`;
  }

  // Unique extra feature per element for identification
  if (el === 'pyro' && tier >= 2) {
    // Smoke from head
    svg += `<circle cx="${cx - 6}" cy="${cy - bodyH - 6}" r="3" fill="${colors.accent}" opacity="0.3"/>`;
    svg += `<circle cx="${cx + 8}" cy="${cy - bodyH - 9}" r="2" fill="${colors.accent}" opacity="0.2"/>`;
  }
  if (el === 'hydro') {
    // Water drop
    svg += `<path d="M${cx + 16},${cy - bodyH*0.3} L${cx + 20},${cy - bodyH*0.3 + 6} L${cx + 12},${cy - bodyH*0.3 + 6} Z" fill="${colors.accent}" opacity="0.5"/>`;
  }
  if (el === 'earth') {
    // Embedded gem
    svg += `<polygon points="${cx - 4},${cy - 4} ${cx},${cy - 8} ${cx + 4},${cy - 4} ${cx},${cy}" fill="${colors.accent}" opacity="0.7"/>`;
  }

  svg += `</svg>`;
  return svg;
}

// Simplified node icons
function getNodeIcon(node) {
  if (node.icon && !node.icon.includes(' ')) {
    // Emoji icons for nodes
    return node.icon;
  }
  // Fallback SVG
  return `<svg width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8" fill="rgba(255,255,255,0.3)"/></svg>`;
}

// ============================================================
// GAME STATE & SAVE SYSTEM
// ============================================================
const SAVE_KEY = 'aethelgard_save_v1';

let gameState = {
  playerName: 'Conductor',
  activeParty: [],    // array of monster instances (max 3)
  storageBox: [],     // additional monsters
  inventory: {
    prism: 5,
    hyperPrism: 1,
    restorer: 2,
    elixir: 1
  },
  shards: 50,
  unlockedZones: ['verdant'],
  currentZone: null,
  currentNodeIndex: 0,
  completedNodes: {}, // { zoneId: [nodeIndices] }
  defeatedBosses: [], // [zoneId]
  dexDiscovered: [],  // [resonantId]
  starterChosen: false,
  settings: { sfx: true, music: true }
};

let uidCounter = 1;
function nextUid() { return 'm' + (uidCounter++); }

// Create a monster instance from base data
function createMonster(baseId, level) {
  const base = RESONANTS_LOOKUP[baseId];
  if (!base) return null;
  const lvl = level || base.level;
  const statMult = 1 + (lvl - 1) * 0.08;
  return {
    uid: nextUid(),
    baseId: baseId,
    name: base.name,
    element: base.element,
    tier: base.tier,
    level: lvl,
    xp: 0,
    xpToNext: xpForLevel(lvl + 1),
    currentHP: Math.floor(base.baseStats.hp * statMult),
    maxHP: Math.floor(base.baseStats.hp * statMult),
    attack: Math.floor(base.baseStats.attack * statMult),
    defense: Math.floor(base.baseStats.defense * statMult),
    speed: Math.floor(base.baseStats.speed * statMult),
    status: null, // { type: 'burn'|'wet'|'spore'|'stun', turns: n }
    moves: base.moves,
    statBoosts: { attack: 0, defense: 0, speed: 0 }
  };
}

function xpForLevel(level) {
  return Math.floor(20 * Math.pow(level, 1.4));
}

function calcMaxHP(mon) {
  const base = RESONANTS_LOOKUP[mon.baseId];
  const statMult = 1 + (mon.level - 1) * 0.08;
  return Math.floor(base.baseStats.hp * statMult);
}

function calcStat(mon, stat) {
  const base = RESONANTS_LOOKUP[mon.baseId];
  const statMult = 1 + (mon.level - 1) * 0.08;
  return Math.floor(base.baseStats[stat] * statMult);
}

function saveGame() {
  try {
    const saveData = {
      ...gameState,
      uidCounter,
      savedAt: Date.now()
    };
    localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
  } catch (e) {
    console.warn('Save failed:', e);
  }
}

function loadGame() {
  try {
    const raw = localStorage.getItem(SAVE_KEY);
    if (!raw) return false;
    const data = JSON.parse(raw);
    gameState = { ...gameState, ...data };
    uidCounter = data.uidCounter || 1;
    return true;
  } catch (e) {
    console.warn('Load failed:', e);
    return false;
  }
}

function hasSave() {
  return !!localStorage.getItem(SAVE_KEY);
}

function resetProgress() {
  if (!confirm('Are you sure? This will erase ALL progress permanently.')) return;
  localStorage.removeItem(SAVE_KEY);
  location.reload();
}

// ============================================================
// SCREEN MANAGEMENT
// ============================================================
let currentScreen = 'screen-menu';

function showScreen(id) {
  const prev = document.getElementById(currentScreen);
  const next = document.getElementById(id);
  if (prev && prev !== next) {
    prev.classList.remove('active');
    prev.style.display = 'none';
  }
  if (next) {
    next.style.display = 'flex';
    // Force reflow
    void next.offsetWidth;
    next.classList.add('active');
  }
  currentScreen = id;

  // Show/hide topbar
  const topbar = document.getElementById('topbar');
  if (id === 'screen-menu' || id === 'screen-settings' || id === 'screen-starter') {
    topbar.classList.add('hidden');
  } else {
    topbar.classList.remove('hidden');
  }

  // Screen-specific init
  if (id === 'screen-zoneselect') renderZoneSelect();
  if (id === 'screen-overworld') renderOverworld();
  if (id === 'screen-party') renderParty();
  if (id === 'screen-shop') renderShop();
  if (id === 'screen-dex') renderDex();
  if (id === 'screen-settings') renderSettings();

  updateTopbar();
}

function goHome() {
  if (gameState.currentZone) {
    showScreen('screen-overworld');
  } else {
    showScreen('screen-zoneselect');
  }
}

function updateTopbar() {
  document.getElementById('shards-display').textContent = gameState.shards;
  const zl = document.getElementById('zone-label');
  if (gameState.currentZone) {
    const zone = ZONES.find(z => z.id === gameState.currentZone);
    zl.textContent = zone ? zone.name : '';
  } else {
    zl.textContent = '';
  }
}

// ============================================================
// NOTIFICATIONS
// ============================================================
function notify(msg, duration = 2500) {
  const el = document.getElementById('notification');
  el.textContent = msg;
  el.classList.remove('hidden');
  el.classList.add('notification');
  clearTimeout(el._timeout);
  el._timeout = setTimeout(() => {
    el.classList.add('hidden');
    el.classList.remove('notification');
  }, duration);
}

// ============================================================
// STARTER SELECTION
// ============================================================
function showStarterSelection() {
  const starters = ['emberkit', 'droplet', 'seedling', 'sparky', 'gusty', 'pebble'];
  const container = document.getElementById('screen-starter');
  let html = `<h2 class="font-pixel text-lg text-purple-300 mb-2 anim-slide-up">CHOOSE YOUR FIRST RESONANT</h2>`;
  html += `<p class="text-sm text-gray-400 mb-6">This companion will begin your journey.</p>`;
  html += `<div class="grid grid-cols-2 md:grid-cols-3 gap-4 max-w-2xl">`;
  starters.forEach(id => {
    const r = RESONANTS_LOOKUP[id];
    const colors = EL_COLORS[r.element];
    html += `<div onclick="selectStarter('${id}')" class="glass p-4 rounded-xl cursor-pointer hover:scale-105 transition-transform ${elBorderClass(r.element)}" style="border-width:2px;">
      <div class="flex justify-center mb-3">${renderResonantSVG(createMonster(id, 1), 80)}</div>
      <p class="font-semibold text-sm text-center ${elTextClass(r.element)}">${r.name}</p>
      <p class="text-[10px] text-gray-400 text-center mt-1">${r.element.toUpperCase()}</p>
      <p class="text-[10px] text-gray-500 text-center mt-1 leading-relaxed">${r.desc}</p>
    </div>`;
  });
  html += `</div>`;
  container.innerHTML = html;
  showScreen('screen-starter');
}

function selectStarter(id) {
  synth.init();
  synth.resume();
  synth.select();
  const mon = createMonster(id, 1);
  gameState.activeParty = [mon];
  gameState.starterChosen = true;
  gameState.dexDiscovered.push(id);
  saveGame();
  showScreen('screen-zoneselect');
  notify(`${mon.name} joins your party!`);
}

// ============================================================
// ZONE SELECT
// ============================================================
function renderZoneSelect() {
  const grid = document.getElementById('zone-grid');
  let html = '';
  ZONES.forEach((zone, i) => {
    const unlocked = gameState.unlockedZones.includes(zone.id);
    const completed = gameState.defeatedBosses.includes(zone.id);
    const colors = zone.elements[0][0];
    html += `<div class="glass p-5 rounded-xl ${unlocked ? elBorderClass(colors) : 'opacity-40'} ${unlocked ? 'cursor-pointer hover:scale-[1.02] transition-transform' : ''}" style="border-width:2px;"
      ${unlocked ? `onclick="enterZone('${zone.id}')"` : ''}>
      <div class="flex items-center justify-between mb-2">
        <h3 class="font-pixel text-[10px] ${elTextClass(colors)}">${zone.name}</h3>
        ${completed ? '<span class="text-green-400 text-xs">✓</span>' : ''}
        ${!unlocked ? '<span class="text-gray-500 text-xs">🔒</span>' : ''}
      </div>
      <p class="text-xs text-gray-400 mb-2">${zone.desc}</p>
      <div class="flex gap-2">
        <span class="badge badge-cosmos text-[9px]">Lv. ${zone.lvlRange[0]}-${zone.lvlRange[1]}</span>
        ${zone.elements[0].map(e => `<span class="badge ${elBadgeClass(e)} text-[9px]">${e}</span>`).join('')}
      </div>
    </div>`;
  });
  grid.innerHTML = html;
}

function enterZone(zoneId) {
  synth.select();
  gameState.currentZone = zoneId;
  gameState.currentNodeIndex = 0;
  if (!gameState.completedNodes[zoneId]) gameState.completedNodes[zoneId] = [];
  saveGame();
  showScreen('screen-overworld');
}

// ============================================================
// OVERWORLD NODE MAP
// ============================================================
function renderOverworld() {
  const zone = ZONES.find(z => z.id === gameState.currentZone);
  if (!zone) return;
  document.getElementById('map-title').textContent = zone.name;
  const completed = gameState.completedNodes[zone.id] || [];
  const totalNodes = zone.nodes.length;
  const completedCount = completed.length;
  document.getElementById('map-progress').textContent = `${completedCount}/${totalNodes}`;

  const mapEl = document.getElementById('node-map');
  // Set background
  mapEl.style.background = `radial-gradient(ellipse at 50% 50%, ${zone.bgGradient[0]}44, ${zone.bgGradient[1]})`;

  // Layout nodes in a winding path
  let html = '<div class="absolute inset-0 flex items-center justify-center">';
  html += '<div class="relative w-full h-full">';

  const nodes = zone.nodes;
  const mapW = 100; // percentage
  const mapH = 100;

  // Calculate positions in a snake pattern
  const positions = [];
  const cols = Math.min(nodes.length, 3);
  const rows = Math.ceil(nodes.length / cols);
  nodes.forEach((node, i) => {
    const col = i % cols;
    const row = Math.floor(i / cols);
    const xPct = cols === 1 ? 50 : 15 + (col / (cols - 1)) * 70;
    // Snake: alternate row direction
    const actualCol = row % 2 === 0 ? col : (cols - 1 - col);
    const xPctSnake = cols === 1 ? 50 : 15 + (actualCol / (cols - 1)) * 70;
    const yPct = 12 + (row / Math.max(rows - 1, 1)) * 76;
    positions.push({ x: xPctSnake, y: yPct });
  });

  // Draw connectors
  for (let i = 0; i < positions.length - 1; i++) {
    const p1 = positions[i];
    const p2 = positions[i + 1];
    const isActive = completed.includes(i);
    const dx = p2.x - p1.x;
    const dy = p2.y - p1.y;
    const len = Math.sqrt(dx * dx + dy * dy);
    const angle = Math.atan2(dy, dx) * 180 / Math.PI;
    html += `<div class="node-connector ${isActive ? 'active' : ''}" style="left:${p1.x}%;top:${p1.y}%;width:${len}%;transform:rotate(${angle}deg);"></div>`;
  }

  // Draw nodes
  nodes.forEach((node, i) => {
    const pos = positions[i];
    const isCompleted = completed.includes(i);
    const isCurrent = i === gameState.currentNodeIndex;
    const isLocked = i > 0 && !completed.includes(i - 1) && !isCompleted;
    const isBoss = node.type === 'boss';
    let nodeClass = 'node';
    if (isCompleted) nodeClass += ' completed';
    if (isLocked) nodeClass += ' locked';
    if (isCurrent && !isCompleted) nodeClass += ' current';
    if (isBoss) nodeClass += ' boss-node';

    const elColor = node.element || (node.elements && node.elements[0]) || 'cosmos';
    const borderClass = isBoss ? elBorderClass(elColor) : '';

    html += `<div class="${nodeClass} ${borderClass}" style="left:${pos.x}%;top:${pos.y}%;transform:translate(-50%,-50%);border-width:2px;"
      ${!isLocked && !isCompleted ? `onclick="interactNode(${i})"` : ''}
      title="${node.label}">
      ${getNodeIcon(node)}
    </div>`;
    // Label
    html += `<div class="absolute text-[9px] text-gray-400 text-center pointer-events-none" style="left:${pos.x}%;top:${pos.y + 8}%;transform:translateX(-50%);white-space:nowrap;">${node.label}</div>`;
  });

  html += '</div></div>';
  mapEl.innerHTML = html;
}

function interactNode(index) {
  synth.select();
  const zone = ZONES.find(z => z.id === gameState.currentZone);
  const node = zone.nodes[index];
  gameState.currentNodeIndex = index;

  switch (node.type) {
    case 'wild': startWildBattle(zone, node); break;
    case 'trainer': startTrainerBattle(zone, node); break;
    case 'boss': startBossBattle(zone, node); break;
    case 'heal': healAtSpring(); break;
    case 'cache': openCache(node); break;
  }
}

function healAtSpring() {
  synth.heal();
  gameState.activeParty.forEach(mon => {
    mon.currentHP = mon.maxHP;
    mon.status = null;
    mon.statBoosts = { attack: 0, defense: 0, speed: 0 };
  });
  completeCurrentNode();
  notify('Your party has been fully restored!');
  saveGame();
}

function openCache(node) {
  const rewards = {
    prism: { type: 'item', item: 'prism', amount: 3, msg: 'Found 3 Harmonizer Prisms!' },
    hyper: { type: 'item', item: 'hyperPrism', amount: 2, msg: 'Found 2 Hyper-Conductive Prisms!' },
    elixir: { type: 'item', item: 'elixir', amount: 2, msg: 'Found 2 Resonance Elixirs!' },
    max: { type: 'shards', amount: 150, msg: 'Found 150 Aether Shards!' }
  };
  const reward = rewards[node.reward] || rewards.prism;
  if (reward.type === 'item') {
    gameState.inventory[reward.item] = (gameState.inventory[reward.item] || 0) + reward.amount;
  } else {
    gameState.shards += reward.amount;
  }
  synth.captureSuccess();
  completeCurrentNode();
  notify(reward.msg);
  saveGame();
}

function completeCurrentNode() {
  const zoneId = gameState.currentZone;
  const idx = gameState.currentNodeIndex;
  if (!gameState.completedNodes[zoneId]) gameState.completedNodes[zoneId] = [];
  if (!gameState.completedNodes[zoneId].includes(idx)) {
    gameState.completedNodes[zoneId].push(idx);
  }
  // Advance to next node
  const zone = ZONES.find(z => z.id === zoneId);
  if (idx + 1 < zone.nodes.length) {
    gameState.currentNodeIndex = idx + 1;
  }
  saveGame();
  renderOverworld();
}

// ============================================================
// COMBAT SYSTEM
// ============================================================
let battle = null;

function initBattle(enemyMon, context) {
  battle = {
    playerMon: gameState.activeParty[0],
    enemyMon: enemyMon,
    isWild: context.isWild,
    isBoss: context.isBoss || false,
    log: [],
    phase: 'select', // select, anim, enemyTurn, result
    context: context,
    turn: 0
  };
  if (context.isWild) {
    battle.log.push(`A wild ${enemyMon.name} emerged!`);
  } else {
    battle.log.push(`${context.trainerName || 'A Conductor'} challenges you!`);
    if (context.dialogue) battle.log.push(context.dialogue);
    battle.log.push(`${enemyMon.name} appears!`);
  }
  renderBattle();
}

function addLog(msg) {
  if (!battle) return;
  battle.log.push(msg);
  if (battle.log.length > 8) battle.log.shift();
  updateBattleLog();
}

function updateBattleLog() {
  const el = document.getElementById('battle-log');
  if (!el || !battle) return;
  el.innerHTML = battle.log.map(l => `<p class="mb-1">${l}</p>`).join('');
  el.scrollTop = el.scrollHeight;
}

function renderBattle() {
  if (!battle) return;
  const pm = battle.playerMon;
  const em = battle.enemyMon;

  // Player side
  document.getElementById('player-sprite-wrap').innerHTML = renderResonantSVG(pm, 120, false);
  document.getElementById('player-mon-name').textContent = pm.name;
  document.getElementById('player-mon-name').className = `font-semibold text-sm ${elTextClass(pm.element)}`;
  document.getElementById('player-mon-level').textContent = `Lv.${pm.level}`;
  updateHPBars();

  // Status
  const pStatus = document.getElementById('player-mon-status');
  pStatus.innerHTML = pm.status ? `<span class="status-icon status-${pm.status.type}">${pm.status.type.toUpperCase()}</span>` : '';

  // Enemy side
  document.getElementById('enemy-sprite-wrap').innerHTML = renderResonantSVG(em, 120, true);
  document.getElementById('enemy-mon-name').textContent = em.name;
  document.getElementById('enemy-mon-name').className = `font-semibold text-sm ${elTextClass(em.element)}`;
  document.getElementById('enemy-mon-level').textContent = `Lv.${em.level}`;

  const eStatus = document.getElementById('enemy-mon-status');
  eStatus.innerHTML = em.status ? `<span class="status-icon status-${em.status.type}">${em.status.type.toUpperCase()}</span>` : '';

  updateBattleLog();
  renderBattleActions();
}

function updateHPBars() {
  if (!battle) return;
  const pm = battle.playerMon;
  const em = battle.enemyMon;

  const pHpPct = Math.max(0, pm.currentHP / pm.maxHP * 100);
  const pBar = document.getElementById('player-mon-hp-bar');
  pBar.style.width = pHpPct + '%';
  pBar.className = 'hp-bar-fill ' + (pHpPct < 25 ? 'danger' : pHpPct < 50 ? 'warn' : '');
  document.getElementById('player-mon-hp-text').textContent = `HP: ${Math.max(0,pm.currentHP)}/${pm.maxHP}`;

  const pXpPct = pm.xp / pm.xpToNext * 100;
  document.getElementById('player-mon-xp-bar').style.width = Math.min(100, pXpPct) + '%';

  const eHpPct = Math.max(0, em.currentHP / em.maxHP * 100);
  const eBar = document.getElementById('enemy-mon-hp-bar');
  eBar.style.width = eHpPct + '%';
  eBar.className = 'hp-bar-fill ' + (eHpPct < 25 ? 'danger' : eHpPct < 50 ? 'warn' : '');
  document.getElementById('enemy-mon-hp-text').textContent = `HP: ${Math.max(0,em.currentHP)}/${em.maxHP}`;
}

function renderBattleActions() {
  const container = document.getElementById('battle-actions');
  if (!battle || battle.phase !== 'select') return;

  const pm = battle.playerMon;
  const moves = pm.moves.map(mid => MOVE_LOOKUP[mid]).filter(Boolean);

  // Main actions
  let html = `
    <button class="btn-game btn-primary col-span-2 md:col-span-1" onclick="showMoveSelect()">⚔️ Attack</button>
    <button class="btn-game btn-success" onclick="showPrisms()" ${battle.isWild ? '' : 'disabled'}>🔮 Prisms</button>
    <button class="btn-game" onclick="showExchange()" ${gameState.activeParty.length < 2 ? 'disabled' : ''}>🔄 Switch</button>
    <button class="btn-game" onclick="showInventory()">🎒 Items</button>
    <button class="btn-game" onclick="tryRunAway()" ${battle.isBoss ? 'disabled' : ''}>🏃 Flee</button>
    <button class="btn-game col-span-2 md:col-span-1" onclick="battleResult('forfeit')">⏭️ Forfeit</button>
  `;
  container.innerHTML = html;
}

function showMoveSelect() {
  synth.select();
  const container = document.getElementById('battle-actions');
  const pm = battle.playerMon;
  const moves = pm.moves.map(mid => MOVE_LOOKUP[mid]).filter(Boolean);

  let html = '';
  moves.forEach((move, i) => {
    const elClass = elTextClass(move.type);
    html += `<button class="btn-game flex-col !items-start !p-3" onclick="executeMove('${move.id}')">
      <span class="font-semibold text-xs ${elClass}">${move.name}</span>
      <span class="text-[10px] text-gray-400 mt-0.5">${move.type.toUpperCase()} ${move.power > 0 ? '• Pwr ' + move.power : ''}${move.accuracy < 100 ? ' • Acc ' + move.accuracy : ''}</span>
    </button>`;
  });
  html += `<button class="btn-game !col-span-full !md:col-span-4" onclick="renderBattleActions()">← Back</button>`;
  container.innerHTML = html;
}

function showPrisms() {
  synth.select();
  const container = document.getElementById('battle-actions');
  const prismCount = gameState.inventory.prism || 0;
  const hyperCount = gameState.inventory.hyperPrism || 0;

  let html = `
    <button class="btn-game flex-col !items-start !p-3" onclick="usePrism('prism')" ${prismCount < 1 ? 'disabled' : ''}>
      <span class="font-semibold text-xs">🔮 Harmonizer Prism</span>
      <span class="text-[10px] text-gray-400 mt-0.5">Basic catch (x${prismCount})</span>
    </button>
    <button class="btn-game flex-col !items-start !p-3" onclick="usePrism('hyperPrism')" ${hyperCount < 1 ? 'disabled' : ''}>
      <span class="font-semibold text-xs">💎 Hyper-Conductive Prism</span>
      <span class="text-[10px] text-gray-400 mt-0.5">High catch rate (x${hyperCount})</span>
    </button>
    <button class="btn-game !col-span-2 md:col-span-2 mt-2" onclick="renderBattleActions()">← Back</button>
  `;
  container.innerHTML = html;
}

function showExchange() {
  synth.select();
  const container = document.getElementById('battle-actions');
  let html = '';
  gameState.activeParty.forEach((mon, i) => {
    const isCurrent = i === 0;
    html += `<button class="btn-game flex-col !items-start !p-3" ${isCurrent ? 'disabled' : `onclick="swapMonster(${i})"`}>
      <span class="font-semibold text-xs ${elTextClass(mon.element)}">${mon.name}</span>
      <span class="text-[10px] text-gray-400 mt-0.5">Lv.${mon.level} • HP ${mon.currentHP}/${mon.maxHP}${isCurrent ? ' (active)' : ''}</span>
    </button>`;
  });
  html += `<button class="btn-game !col-span-2 md:col-span-2 mt-2" onclick="renderBattleActions()">← Back</button>`;
  container.innerHTML = html;
}

function showInventory() {
  synth.select();
  const container = document.getElementById('battle-actions');
  const items = [
    { key: 'restorer', name: 'Core Restorer', desc: 'Restore 50% HP', icon: '💚' },
    { key: 'elixir', name: 'Resonance Elixir', desc: 'Cure status', icon: '✨' }
  ];
  let html = '';
  items.forEach(item => {
    const count = gameState.inventory[item.key] || 0;
    html += `<button class="btn-game flex-col !items-start !p-3" onclick="useItem('${item.key}')" ${count < 1 ? 'disabled' : ''}>
      <span class="font-semibold text-xs">${item.icon} ${item.name}</span>
      <span class="text-[10px] text-gray-400 mt-0.5">${item.desc} (x${count})</span>
    </button>`;
  });
  html += `<button class="btn-game !col-span-2 md:col-span-2 mt-2" onclick="renderBattleActions()">← Back</button>`;
  container.innerHTML = html;
}

// ============================================================
// COMBAT EXECUTION
// ============================================================
function executeMove(moveId) {
  if (!battle || battle.phase !== 'select') return;
  synth.select();
  const move = MOVE_LOOKUP[moveId];
  const playerMon = battle.playerMon;
  const enemyMon = battle.enemyMon;

  // Check accuracy
  const accRoll = Math.random() * 100;
  if (accRoll > move.accuracy) {
    synth.miss();
    addLog(`${playerMon.name} used ${move.name}... but it missed!`);
    battle.phase = 'anim';
    renderBattleUIAfterAction();
    setTimeout(() => enemyTurn(), 1200);
    return;
  }

  if (move.power === 0) {
    // Support move
    executeSupportMove(playerMon, move);
    battle.phase = 'anim';
    renderBattleUIAfterAction();
    setTimeout(() => enemyTurn(), 1200);
    return;
  }

  // Calculate damage
  const isCrit = Math.random() < 0.10;
  const atkStat = playerMon.attack * (1 + playerMon.statBoosts.attack * 0.5);
  const defStat = enemyMon.defense * (1 + enemyMon.statBoosts.defense * 0.5);
  const typeMod = getTypeMultiplier(move.type, [enemyMon.element]);
  const randFactor = 0.85 + Math.random() * 0.15;
  let damage = Math.floor((((2 * playerMon.level / 5 + 2) * move.power * (atkStat / defStat)) / 50 + 2) * typeMod * randFactor);
  if (isCrit) damage = Math.floor(damage * 1.5);

  damage = Math.max(1, damage);

  // STUN check pre-attack
  if (playerMon.status && playerMon.status.type === 'stun' && Math.random() < 0.3) {
    addLog(`${playerMon.name} is stunned and can't move!`);
    playerMon.status.turns--;
    if (playerMon.status.turns <= 0) playerMon.status = null;
    battle.phase = 'anim';
    renderBattleUIAfterAction();
    setTimeout(() => enemyTurn(), 1200);
    return;
  }

  // Apply damage
  enemyMon.currentHP = Math.max(0, enemyMon.currentHP - damage);

  // Sound & animation
  if (typeMod > 1) {
    synth.hitSuper();
    addLog(`${playerMon.name} used ${move.name}! Super effective! ${damage} damage!`);
    spawnParticles('enemy', 'super');
    shakeScreen();
  } else if (typeMod < 1) {
    synth.hitPhysical();
    addLog(`${playerMon.name} used ${move.name}. Not very effective... ${damage} damage.`);
    spawnParticles('enemy', 'weak');
  } else {
    synth.hitElemental();
    addLog(`${playerMon.name} used ${move.name}! ${damage} damage!`);
    spawnParticles('enemy', 'hit');
  }

  if (isCrit) addLog('Critical hit!');

  // Status effect
  if (move.effect && move.effect.status && Math.random() < move.effect.chance) {
    if (!enemyMon.status) {
      enemyMon.status = { type: move.effect.status, turns: 3 };
      addLog(`${enemyMon.name} is afflicted with ${move.effect.status}!`);
    }
  }

  // Heal from drain
  if (move.effect && move.effect.heal) {
    const healAmt = Math.floor(damage * move.effect.heal);
    playerMon.currentHP = Math.min(playerMon.maxHP, playerMon.currentHP + healAmt);
    addLog(`${playerMon.name} recovered ${healAmt} HP!`);
  }

  battle.phase = 'anim';
  renderBattleUIAfterAction();

  // Check if enemy fainted
  if (enemyMon.currentHp <= 0 || enemyMon.currentHP <= 0) {
    setTimeout(() => handleEnemyFainted(), 1000);
    return;
  }

  setTimeout(() => enemyTurn(), 1200);
}

function executeSupportMove(mon, move) {
  if (move.effect && move.effect.stat) {
    const boost = move.effect.stages || 1;
    mon.statBoosts[move.effect.stat] = Math.min(6, mon.statBoosts[move.effect.stat] + boost);
    addLog(`${mon.name}'s ${move.effect.stat} rose sharply!`);
    synth.heal();
  }
}

function enemyTurn() {
  if (!battle) return;
  const enemyMon = battle.enemyMon;
  const playerMon = battle.playerMon;

  // Check stun
  if (enemyMon.status && enemyMon.status.type === 'stun' && Math.random() < 0.3) {
    addLog(`${enemyMon.name} is stunned!`);
    enemyMon.status.turns--;
    if (enemyMon.status.turns <= 0) enemyMon.status = null;
    endEnemyTurn();
    return;
  }

  // Status damage
  if (enemyMon.status && enemyMon.status.type === 'burn') {
    const burnDmg = Math.floor(enemyMon.maxHP * 0.06);
    enemyMon.currentHP = Math.max(0, enemyMon.currentHP - burnDmg);
    addLog(`${enemyMon.name} takes ${burnDmg} burn damage!`);
    if (enemyMon.currentHP <= 0) {
      setTimeout(() => handleEnemyFainted(), 800);
      return;
    }
  }
  if (enemyMon.status && enemyMon.status.type === 'spore') {
    const sporeDmg = Math.floor(enemyMon.maxHP * 0.05);
    enemyMon.currentHP = Math.max(0, enemyMon.currentHP - sporeDmg);
    addLog(`${enemyMon.name} takes ${sporeDmg} spore damage!`);
    if (enemyMon.currentHP <= 0) {
      setTimeout(() => handleEnemyFainted(), 800);
      return;
    }
  }

  // Pick enemy move (random from available)
  const availableMoves = enemyMon.moves.map(mid => MOVE_LOOKUP[mid]).filter(m => m && m.power > 0);
  if (availableMoves.length === 0) {
    addLog(`${enemyMon.name} has no moves!`);
    endEnemyTurn();
    return;
  }
  const move = availableMoves[Math.floor(Math.random() * availableMoves.length)];

  // Accuracy check
  const accRoll = Math.random() * 100;
  if (accRoll > move.accuracy) {
    synth.miss();
    addLog(`${enemyMon.name} used ${move.name}... but it missed!`);
    endEnemyTurn();
    return;
  }

  const isCrit = Math.random() < 0.10;
  const atkStat = enemyMon.attack * (1 + enemyMon.statBoosts.attack * 0.5);
  const defStat = playerMon.defense * (1 + playerMon.statBoosts.defense * 0.5);
  const typeMod = getTypeMultiplier(move.type, [playerMon.element]);
  const randFactor = 0.85 + Math.random() * 0.15;
  let damage = Math.floor((((2 * enemyMon.level / 5 + 2) * move.power * (atkStat / defStat)) / 50 + 2) * typeMod * randFactor);
  if (isCrit) damage = Math.floor(damage * 1.5);
  damage = Math.max(1, damage);

  playerMon.currentHP = Math.max(0, playerMon.currentHP - damage);

  if (typeMod > 1) {
    synth.hitSuper();
    addLog(`${enemyMon.name} used ${move.name}! Super effective! ${damage} damage!`);
    spawnParticles('player', 'super');
    shakeScreen();
  } else if (typeMod < 1) {
    synth.hitPhysical();
    addLog(`${enemyMon.name} used ${move.name}. Not very effective... ${damage} damage.`);
    spawnParticles('player', 'weak');
  } else {
    synth.hitElemental();
    addLog(`${enemyMon.name} used ${move.name}! ${damage} damage!`);
    spawnParticles('player', 'hit');
  }

  if (isCrit) addLog('Critical hit!');

  // Status from enemy move
  if (move.effect && move.effect.status && Math.random() < move.effect.chance) {
    if (!playerMon.status) {
      playerMon.status = { type: move.effect.status, turns: 3 };
      addLog(`${playerMon.name} is afflicted with ${move.effect.status}!`);
    }
  }

  renderBattle();

  if (playerMon.currentHP <= 0) {
    setTimeout(() => handlePlayerFainted(), 1000);
    return;
  }

  endEnemyTurn();
}

function endEnemyTurn() {
  // Decrement status turns
  const em = battle.enemyMon;
  if (em.status) {
    em.status.turns--;
    if (em.status.turns <= 0) {
      addLog(`${em.name} recovered from ${em.status.type}!`);
      em.status = null;
    }
  }

  battle.phase = 'select';
  battle.turn++;
  renderBattle();
}

function renderBattleUIAfterAction() {
  updateHPBars();
  updateBattleLog();
  // Re-render status
  const pm = battle.playerMon;
  const em = battle.enemyMon;
  document.getElementById('player-mon-status').innerHTML = pm.status ? `<span class="status-icon status-${pm.status.type}">${pm.status.type.toUpperCase()}</span>` : '';
  document.getElementById('enemy-mon-status').innerHTML = em.status ? `<span class="status-icon status-${em.status.type}">${em.status.type.toUpperCase()}</span>` : '';
}

function swapMonster(index) {
  if (!battle || battle.phase !== 'select') return;
  const mon = gameState.activeParty[index];
  if (!mon || mon.currentHP <= 0) {
    synth.error();
    notify('That Resonant is fainted!');
    return;
  }
  synth.select();
  addLog(`Go, ${mon.name}!`);
  gameState.activeParty[0] = gameState.activeParty.splice(index, 1, gameState.activeParty[0])[0];
  battle.playerMon = gameState.activeParty[0];
  renderBattle();
  setTimeout(() => enemyTurn(), 800);
}

function tryRunAway() {
  if (!battle || !battle.isWild) return;
  synth.runAway();
  addLog('Got away safely!');
  battle.phase = 'anim';
  setTimeout(() => endBattle('flee'), 1000);
}

// ============================================================
// CAPTURE SYSTEM
// ============================================================
function usePrism(prismType) {
  if (!battle || !battle.isWild) return;
  const prismCount = gameState.inventory[prismType] || 0;
  if (prismCount < 1) {
    synth.error();
    notify('No prisms left!');
    return;
  }

  gameState.inventory[prismType]--;
  synth.captureStart();

  const enemy = battle.enemyMon;
  const hpFactor = 1 - (enemy.currentHP / enemy.maxHP);
  const statusBonus = enemy.status ? 0.2 : 0;
  const prismBonus = prismType === 'hyperPrism' ? 0.3 : 0;
  let catchRate = 0.15 + hpFactor * 0.5 + statusBonus + prismBonus;
  catchRate = Math.min(0.95, catchRate);

  battle.phase = 'anim';

  addLog(`Thrown a ${prismType === 'hyperPrism' ? 'Hyper-Conductive' : 'Harmonizer'} Prism...`);

  // Shake animation
  const enemyWrap = document.getElementById('enemy-sprite-wrap');
  enemyWrap.classList.add('anim-capture');

  setTimeout(() => {
    enemyWrap.classList.remove('anim-capture');
    const roll = Math.random();
    if (roll < catchRate) {
      synth.captureSuccess();
      addLog(`✨ ${enemy.name} was caught!`);
      // Discover in dex
      if (!gameState.dexDiscovered.includes(enemy.baseId)) {
        gameState.dexDiscovered.push(enemy.baseId);
      }
      // Add to party or storage
      const newMon = createMonster(enemy.baseId, enemy.level);
      if (gameState.activeParty.length < 3) {
        gameState.activeParty.push(newMon);
      } else {
        gameState.storageBox.push(newMon);
      }
      saveGame();
      setTimeout(() => endBattle('capture'), 1500);
    } else {
      synth.captureFail();
      addLog(`${enemy.name} broke free!`);
      renderBattle();
      setTimeout(() => enemyTurn(), 800);
    }
  }, 2000);

  renderBattle();
}

// ============================================================
// ITEMS IN BATTLE
// ============================================================
function useItem(itemKey) {
  if (!battle || battle.phase !== 'select') return;
  const count = gameState.inventory[itemKey] || 0;
  if (count < 1) { synth.error(); return; }

  gameState.inventory[itemKey]--;
  const mon = battle.playerMon;

  if (itemKey === 'restorer') {
    const healAmt = Math.floor(mon.maxHP * 0.5);
    mon.currentHP = Math.min(mon.maxHP, mon.currentHP + healAmt);
    synth.heal();
    addLog(`Used Core Restorer. Restored ${healAmt} HP!`);
  } else if (itemKey === 'elixir') {
    mon.status = null;
    synth.heal();
    addLog(`Used Resonance Elixir. Status cleared!`);
  }

  battle.phase = 'anim';
  renderBattle();
  setTimeout(() => enemyTurn(), 1000);
}

// ============================================================
// BATTLE RESULTS & LEVELING
// ============================================================
function handleEnemyFainted() {
  if (!battle) return;
  const enemy = battle.enemyMon;
  const player = battle.playerMon;

  // XP reward
  const xpGain = Math.floor((enemy.level * 15) * (battle.isBoss ? 2.5 : battle.isWild ? 1 : 1.8));
  player.xp += xpGain;

  let shardReward = 0;
  if (battle.isWild) shardReward = Math.floor(5 + enemy.level * 3);
  else if (battle.context.isTrainer) shardReward = Math.floor(15 + enemy.level * 8);
  else if (battle.isBoss) shardReward = battle.context.rewardShards || 100;

  gameState.shards += shardReward;
  addLog(`${enemy.name} was defeated! Gained ${xpGain} XP!${shardReward > 0 ? ' +' + shardReward + ' shards' : ''}`);

  // Level up check
  while (player.xp >= player.xpToNext && player.level < 50) {
    player.level++;
    player.xp -= player.xpToNext;
    player.xpToNext = xpForLevel(player.level + 1);
    // Recalc stats
    const base = RESONANTS_LOOKUP[player.baseId];
    const statMult = 1 + (player.level - 1) * 0.08;
    player.maxHP = Math.floor(base.baseStats.hp * statMult);
    player.currentHP = Math.min(player.maxHP, player.currentHP + Math.floor(base.baseStats.hp * 0.08));
    player.attack = Math.floor(base.baseStats.attack * statMult);
    player.defense = Math.floor(base.baseStats.defense * statMult);
    player.speed = Math.floor(base.baseStats.speed * statMult);
    addLog(`${player.name} grew to Lv.${player.level}!`);
    synth.levelUp();

    // Evolution check
    if (base.evolvesTo && player.level >= base.evolveLevel && !player.evolved) {
      player.evolved = true;
      evolveInBattle(player);
    }
  }

  // Discover enemy in dex
  if (!gameState.dexDiscovered.includes(enemy.baseId)) {
    gameState.dexDiscovered.push(enemy.baseId);
  }

  saveGame();
  updateHPBars();

  // Handle trainers with multiple mons or boss progression
  if (battle.isBoss) {
    const zoneId = gameState.currentZone;
    if (!gameState.defeatedBosses.includes(zoneId)) {
      gameState.defeatedBosses.push(zoneId);
    }
    // Unlock next zone
    const zoneIdx = ZONES.findIndex(z => z.id === zoneId);
    if (zoneIdx < ZONES.length - 1) {
      const nextZone = ZONES[zoneIdx + 1];
      if (!gameState.unlockedZones.includes(nextZone.id)) {
        gameState.unlockedZones.push(nextZone.id);
      }
    }
    completeCurrentNode();
    saveGame();
    setTimeout(() => endBattle('victory_boss'), 2000);
  } else {
    completeCurrentNode();
    saveGame();
    setTimeout(() => endBattle('victory'), 1500);
  }
}

function evolveInBattle(mon) {
  const base = RESONANTS_LOOKUP[mon.baseId];
  if (!base || !base.evolvesTo) return;
  const evoBase = RESONANTS_LOOKUP[base.evolvesTo];
  if (!evoBase) return;

  addLog(`✨ ${mon.name} is evolving!`);
  mon.baseId = evoBase.id;
  mon.name = evoBase.name;
  mon.element = evoBase.element;
  mon.tier = evoBase.tier;
  mon.moves = [...evoBase.moves];

  // Recalc stats with evolution base
  const statMult = 1 + (mon.level - 1) * 0.08;
  mon.maxHP = Math.floor(evoBase.baseStats.hp * statMult);
  mon.currentHP = mon.maxHP;
  mon.attack = Math.floor(evoBase.baseStats.attack * statMult);
  mon.defense = Math.floor(evoBase.baseStats.defense * statMult);
  mon.speed = Math.floor(evoBase.baseStats.speed * statMult);

  if (!gameState.dexDiscovered.includes(evoBase.id)) {
    gameState.dexDiscovered.push(evoBase.id);
  }

  showLevelUp(mon, true);
}

function handlePlayerFainted() {
  if (!battle) return;
  const mon = battle.playerMon;
  addLog(`${mon.name} fainted!`);

  // Check if any conscious mon available
  const conscious = gameState.activeParty.find(m => m.currentHP > 0 && m.uid !== mon.uid);
  if (conscious) {
    addLog(`Switch to another Resonant!`);
    battle.phase = 'select';
    renderBattleActions();
    // Force switch
    setTimeout(() => {
      showExchange();
    }, 500);
  } else {
    addLog('All your Resonants have fallen...');
    setTimeout(() => endBattle('defeat'), 1500);
  }
}

function endBattle(result) {
  const modal = document.getElementById('battle-result-modal');
  const content = document.getElementById('battle-result-content');

  let html = '';
  if (result === 'victory') {
    html = `<div class="text-2xl mb-3">🏆</div><h2 class="font-pixel text-sm text-green-400 mb-3">VICTORY!</h2>
      <p class="text-sm text-gray-300 mb-4">You defeated the opponent!</p>`;
  } else if (result === 'victory_boss') {
    html = `<div class="text-2xl mb-3">👑</div><h2 class="font-pixel text-sm text-yellow-400 mb-3">GATEWAY OPENED!</h2>
      <p class="text-sm text-gray-300 mb-4">The zone gatekeeper has fallen. A new path is revealed!</p>`;
    synth.victory();
  } else if (result === 'capture') {
    html = `<div class="text-2xl mb-3">🔮</div><h2 class="font-pixel text-sm text-purple-400 mb-3">CAPTURED!</h2>
      <p class="text-sm text-gray-300 mb-4">A new Resonant joins your party!</p>`;
  } else if (result === 'flee') {
    html = `<div class="text-2xl mb-3">🏃</div><h2 class="font-pixel text-sm text-blue-400 mb-3">ESCAPED!</h2>
      <p class="text-sm text-gray-300 mb-4">You retreated safely.</p>`;
  } else if (result === 'defeat') {
    html = `<div class="text-2xl mb-3">💀</div><h2 class="font-pixel text-sm text-red-400 mb-3">DEFEATED</h2>
      <p class="text-sm text-gray-300 mb-4">Your party was knocked out. You return to base.</p>`;
    // Heal after defeat
    gameState.activeParty.forEach(m => {
      m.currentHP = m.maxHP;
      m.status = null;
    });
    gameState.currentZone = null;
  } else {
    html = `<div class="text-2xl mb-3">⏹️</div><h2 class="font-pixel text-sm text-gray-400 mb-3">FORFEIT</h2>`;
  }

  html += `<button onclick="closeBattleResult()" class="btn-game btn-primary w-full justify-center mt-2">Continue</button>`;
  content.innerHTML = html;
  modal.classList.remove('hidden');
}

function closeBattleResult() {
  document.getElementById('battle-result-modal').classList.add('hidden');
  battle = null;
  if (gameState.currentZone) {
    showScreen('screen-overworld');
  } else {
    showScreen('screen-zoneselect');
  }
}

function showLevelUp(mon, isEvolution) {
  const modal = document.getElementById('levelup-modal');
  const content = document.getElementById('levelup-content');
  content.innerHTML = isEvolution
    ? `<div class="anim-levelup text-3xl mb-3">✨</div><h2 class="font-pixel text-sm text-yellow-400 mb-2">EVOLUTION!</h2>
       <p class="text-sm text-gray-300 mb-1">${mon.name} evolved!</p>
       <p class="text-xs text-gray-400 mb-4">Now Lv.${mon.level} ${RESONANTS_LOOKUP[mon.baseId]?.name || ''}</p>
       <button onclick="closeLevelUp()" class="btn-game btn-primary w-full justify-center">Amazing!</button>`
    : `<div class="anim-levelup text-2xl mb-3">⬆️</div><h2 class="font-pixel text-sm text-purple-400 mb-2">LEVEL UP!</h2>
       <p class="text-sm text-gray-300 mb-4">${mon.name} reached Lv.${mon.level}!</p>
       <button onclick="closeLevelUp()" class="btn-game btn-primary w-full justify-center">Great!</button>`;
  modal.classList.remove('hidden');
}

function closeLevelUp() {
  document.getElementById('levelup-modal').classList.add('hidden');
}

// ============================================================
// BATTLE STARTERS
// ============================================================
function startWildBattle(zone, node) {
  const elPool = node.elements;
  const chosenEl = elPool[Math.floor(Math.random() * elPool.length)];
  // Find a monster of this element appropriate for level range
  const candidates = RESONANTS_DB.filter(r => r.element === chosenEl && r.tier === 1);
  if (candidates.length === 0) {
    const allEl = RESONANTS_DB.filter(r => r.element === chosenEl);
    candidates.push(...allEl);
  }
  if (candidates.length === 0) {
    // Fallback: any monster
    candidates.push(...RESONANTS_DB.filter(r => r.tier === 1));
  }
  const base = candidates[Math.floor(Math.random() * candidates.length)];
  const lvlRange = node.levels || zone.lvlRange;
  const lvl = lvlRange[0] + Math.floor(Math.random() * (lvlRange[1] - lvlRange[0] + 1));
  const enemy = createMonster(base.id, lvl);

  showScreen('screen-battle');
  setTimeout(() => initBattle(enemy, { isWild: true, zone, node }), 400);
}

function startTrainerBattle(zone, node) {
  const elPool = node.elements;
  const trainerName = node.name;
  const dialogue = node.dialogue;
  const lvlRange = node.levels || zone.lvlRange;
  const lvl = lvlRange[0] + Math.floor(Math.random() * (lvlRange[1] - lvlRange[0] + 1));

  const chosenEl = elPool[Math.floor(Math.random() * elPool.length)];
  const candidates = RESONANTS_DB.filter(r => r.element === chosenEl && r.tier <= 2);
  if (candidates.length === 0) candidates.push(...RESONANTS_DB.filter(r => r.tier <= 2));
  const base = candidates[Math.floor(Math.random() * candidates.length)];
  const enemy = createMonster(base.id, lvl);

  showScreen('screen-battle');
  setTimeout(() => initBattle(enemy, { isWild: false, isTrainer: true, trainerName, dialogue, zone, node }), 400);
}

function startBossBattle(zone, node) {
  const bossName = node.bossName || node.name;
  const dialogue = node.dialogue;
  const lvl = node.level || zone.lvlRange[1];
  const el = node.element || 'cosmos';

  // Find a tier 2-3 boss monster
  const candidates = RESONANTS_DB.filter(r => r.element === el && r.tier >= 2);
  const base = candidates.length > 0 ? candidates[Math.floor(Math.random() * candidates.length)]
    : RESONANTS_DB.filter(r => r.tier >= 2)[0];
  const enemy = createMonster(base.id, lvl);

  showScreen('screen-battle');
  setTimeout(() => initBattle(enemy, {
    isWild: false, isBoss: true, bossName, dialogue,
    rewardShards: node.rewardShards || 100,
    zone, node
  }), 400);
}

// ============================================================
// VISUAL EFFECTS
// ============================================================
function spawnParticles(side, type) {
  const layer = document.getElementById('battle-particles');
  if (!layer) return;

  const colors = {
    super: ['#FF6B35', '#FFEB3B', '#A855F7'],
    hit: ['#00BCD4', '#81D4FA', '#ffffff'],
    weak: ['#6B7280', '#9CA3AF'],
    heal: ['#4CAF50', '#8BC34A', '#C8E6C9']
  };
  const cols = colors[type] || colors.hit;
  const count = type === 'super' ? 20 : (type === 'heal' ? 12 : 14);
  const targetEl = document.getElementById(side === 'enemy' ? 'enemy-sprite-wrap' : 'player-sprite-wrap');
  const rect = targetEl.getBoundingClientRect();
  const layerRect = layer.getBoundingClientRect();
  const cx = rect.left + rect.width / 2 - layerRect.left;
  const cy = rect.top + rect.height / 2 - layerRect.top;

  for (let i = 0; i < count; i++) {
    const p = document.createElement('div');
    p.className = 'particle';
    const size = 3 + Math.random() * 8;
    const angle = Math.random() * Math.PI * 2;
    const dist = 20 + Math.random() * 60;
    const color = cols[Math.floor(Math.random() * cols.length)];
    p.style.cssText = `
      width:${size}px;height:${size}px;background:${color};
      left:${cx}px;top:${cy}px;
      box-shadow:0 0 6px ${color};
    `;
    // Use CSS variable for direction
    p.style.setProperty('--dx', Math.cos(angle) * dist + 'px');
    p.style.setProperty('--dy', Math.sin(angle) * dist + 'px');
    layer.appendChild(p);
    setTimeout(() => p.remove(), 1200);
  }
}

function shakeScreen() {
  const battleEl = document.getElementById('screen-battle');
  if (!battleEl) return;
  battleEl.classList.remove('anim-shake');
  void battleEl.offsetWidth;
  battleEl.classList.add('anim-shake');
  setTimeout(() => battleEl.classList.remove('anim-shake'), 400);
}

// ============================================================
// SHOP
// ============================================================
const SHOP_ITEMS = [
  { id: 'prism', name: 'Harmonizer Prism', desc: 'Basic capture prism.', price: 30, icon: '🔮' },
  { id: 'hyperPrism', name: 'Hyper-Conductive Prism', desc: 'High catch rate prism.', price: 80, icon: '💎' },
  { id: 'restorer', name: 'Core Restorer', desc: 'Restores 50% HP.', price: 50, icon: '💚' },
  { id: 'elixir', name: 'Resonance Elixir', desc: 'Cures all status conditions.', price: 60, icon: '✨' },
  { id: 'harmonyBoost', name: 'Harmonic Tuner', desc: 'Permanently boosts a Resonants Speed by 5.', price: 200, icon: '🎯' }
];

function renderShop() {
  document.getElementById('shop-shards').textContent = gameState.shards;
  const grid = document.getElementById('shop-items');
  let html = '';
  SHOP_ITEMS.forEach(item => {
    const owned = gameState.inventory[item.id] || 0;
    html += `<div class="glass p-4 rounded-xl flex flex-col gap-3">
      <div class="flex items-center gap-3">
        <span class="text-2xl">${item.icon}</span>
        <div>
          <p class="font-semibold text-sm">${item.name}</p>
          <p class="text-[10px] text-gray-400">${item.desc}</p>
          ${owned > 0 ? `<p class="text-[10px] text-purple-300">Owned: ${owned}</p>` : ''}
        </div>
      </div>
      <button class="btn-game btn-primary justify-center ${gameState.shards < item.price ? 'opacity-50' : ''}"
        onclick="buyItem('${item.id}')" ${gameState.shards < item.price ? 'disabled' : ''}>
        🪙 ${item.price} Shards
      </button>
    </div>`;
  });
  grid.innerHTML = html;
}

function buyItem(itemId) {
  const item = SHOP_ITEMS.find(i => i.id === itemId);
  if (!item || gameState.shards < item.price) return;

  synth.select();
  gameState.shards -= item.price;

  if (itemId === 'harmonyBoost') {
    // Apply to first conscious mon
    const mon = gameState.activeParty[0];
    if (mon) {
      mon.speed += 5;
      notify(`${mon.name}'s Speed increased!`);
    }
  } else {
    gameState.inventory[itemId] = (gameState.inventory[itemId] || 0) + 1;
    notify(`Purchased ${item.name}!`);
  }

  saveGame();
  renderShop();
  updateTopbar();
}

function openShop() { synth.select(); showScreen('screen-shop'); }

// ============================================================
// PARTY SCREEN
// ============================================================
function renderParty() {
  const partyGrid = document.getElementById('party-grid');
  let html = '';

  for (let i = 0; i < 3; i++) {
    const mon = gameState.activeParty[i];
    if (mon) {
      html += `<div class="glass p-4 rounded-xl ${elBorderClass(mon.element)}" style="border-width:2px;">
        <div class="flex justify-center mb-3">${renderResonantSVG(mon, 100)}</div>
        <p class="font-semibold text-sm ${elTextClass(mon.element)} text-center">${mon.name}</p>
        <p class="text-center text-[10px] text-gray-400">Lv.${mon.level} ${mon.element.toUpperCase()}</p>
        <div class="mt-2">
          <div class="hp-bar-track mb-2"><div class="hp-bar-fill" style="width:${Math.max(0,mon.currentHP/mon.maxHP*100)}%;"></div></div>
          <div class="xp-bar-track mb-2"><div class="xp-bar-fill" style="width:${Math.min(100,mon.xp/mon.xpToNext*100)}%;"></div></div>
          <div class="grid grid-cols-2 gap-1 text-[9px] text-gray-400">
            <span>HP: ${mon.currentHP}/${mon.maxHP}</span>
            <span>ATK: ${mon.attack}</span>
            <span>DEF: ${mon.defense}</span>
            <span>SPD: ${mon.speed}</span>
          </div>
        </div>
        <div class="mt-2 flex flex-wrap gap-1">
          ${mon.moves.slice(0, 4).map(mId => {
            const mv = MOVE_LOOKUP[mId];
            return mv ? `<span class="badge ${elBadgeClass(mv.type)} text-[8px]">${mv.name}</span>` : '';
          }).join('')}
        </div>
        ${i > 0 ? `<button class="btn-game !mt-3 w-full justify-center text-xs" onclick="swapPartySlots(0, ${i})">Move to Front</button>` : '<span class="badge badge-cosmos text-[8px] mt-3 inline-block">LEAD</span>'}
      </div>`;
    } else {
      html += `<div class="glass p-4 rounded-xl border border-dashed border-white/10 flex items-center justify-center min-h-[250px]">
        <span class="text-gray-500 text-xs">Empty Slot</span>
      </div>`;
    }
  }
  partyGrid.innerHTML = html;

  // Storage box
  const storageGrid = document.getElementById('storage-grid');
  let shtml = '';
  if (gameState.storageBox.length === 0) {
    shtml = '<p class="text-gray-500 text-xs col-span-full text-center py-4">Storage is empty</p>';
  } else {
    gameState.storageBox.forEach((mon, i) => {
      shtml += `<div class="glass-light p-2 rounded-lg cursor-pointer hover:border-purple-400/50 transition-colors" onclick="swapToParty(${i})">
        <div class="flex justify-center mb-1">${renderResonantSVG(mon, 50)}</div>
        <p class="text-[10px] text-center ${elTextClass(mon.element)}">${mon.name}</p>
        <p class="text-[9px] text-center text-gray-500">Lv.${mon.level}</p>
      </div>`;
    });
  }
  storageGrid.innerHTML = shtml;
}

function swapPartySlots(a, b) {
  synth.select();
  [gameState.activeParty[a], gameState.activeParty[b]] = [gameState.activeParty[b], gameState.activeParty[a]];
  saveGame();
  renderParty();
}

function swapToParty(storageIndex) {
  synth.select();
  if (gameState.activeParty.length >= 3) {
    notify('Party is full! Remove a Resonant first.');
    return;
  }
  const mon = gameState.storageBox.splice(storageIndex, 1)[0];
  gameState.activeParty.push(mon);
  saveGame();
  renderParty();
  notify(`${mon.name} joined the party!`);
}

function openParty() { synth.select(); showScreen('screen-party'); }

// ============================================================
// CONDUCTOR'S DEX
// ============================================================
function renderDex() {
  const grid = document.getElementById('dex-grid');
  const discovered = gameState.dexDiscovered;
  document.getElementById('dex-count').textContent = `${discovered.length}/18`;

  let html = '';
  RESONANTS_DB.forEach(r => {
    const isFound = discovered.includes(r.id);
    html += `<div class="dex-card glass-light p-3 rounded-lg cursor-pointer ${isFound ? '' : 'undiscovered'} ${isFound ? 'hover:border-purple-400/50' : ''}"
      ${isFound ? `onclick="showDexDetail('${r.id}')"` : ''}>
      <div class="flex justify-center mb-2">${isFound ? renderResonantSVG(createMonster(r.id, r.level), 60) : '<svg width="60" height="60" viewBox="0 0 60 60"><circle cx="30" cy="30" r="20" fill="rgba(100,100,100,0.2)" stroke="rgba(255,255,255,0.1)" stroke-dasharray="4"/><text x="30" y="35" text-anchor="middle" fill="rgba(255,255,255,0.2)" font-size="16">?</text></svg>'}</div>
      <p class="${isFound ? elTextClass(r.element) : 'text-gray-600'} text-[10px] text-center font-semibold">${isFound ? r.name : '???'}</p>
      ${isFound ? `<p class="text-[9px] text-center text-gray-400">#${String(RESONANTS_DB.indexOf(r)+1).padStart(2,'0')}</p>` : ''}
    </div>`;
  });
  grid.innerHTML = html;
}

function showDexDetail(id) {
  synth.select();
  const r = RESONANTS_LOOKUP[id];
  const detail = document.getElementById('dex-detail');
  detail.classList.remove('hidden');
  detail.innerHTML = `<div class="glass p-5 rounded-xl ${elBorderClass(r.element)}" style="border-width:2px;">
    <div class="flex items-start gap-5">
      <div>${renderResonantSVG(createMonster(id, r.level), 100)}</div>
      <div class="flex-1">
        <h3 class="font-pixel text-[11px] ${elTextClass(r.element)} mb-1">${r.name}</h3>
        <div class="flex gap-2 mb-2">
          <span class="badge ${elBadgeClass(r.element)}">${r.element.toUpperCase()}</span>
          <span class="badge badge-cosmos">Tier ${r.tier}</span>
          <span class="badge badge-cosmos">Lv.${r.level}+</span>
        </div>
        <p class="text-xs text-gray-300 mb-3 leading-relaxed">${r.desc}</p>
        <div class="grid grid-cols-2 gap-x-4 gap-y-1 text-[10px] mb-3">
          <span class="text-gray-400">HP: <span class="text-white">${r.baseStats.hp}</span></span>
          <span class="text-gray-400">ATK: <span class="text-white">${r.baseStats.attack}</span></span>
          <span class="text-gray-400">DEF: <span class="text-white">${r.baseStats.defense}</span></span>
          <span class="text-gray-400">SPD: <span class="text-white">${r.baseStats.speed}</span></span>
        </div>
        <div class="flex flex-wrap gap-1">
          ${r.moves.map(mId => { const mv = MOVE_LOOKUP[mId]; return mv ? `<span class="badge ${elBadgeClass(mv.type)} text-[8px]">${mv.name} ${mv.power > 0 ? '(' + mv.power + ')' : ''}</span>` : ''; }).join('')}
        </div>
        ${r.evolvesTo ? `<p class="text-[10px] text-purple-300 mt-2">Evolves into ${RESONANTS_LOOKUP[r.evolvesTo]?.name || '???'} at Lv.${r.evolveLevel}</p>` : '<p class="text-[10px] text-yellow-300 mt-2">Final Evolution</p>'}
      </div>
    </div>
  </div>`;
  detail.scrollIntoView({ behavior: 'smooth' });
}

function openDex() { synth.select(); showScreen('screen-dex'); }

// ============================================================
// SETTINGS
// ============================================================
function renderSettings() {
  document.getElementById('toggle-sfx').checked = gameState.settings.sfx;
  document.getElementById('toggle-music').checked = gameState.settings.music;
}

function openSettings() {
  synth.select();
  showScreen('screen-settings');
}

// ============================================================
// HOVER SOUND HANDLERS
// ============================================================
document.addEventListener('mouseover', (e) => {
  const btn = e.target.closest('.btn-game, .node, .dex-card');
  if (btn) synth.hover();
});

document.addEventListener('change', (e) => {
  if (e.target.id === 'toggle-sfx') {
    gameState.settings.sfx = e.target.checked;
    synth.sfxEnabled = e.target.checked;
    saveGame();
  }
  if (e.target.id === 'toggle-music') {
    gameState.settings.music = e.target.checked;
    synth.musicEnabled = e.target.checked;
    if (e.target.checked) synth.startMusic();
    else synth.stopMusic();
    saveGame();
  }
});

// ============================================================
// INITIALIZATION
// ============================================================
function startNewGame() {
  synth.init();
  synth.resume();
  synth.select();
  if (hasSave() && !confirm('Starting a new game will erase your current progress. Continue?')) return;
  // Reset
  gameState = {
    playerName: 'Conductor',
    activeParty: [],
    storageBox: [],
    inventory: { prism: 5, hyperPrism: 1, restorer: 2, elixir: 1 },
    shards: 50,
    unlockedZones: ['verdant'],
    currentZone: null,
    currentNodeIndex: 0,
    completedNodes: {},
    defeatedBosses: [],
    dexDiscovered: [],
    starterChosen: false,
    settings: { sfx: true, music: true }
  };
  uidCounter = 1;
  saveGame();
  showStarterSelection();
}

function continueGame() {
  synth.init();
  synth.resume();
  synth.select();
  if (loadGame()) {
    synth.sfxEnabled = gameState.settings.sfx !== false;
    synth.musicEnabled = gameState.settings.music !== false;
    showScreen('screen-zoneselect');
    if (gameState.settings.music) synth.startMusic();
  } else {
    notify('No save data found!');
  }
}

// Keyboard shortcut for audio init
document.addEventListener('click', () => {
  synth.init();
  synth.resume();
  if (gameState.settings.music) synth.startMusic();
}, { once: true });

// Boot
window.addEventListener('DOMContentLoaded', () => {
  // Apply settings from save
  if (hasSave()) {
    loadGame();
    document.getElementById('btn-continue').disabled = false;
    document.getElementById('btn-continue').classList.remove('opacity-40');
    synth.sfxEnabled = gameState.settings.sfx !== false;
    synth.musicEnabled = gameState.settings.music !== false;
  }
  showScreen('screen-menu');
});

// Auto-save on unload
window.addEventListener('beforeunload', saveGame);

// Prevent context menu to feel more game-like
document.addEventListener('contextmenu', e => {
  if (e.target.closest('#app')) e.preventDefault();
});

</script>
</body>
</html>
<!-- agent-meta {"model":"meituan/longcat-2.0","provider":"openrouter","persona":"full","sessionId":"6b4880b1-f4e6-45dc-b8c3-9d860357c113","tokensIn":1353553,"tokensOut":49723,"tokensTotal":1403276,"turns":22,"toolCalls":23,"failedToolCalls":1,"timestamp":"2026-07-20T20:05:08.271Z"} -->