← Aethelgard results

Aethelgard

ornith 1.0 35b · typescript

Initial view of Aethelgard

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 161,457 bytes · SHA-256 4dc3615c68f6
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aethelgard: Resonant Chronicles</title>
<script src="https://cdn.tailwindcss.com/"></script>
<style>
/* ── css: base ── */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Press+Start+2P&display=swap');

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; }
body {
  background: #0f0a1e;
  background-image: 
    radial-gradient(ellipse at 20% 50%, rgba(88, 28, 135, 0.15) 0%, transparent 60%),
    radial-gradient(ellipse at 80% 20%, rgba(15, 118, 110, 0.1) 0%, transparent 50%),
    radial-gradient(ellipse at 50% 80%, rgba(139, 92, 246, 0.08) 0%, transparent 50%);
  font-family: 'Inter', sans-serif;
  color: #e2e8f0;
  user-select: none;
  -webkit-user-select: none;
}

#app { width: 100%; height: 100vh; }

::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: rgba(15, 23, 42, 0.3); }
::-webkit-scrollbar-thumb { background: rgba(139, 92, 246, 0.3); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(139, 92, 246, 0.5); }

canvas { display: block; }

.screen { transition: opacity 0.4s ease, transform 0.4s ease; }
.screen.hidden { 
  opacity: 0; 
  pointer-events: none; 
  transform: scale(0.98);
}
/* ── css: glassmorphism ── */
.glass-card {
  background: rgba(15, 23, 42, 0.6);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 
    0 8px 32px rgba(0, 0, 0, 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.glass-bar {
  background: rgba(15, 23, 42, 0.8);
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.glass-panel {
  background: rgba(15, 23, 42, 0.7);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
}

.glass-badge {
  background: rgba(30, 41, 59, 0.5);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.06);
}

.glass-icon-btn {
  background: rgba(30, 41, 59, 0.4);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.06);
}

.glass-icon-btn:hover {
  background: rgba(30, 41, 59, 0.7);
  border-color: rgba(255, 255, 255, 0.12);
}
/* ── css: element-themes ── */
.element-pyro { border-color: rgba(249, 115, 22, 0.3) !important; box-shadow: 0 0 20px rgba(249, 115, 22, 0.1), inset 0 0 20px rgba(249, 115, 22, 0.02); }
.element-hydro { border-color: rgba(6, 182, 212, 0.3) !important; box-shadow: 0 0 20px rgba(6, 182, 212, 0.1), inset 0 0 20px rgba(6, 182, 212, 0.02); }
.element-flora { border-color: rgba(34, 197, 94, 0.3) !important; box-shadow: 0 0 20px rgba(34, 197, 94, 0.1), inset 0 0 20px rgba(34, 197, 94, 0.02); }
.element-volt { border-color: rgba(250, 204, 21, 0.3) !important; box-shadow: 0 0 20px rgba(250, 204, 21, 0.1), inset 0 0 20px rgba(250, 204, 21, 0.02); }
.element-zephyr { border-color: rgba(167, 139, 250, 0.3) !important; box-shadow: 0 0 20px rgba(167, 139, 250, 0.1), inset 0 0 20px rgba(167, 139, 250, 0.02); }
.element-earth { border-color: rgba(161, 98, 7, 0.3) !important; box-shadow: 0 0 20px rgba(161, 98, 7, 0.1), inset 0 0 20px rgba(161, 98, 7, 0.02); }
.element-cosmos { border-color: rgba(168, 85, 247, 0.3) !important; box-shadow: 0 0 20px rgba(168, 85, 247, 0.1), inset 0 0 20px rgba(168, 85, 247, 0.02); }

.type-pyro { color: #f97316; }
.type-hydro { color: #06b6d4; }
.type-flora { color: #22c55e; }
.type-volt { color: #facc15; }
.type-zephyr { color: #a78bfa; }
.type-earth { color: #a16207; }
.type-cosmos { color: #a855f7; }

.bg-pyro { background: linear-gradient(135deg, rgba(249, 115, 22, 0.15), rgba(234, 88, 12, 0.05)); }
.bg-hydro { background: linear-gradient(135deg, rgba(6, 182, 212, 0.15), rgba(8, 145, 178, 0.05)); }
.bg-flora { background: linear-gradient(135deg, rgba(34, 197, 94, 0.15), rgba(22, 163, 74, 0.05)); }
.bg-volt { background: linear-gradient(135deg, rgba(250, 204, 21, 0.15), rgba(234, 179, 8, 0.05)); }
.bg-zephyr { background: linear-gradient(135deg, rgba(167, 139, 250, 0.15), rgba(139, 92, 246, 0.05)); }
.bg-earth { background: linear-gradient(135deg, rgba(161, 98, 7, 0.15), rgba(133, 77, 14, 0.05)); }
.bg-cosmos { background: linear-gradient(135deg, rgba(168, 85, 247, 0.15), rgba(147, 51, 234, 0.05)); }

.type-glow-pyro { text-shadow: 0 0 10px rgba(249, 115, 22, 0.5); }
.type-glow-hydro { text-shadow: 0 0 10px rgba(6, 182, 212, 0.5); }
.type-glow-flora { text-shadow: 0 0 10px rgba(34, 197, 94, 0.5); }
.type-glow-volt { text-shadow: 0 0 10px rgba(250, 204, 21, 0.5); }
.type-glow-zephyr { text-shadow: 0 0 10px rgba(167, 139, 250, 0.5); }
.type-glow-earth { text-shadow: 0 0 10px rgba(161, 98, 7, 0.5); }
.type-glow-cosmos { text-shadow: 0 0 10px rgba(168, 85, 247, 0.5); }
/* ── css: hud ── */
.hp-bar-container { position: relative; }
.hp-bar { background: rgba(15, 23, 42, 0.8); border-radius: 9999px; overflow: hidden; }
.hp-fill { 
  background: linear-gradient(90deg, #22c55e, #4ade80);
  box-shadow: 0 0 10px rgba(34, 197, 94, 0.3);
  transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease;
}
.hp-fill.mid { 
  background: linear-gradient(90deg, #eab308, #facc15);
  box-shadow: 0 0 10px rgba(234, 179, 8, 0.3);
}
.hp-fill.low { 
  background: linear-gradient(90deg, #ef4444, #f87171);
  box-shadow: 0 0 10px rgba(239, 68, 68, 0.3);
}

.xp-bar { background: rgba(15, 23, 42, 0.8); border-radius: 9999px; overflow: hidden; }

.status-badge { 
  display: inline-flex; align-items: center; gap: 4px; 
  padding: 2px 8px; border-radius: 9999px; font-size: 10px;
  background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(255,255,255,0.08);
}

.party-slot {
  width: 56px; height: 56px; border-radius: 12px;
  background: rgba(30, 41, 59, 0.5); backdrop-filter: blur(8px);
  border: 2px solid rgba(255, 255, 255, 0.08);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: all 0.2s ease; position: relative;
}
.party-slot:hover { border-color: rgba(139, 92, 246, 0.4); background: rgba(30, 41, 59, 0.7); }
.party-slot.active { border-color: rgba(6, 182, 212, 0.6); box-shadow: 0 0 12px rgba(6, 182, 212, 0.2); }
.party-slot.fainted { opacity: 0.3; border-color: rgba(239, 68, 68, 0.3); }

.dex-card {
  background: rgba(15, 23, 42, 0.5); backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 16px;
  padding: 12px; cursor: pointer; transition: all 0.2s ease;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
}
.dex-card:hover { 
  background: rgba(30, 41, 59, 0.7); border-color: rgba(139, 92, 246, 0.3);
  transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.dex-card.locked { opacity: 0.3; cursor: default; }
.dex-card.locked:hover { transform: none; border-color: rgba(255,255,255,0.06); }

.shop-item {
  background: rgba(15, 23, 42, 0.5); backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 16px;
  padding: 16px; transition: all 0.2s ease; display: flex; flex-direction: column; gap: 8px;
}
.shop-item:hover { 
  background: rgba(30, 41, 59, 0.7); border-color: rgba(250, 204, 21, 0.3);
  transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}

.menu-btn { position: relative; overflow: hidden; }
.menu-btn::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(135deg, transparent 40%, rgba(255,255,255,0.1) 50%, transparent 60%);
  transform: translateX(-100%); transition: transform 0.5s ease;
}
.menu-btn:hover::after { transform: translateX(100%); }

.action-btn { position: relative; overflow: hidden; }
.action-btn:hover { border-color: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.05); }

.zone-btn.active { 
  background: rgba(139, 92, 246, 0.15);
  box-shadow: 0 0 16px rgba(139, 92, 246, 0.15);
}

.prism-btn:hover { border-color: rgba(6, 182, 212, 0.4); background: rgba(6, 182, 212, 0.05); }

.dex-mini-sprite { width: 48px; height: 48px; }
/* ── css: animations ── */
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  10% { transform: translateX(-8px) rotate(-1deg); }
  20% { transform: translateX(6px) rotate(1deg); }
  30% { transform: translateX(-5px); }
  40% { transform: translateX(4px); }
  50% { transform: translateX(-3px); }
  60% { transform: translateX(2px); }
  70% { transform: translateX(-1px); }
}

@keyframes flash-white {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.2; }
}

@keyframes bounce-in {
  0% { transform: scale(0.3); opacity: 0; }
  50% { transform: scale(1.05); }
  70% { transform: scale(0.95); }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes float {
  0%, 100% { transform: translateY(0px); }
  50% { transform: translateY(-8px); }
}

@keyframes pulse-glow {
  0%, 100% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.2); }
  50% { box-shadow: 0 0 40px rgba(139, 92, 246, 0.4); }
}

@keyframes sparkle {
  0%, 100% { opacity: 0; transform: scale(0) rotate(0deg); }
  50% { opacity: 1; transform: scale(1) rotate(180deg); }
}

@keyframes prism-capture {
  0% { transform: scale(1) rotate(0deg); filter: brightness(1); }
  25% { transform: scale(1.3) rotate(90deg); filter: brightness(2); }
  50% { transform: scale(0.8) rotate(180deg); filter: brightness(1.5); }
  75% { transform: scale(1.2) rotate(270deg); filter: brightness(2); }
  100% { transform: scale(0.1) rotate(360deg); filter: brightness(3); }
}

@keyframes evolutions-flash {
  0%, 100% { opacity: 1; filter: brightness(1); }
  25% { opacity: 0.5; filter: brightness(3) hue-rotate(90deg); }
  50% { opacity: 1; filter: brightness(1); }
  75% { opacity: 0.5; filter: brightness(3) hue-rotate(-90deg); }
}

@keyframes particle-float {
  0% { transform: translateY(0) translateX(0); opacity: 1; }
  100% { transform: translateY(-200px) translateX(var(--drift)); opacity: 0; }
}

.shake { animation: shake 0.5s ease-in-out; }
.flash-white { animation: flash-white 0.3s ease-in-out; }
.bounce-in { animation: bounce-in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); }
.float { animation: float 3s ease-in-out infinite; }
.pulse-glow { animation: pulse-glow 2s ease-in-out infinite; }

.battle-sprite { 
  transition: transform 0.3s ease, filter 0.3s ease;
  filter: drop-shadow(0 4px 12px rgba(0,0,0,0.4));
}
.battle-sprite.hit { animation: shake 0.4s ease-in-out; }
.battle-sprite.attack-right { transform: translateX(30px); }
.battle-sprite.attack-left { transform: translateX(-30px); }
.battle-sprite.faint { 
  opacity: 0; transform: translateY(20px) scale(0.8);
  transition: all 0.6s ease-in; filter: grayscale(1);
}

.particle {
  position: absolute; pointer-events: none; border-radius: 50%;
  animation: particle-float 1.5s ease-out forwards;
}

.toast {
  background: rgba(15, 23, 42, 0.9); backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px;
  padding: 12px 20px; color: #e2e8f0; font-size: 14px;
  animation: bounce-in 0.3s ease-out; box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}

.dialog-overlay-enter {
  animation: slide-up 0.3s ease-out;
}

@keyframes slide-up {
  from { transform: translateY(100%); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

.map-node {
  cursor: pointer; transition: all 0.2s ease;
  filter: drop-shadow(0 2px 8px rgba(0,0,0,0.3));
}
.map-node:hover { transform: scale(1.15); filter: drop-shadow(0 4px 16px rgba(139, 92, 246, 0.4)); }
.map-node.current { animation: pulse-glow 2s ease-in-out infinite; }
.map-node.cleared { opacity: 0.5; cursor: default; }
.map-node.cleared:hover { transform: none; filter: none; }

.evolution-flash { animation: evolutions-flash 1.5s ease-in-out; }
.prism-spin { animation: prism-capture 2s ease-in-out forwards; }
</style>
</head>
<body>
<!-- Main Menu Screen -->
<div id="screen-menu" class="screen absolute inset-0 flex items-center justify-center z-10">
  <div class="glass-card text-center p-12 rounded-3xl border border-white/10 shadow-2xl max-w-md w-full mx-4">
    <h1 class="text-5xl font-bold mb-2 bg-gradient-to-r from-violet-400 via-cyan-400 to-emerald-400 bg-clip-text text-transparent" style="font-family: 'Press Start 2P', cursive;">AETHELGARD</h1>
    <p class="text-slate-400 mb-8 text-sm tracking-widest uppercase">Resonant Chronicles</p>
    <div class="space-y-3">
      <button id="btn-new-game" class="menu-btn w-full py-3 px-6 rounded-xl bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-500 hover:to-indigo-500 text-white font-semibold transition-all duration-200 shadow-lg shadow-violet-500/25 border border-white/10">New Journey</button>
      <button id="btn-load-game" class="menu-btn w-full py-3 px-6 rounded-xl bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 text-white font-semibold transition-all duration-200 shadow-lg shadow-emerald-500/25 border border-white/10">Continue</button>
      <button id="btn-reset" class="menu-btn w-full py-3 px-6 rounded-xl bg-slate-800/50 hover:bg-slate-700/50 text-slate-400 hover:text-red-400 font-semibold transition-all duration-200 border border-white/5">Reset Progress</button>
    </div>
  </div>
</div>

<!-- Overworld Screen -->
<div id="screen-overworld" class="screen hidden absolute inset-0 flex flex-col z-10">
  <div class="glass-bar px-6 py-3 flex items-center justify-between border-b border-white/5">
    <div class="flex items-center gap-4">
      <span id="player-name" class="text-cyan-400 font-bold text-sm">Conductor</span>
      <span class="text-slate-500">|</span>
      <span id="zone-name" class="text-emerald-400 text-sm">Verdant Canopy</span>
    </div>
    <div class="flex items-center gap-3">
      <div class="glass-badge flex items-center gap-1.5 px-3 py-1 rounded-full">
        <svg class="w-4 h-4 text-yellow-400" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path></svg>
        <span id="shard-count" class="text-yellow-400 text-sm font-bold">0</span>
      </div>
      <button id="btn-dex" class="glass-icon-btn p-2 rounded-lg hover:bg-white/5 transition-colors" title="Resonants Dex">
        <svg class="w-5 h-5 text-violet-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
      </button>
      <button id="btn-shop" class="glass-icon-btn p-2 rounded-lg hover:bg-white/5 transition-colors" title="Shop">
        <svg class="w-5 h-5 text-amber-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>
      </button>
    </div>
  </div>

  <div id="map-container" class="flex-1 relative overflow-hidden">
    <canvas id="map-canvas"></canvas>
  </div>

  <div id="overworld-bottom" class="glass-panel border-t border-white/5 p-4 flex items-center justify-between gap-4">
    <div id="party-display" class="flex items-center gap-2 flex-1"></div>
    <div id="event-text" class="text-slate-300 text-sm max-w-md flex-1 italic opacity-0 transition-opacity duration-300"></div>
  </div>

  <div id="zone-nav" class="glass-panel border-t border-white/5 p-3 hidden">
    <div class="flex items-center justify-center gap-2 flex-wrap">
      <button data-zone="0" class="zone-btn glass-badge px-4 py-2 rounded-xl text-xs font-semibold transition-all border border-emerald-500/30 text-emerald-400 hover:bg-emerald-500/10">Canopy</button>
      <button data-zone="1" class="zone-btn glass-badge px-4 py-2 rounded-xl text-xs font-semibold transition-all border border-blue-500/30 text-blue-400 opacity-50 cursor-not-allowed" disabled="">Grotto</button>
      <button data-zone="2" class="zone-btn glass-badge px-4 py-2 rounded-xl text-xs font-semibold transition-all border border-red-500/30 text-red-400 opacity-50 cursor-not-allowed" disabled="">Crag</button>
      <button data-zone="3" class="zone-btn glass-badge px-4 py-2 rounded-xl text-xs font-semibold transition-all border border-purple-500/30 text-purple-400 opacity-50 cursor-not-allowed" disabled="">Pillar</button>
    </div>
  </div>
</div>

<!-- Battle Screen -->
<div id="screen-battle" class="screen hidden absolute inset-0 flex flex-col z-20">
  <div id="battle-arena" class="flex-1 relative overflow-hidden">
    <canvas id="battle-canvas"></canvas>
    <div id="enemy-info" class="absolute top-6 right-6 glass-card p-4 rounded-2xl border border-red-500/20 min-w-[220px]">
      <div class="flex items-center justify-between mb-2">
        <span id="enemy-name" class="text-white font-bold text-sm"></span>
        <span id="enemy-level" class="text-slate-400 text-xs"></span>
      </div>
      <div class="hp-bar-container">
        <div class="flex justify-between text-xs mb-1">
          <span id="enemy-hp-text" class="text-red-400"></span>
          <span id="enemy-hp-max" class="text-slate-500"></span>
        </div>
        <div class="hp-bar bg-slate-800/80 rounded-full h-2.5 overflow-hidden">
          <div id="enemy-hp-fill" class="hp-fill h-full rounded-full transition-all duration-500" style="width:100%"></div>
        </div>
      </div>
      <div id="enemy-status" class="mt-2 text-xs text-slate-400"></div>
    </div>

    <div id="player-info" class="absolute bottom-36 left-6 glass-card p-4 rounded-2xl border border-cyan-500/20 min-w-[240px]">
      <div class="flex items-center justify-between mb-2">
        <span id="player-resonant-name" class="text-white font-bold text-sm"></span>
        <span id="player-resonant-level" class="text-slate-400 text-xs"></span>
      </div>
      <div class="hp-bar-container">
        <div class="flex justify-between text-xs mb-1">
          <span id="player-hp-text" class="text-cyan-400"></span>
          <span id="player-hp-max" class="text-slate-500"></span>
        </div>
        <div class="hp-bar bg-slate-800/80 rounded-full h-2.5 overflow-hidden">
          <div id="player-hp-fill" class="hp-fill h-full rounded-full transition-all duration-500" style="width:100%"></div>
        </div>
      </div>
      <div id="player-xp-bar" class="mt-2">
        <div class="flex justify-between text-xs mb-1">
          <span class="text-violet-400">XP</span>
          <span id="player-xp-text" class="text-slate-500"></span>
        </div>
        <div class="xp-bar bg-slate-800/80 rounded-full h-1.5 overflow-hidden">
          <div id="player-xp-fill" class="xp-fill h-full bg-gradient-to-r from-violet-500 to-indigo-500 rounded-full transition-all duration-300" style="width:0%"></div>
        </div>
      </div>
      <div id="player-status" class="mt-1.5 text-xs text-slate-400"></div>
    </div>

    <div id="battle-message" class="absolute top-6 left-6 glass-card px-5 py-3 rounded-2xl border border-white/10 max-w-sm">
      <p id="battle-msg-text" class="text-slate-200 text-sm"></p>
    </div>

    <div id="battle-particles" class="absolute inset-0 pointer-events-none overflow-hidden"></div>
  </div>

  <div id="battle-actions" class="glass-panel border-t border-white/5 p-4">
    <div id="action-buttons" class="grid grid-cols-2 gap-3 max-w-lg mx-auto">
      <button data-action="attack" class="action-btn glass-card p-3 rounded-xl text-center hover:bg-white/5 transition-all border border-white/10 group">
        <span class="text-red-400 font-bold text-sm block group-hover:scale-105 transition-transform">⚔ Attack</span>
      </button>
      <button data-action="prism" class="action-btn glass-card p-3 rounded-xl text-center hover:bg-white/5 transition-all border border-white/10 group">
        <span class="text-cyan-400 font-bold text-sm block group-hover:scale-105 transition-transform">◇ Prism</span>
      </button>
      <button data-action="exchange" class="action-btn glass-card p-3 rounded-xl text-center hover:bg-white/5 transition-all border border-white/10 group">
        <span class="text-emerald-400 font-bold text-sm block group-hover:scale-105 transition-transform">↔ Exchange</span>
      </button>
      <button data-action="inventory" class="action-btn glass-card p-3 rounded-xl text-center hover:bg-white/5 transition-all border border-white/10 group">
        <span class="text-amber-400 font-bold text-sm block group-hover:scale-105 transition-transform">✦ Items</span>
      </button>
    </div>

    <div id="submenu-prism" class="hidden max-w-lg mx-auto space-y-2">
      <p class="text-slate-400 text-xs mb-2">Select Prism Type:</p>
      <div class="grid grid-cols-3 gap-2">
        <button data-prism-type="basic" class="prism-btn glass-card p-3 rounded-xl text-center hover:bg-white/5 transition-all border border-cyan-500/20">
          <span class="text-cyan-400 font-bold text-xs block">Aether Prism</span>
          <span class="text-slate-500 text-[10px]">Catch Rate: 1.0×</span>
        </button>
        <button data-prism-type="hyper" class="prism-btn glass-card p-3 rounded-xl text-center hover:bg-white/5 transition-all border border-violet-500/20">
          <span class="text-violet-400 font-bold text-xs block">Hyper Prism</span>
          <span class="text-slate-500 text-[10px]">Catch Rate: 2.5×</span>
        </button>
        <button data-prism-type="return" class="prism-btn glass-card p-3 rounded-xl text-center hover:bg-white/5 transition-all border border-red-500/20">
          <span class="text-red-400 font-bold text-xs block">Cancel</span>
        </button>
      </div>
    </div>

    <div id="submenu-exchange" class="hidden max-w-lg mx-auto space-y-2">
      <p class="text-slate-400 text-xs mb-2">Select Resonant to swap:</p>
      <div id="exchange-list" class="grid grid-cols-1 gap-2"></div>
    </div>

    <div id="submenu-inventory" class="hidden max-w-lg mx-auto space-y-2">
      <p class="text-slate-400 text-xs mb-2">Select Item:</p>
      <div id="inventory-list" class="grid grid-cols-1 gap-2"></div>
    </div>

    <button id="battle-back-btn" class="hidden mx-auto mt-3 glass-badge px-4 py-2 rounded-xl text-xs text-slate-400 hover:text-white transition-colors border border-white/5">← Back</button>
  </div>
</div>

<!-- Dex Screen -->
<div id="screen-dex" class="screen hidden absolute inset-0 flex flex-col z-30 bg-slate-950/95 backdrop-blur-xl">
  <div class="glass-bar px-6 py-3 flex items-center justify-between border-b border-white/5">
    <h2 class="text-violet-400 font-bold text-lg" style="font-family: 'Press Start 2P', cursive; font-size:14px;">Resonants Dex</h2>
    <button id="btn-dex-close" class="glass-icon-btn p-2 rounded-lg hover:bg-white/5 transition-colors">
      <svg class="w-5 h-5 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2:="18"></line></svg>
    </button>
  </div>
  <div id="dex-grid" class="flex-1 overflow-y-auto p-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3"></div>
</div>

<!-- Shop Screen -->
<div id="screen-shop" class="screen hidden absolute inset-0 flex flex-col z-30 bg-slate-950/95 backdrop-blur-xl">
  <div class="glass-bar px-6 py-3 flex items-center justify-between border-b border-white/5">
    <h2 class="text-amber-400 font-bold text-lg" style="font-family: 'Press Start 2P', cursive; font-size:14px;">Camp Shop</h2>
    <div class="flex items-center gap-3">
      <span id="shop-shards" class="text-yellow-400 text-sm font-bold"></span>
      <button id="btn-shop-close" class="glass-icon-btn p-2 rounded-lg hover:bg-white/5 transition-colors">
        <svg class="w-5 h-5 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2:="18"></line><line x1="6" y1="6" x2="18" y2:="18"></line></svg>
      </button>
    </div>
  </div>
  <div id="shop-grid" class="flex-1 overflow-y-auto p-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 max-w-3xl mx-auto w-full"></div>
</div>

<!-- Encounter Overlay -->
<div id="encounter-overlay" class="hidden absolute inset-0 z-40 bg-black/70 backdrop-blur-sm flex items-center justify-center">
  <div class="glass-card p-8 rounded-3xl border border-white/10 text-center max-w-sm w-full mx-4 animate-bounce-in">
    <p id="encounter-text" class="text-slate-200 text-lg mb-6"></p>
    <button id="encounter-btn" class="px-8 py-3 rounded-xl bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-500 hover:to-indigo-500 text-white font-semibold transition-all shadow-lg shadow-violet-500/25">Continue</button>
  </div>
</div>

<!-- Level Up Overlay -->
<div id="levelup-overlay" class="hidden absolute inset-0 z-40 bg-black/70 backdrop-blur-sm flex items-center justify-center">
  <div class="glass-card p-8 rounded-3xl border border-violet-500/30 text-center max-w-sm w-full mx-4">
    <h3 class="text-violet-400 font-bold text-xl mb-2" style="font-family: 'Press Start 2P', cursive; font-size:16px;">LEVEL UP!</h3>
    <p id="levelup-text" class="text-slate-200 mb-4"></p>
    <button id="levelup-btn" class="px-8 py-3 rounded-xl bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-500 hover:to-indigo-500 text-white font-semibold transition-all shadow-lg">Continue</button>
  </div>
</div>

<!-- Evolution Overlay -->
<div id="evolution-overlay" class="hidden absolute inset-0 z-40 bg-black/80 backdrop-blur-sm flex items-center justify-center">
  <div class="text-center max-w-sm w-full mx-4">
    <h3 class="text-yellow-400 font-bold text-xl mb-4" style="font-family: 'Press Start 2P', cursive; font-size:16px;">EVOLVING!</h3>
    <div id="evolution-sprite-container" class="w-48 h-48 mx-auto mb-4"></div>
    <p id="evolution-text" class="text-slate-200 text-lg mb-6"></p>
    <button id="evolution-btn" class="px-8 py-3 rounded-xl bg-gradient-to-r from-yellow-600 to-orange-600 hover:from-yellow-500 hover:to-orange-500 text-white font-semibold transition-all shadow-lg">Continue</button>
  </div>
</div>

<!-- Capture Animation Overlay -->
<div id="capture-overlay" class="hidden absolute inset-0 z-40 bg-black/70 backdrop-blur-sm flex items-center justify-center">
  <div class="text-center max-w-sm w-full mx-4">
    <div id="capture-prism" class="w-32 h-32 mx-auto mb-6"></div>
    <p id="capture-text" class="text-slate-200 text-lg"></p>
  </div>
</div>

<!-- Dialog Overlay -->
<div id="dialog-overlay" class="hidden absolute inset-x-0 bottom-0 z-30 p-4">
  <div class="glass-card p-5 rounded-2xl border border-white/10 max-w-2xl mx-auto">
    <div class="flex items-start gap-3">
      <div id="dialog-avatar" class="w-12 h-12 rounded-full bg-slate-700 flex-shrink-0 flex items-center justify-center text-xl"></div>
      <div class="flex-1">
        <p id="dialog-speaker" class="text-cyan-400 font-bold text-sm mb-1"></p>
        <p id="dialog-text" class="text-slate-200 text-sm leading-relaxed"></p>
      </div>
    </div>
    <button id="dialog-next" class="mt-3 ml-auto block text-slate-500 hover:text-white transition-colors text-xs">▼ Continue</button>
  </div>
</div>

<!-- Notification Toast -->
<div id="toast-container" class="fixed top-4 right-4 z-50 space-y-2"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./resonants":"src/resonants.ts","./types":"src/types.ts","./sounds":"src/sounds.ts","./state":"src/state.ts","./combat":"src/combat.ts","./map":"src/map.ts","./ui":"src/ui.ts"},"src/resonants.ts":{"./types":"src/types.ts"},"src/state.ts":{"./types":"src/types.ts","./resonants":"src/resonants.ts"},"src/map.ts":{"./types":"src/types.ts","./resonants":"src/resonants.ts"},"src/combat.ts":{"./types":"src/types.ts","./resonants":"src/resonants.ts"},"src/ui.ts":{"./types":"src/types.ts","./resonants":"src/resonants.ts","./map":"src/map.ts","./combat":"src/combat.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const resonants_1 = require("./resonants");
const types_1 = require("./types");
const sounds_1 = require("./sounds");
const state_1 = require("./state");
const combat_1 = require("./combat");
const map_1 = require("./map");
const ui = __importStar(require("./ui"));
// === GAME STATE ===
let gameState;
let currentBattle = null;
let isProcessing = false;
let pendingCallback = null;
function init() {
    sounds_1.synth.init();
    // Check for save
    const saved = (0, state_1.getSaveData)();
    if (saved) {
        gameState = saved;
        ui.initMenuScreen(true);
    }
    else {
        gameState = (0, state_1.createInitialState)();
        ui.initMenuScreen(false);
    }
    // Wire up menu buttons
    document.getElementById('btn-new-game').addEventListener('click', () => {
        sounds_1.synth.playSelect();
        startNewGame();
    });
    document.getElementById('btn-load-game').addEventListener('click', () => {
        sounds_1.synth.playSelect();
        if (saved) {
            gameState = saved;
            enterOverworld();
        }
    });
    document.getElementById('btn-reset').addEventListener('click', () => {
        if (confirm('Reset all progress? This cannot be undone.')) {
            (0, state_1.resetSave)();
            location.reload();
        }
    });
    // Dex button
    document.getElementById('btn-dex').addEventListener('click', () => {
        sounds_1.synth.playSelect();
        renderDexScreen();
        ui.showScreen('screen-dex');
    });
    // Shop button
    document.getElementById('btn-shop').addEventListener('click', () => {
        sounds_1.synth.playSelect();
        renderShopScreen();
        ui.showScreen('screen-shop');
    });
    // Dex close
    document.getElementById('btn-dex-close').addEventListener('click', () => {
        sounds_1.synth.playSelect();
        ui.showScreen('screen-overworld');
    });
    // Shop close
    document.getElementById('btn-shop-close').addEventListener('click', () => {
        sounds_1.synth.playSelect();
        ui.showScreen('screen-overworld');
    });
    // Zone navigation
    document.querySelectorAll('.zone-btn').forEach(btn => {
        btn.addEventListener('click', () => {
            const zoneIdx = parseInt(btn.dataset.zone || '0');
            if (gameState.unlockedZones[zoneIdx]) {
                sounds_1.synth.playSelect();
                gameState.currentZone = zoneIdx;
                // Find first non-cleared node
                const zone = map_1.ZONES[zoneIdx];
                const nextNode = zone.nodes.findIndex(n => !n.cleared);
                gameState.currentNode = nextNode >= 0 ? nextNode : 0;
                ui.renderOverworld(gameState);
                (0, state_1.saveGame)(gameState);
            }
        });
    });
    // Battle action buttons
    document.querySelectorAll('.action-btn').forEach(btn => {
        btn.addEventListener('click', () => handleBattleAction(btn.dataset.action || ''));
    });
    // Prism sub-menu
    document.querySelectorAll('.prism-btn').forEach(btn => {
        btn.addEventListener('click', () => {
            const type = btn.dataset.prismType;
            if (type === 'return') {
                hideSubMenus();
            }
            else if (type) {
                usePrism(type);
            }
        });
    });
    // Battle back button
    document.getElementById('battle-back-btn').addEventListener('click', () => {
        hideSubMenus();
    });
    // Map click handler
    ui.initMap();
    ui.setupMapClickHandler(handleNodeClick);
    // Window resize
    window.addEventListener('resize', () => {
        ui.initMap();
        if (!document.getElementById('screen-overworld')?.classList.contains('hidden')) {
            ui.renderOverworld(gameState);
        }
    });
    // Show starter selection if no party
    if (gameState.party.length === 0) {
        showStarterSelection();
    }
    else {
        enterOverworld();
    }
}
function startNewGame() {
    gameState = (0, state_1.createInitialState)();
    showStarterSelection();
}
function showStarterSelection() {
    const starters = [
        { id: 'emberkit', name: 'Emberkit', element: 'pyro', desc: 'A fiery spirit with a restless heart.' },
        { id: 'droplet', name: 'Droplet', element: 'hydro', desc: 'A fluid essence of pure water resonance.' },
        { id: 'seedling', name: 'Seedling', element: 'flora', desc: 'A nascent nature spirit full of potential.' }
    ];
    const overlay = document.getElementById('encounter-overlay');
    if (!overlay)
        return;
    document.getElementById('encounter-text').innerHTML = `
    <div class="space-y-4 text-left">
      <p class="text-center text-slate-300 mb-4">Choose your first Resonant companion:</p>
      ${starters.map((s, i) => `
        <button data-starter="${i}" class="starter-btn w-full p-4 rounded-xl glass-card border border-white/10 hover:border-violet-500/40 transition-all text-left group">
          <div class="flex items-center gap-3">
            <span class="text-2xl">${s.element === 'pyro' ? '🔥' : s.element === 'hydro' ? '💧' : '🌱'}</span>
            <div>
              <h3 class="type-${s.element} font-bold">${s.name}</h3>
              <p class="text-slate-400 text-xs">${s.desc}</p>
            </div>
          </div>
        </button>
      `).join('')}
    </div>`;
    overlay.classList.remove('hidden');
    // Wire starter buttons
    document.querySelectorAll('.starter-btn').forEach(btn => {
        btn.addEventListener('click', () => {
            const idx = parseInt(btn.dataset.starter || '0');
            const starter = starters[idx];
            const resonant = (0, resonants_1.createResonantInstance)(starter.id, 1);
            gameState.party.push(resonant);
            gameState.dexUnlocked.push(starter.id);
            sounds_1.synth.playSelect();
            overlay.classList.add('hidden');
            enterOverworld();
        });
    });
}
function enterOverworld() {
    ui.showScreen('screen-overworld');
    // Small delay to let layout settle, then init map with proper dimensions
    requestAnimationFrame(() => {
        ui.resizeMap();
        ui.renderOverworld(gameState);
    });
    (0, state_1.saveGame)(gameState);
}
// === NODE INTERACTION ===
async function handleNodeClick(zoneIdx, nodeIdx) {
    if (isProcessing)
        return;
    const zone = map_1.ZONES[zoneIdx];
    if (!zone || !zone.nodes[nodeIdx])
        return;
    const node = zone.nodes[nodeIdx];
    // Must be adjacent or first node
    const playerNode = gameState.currentNode;
    if (nodeIdx !== playerNode && !(nodeIdx === playerNode + 1) && nodeIdx !== 0) {
        ui.showToast('Move to an adjacent node first!', 'info');
        return;
    }
    isProcessing = true;
    switch (node.type) {
        case 'wild':
            await handleWildEncounter(zoneIdx, node);
            break;
        case 'mystery':
            await handleMysteryCache(node);
            break;
        case 'npc':
            await handleNPCDialog(node);
            break;
        case 'healing':
            await handleHealingSpring();
            break;
        case 'boss':
            await handleBossBattle(zoneIdx, node);
            break;
    }
    // Move to this node
    gameState.currentNode = nodeIdx;
    if (node.type !== 'boss') {
        node.cleared = true;
    }
    // Check if zone is complete
    checkZoneProgress(zoneIdx);
    ui.renderOverworld(gameState);
    (0, state_1.saveGame)(gameState);
    isProcessing = false;
}
async function handleWildEncounter(zoneIdx, node) {
    const encounter = (0, map_1.getNodeEncounter)(node.encounterId);
    if (!encounter)
        return;
    const template = (0, resonants_1.getResonantTemplate)(encounter.resonantId);
    if (!template)
        return;
    ui.showEncounter(`A wild ${template.name} appeared!`, async () => {
        await startWildBattle(encounter.resonantId, encounter.level);
    });
}
async function startWildBattle(resonantId, level) {
    const ctx = {
        playerParty: gameState.party,
        playerBox: gameState.box,
        inventory: [...gameState.inventory],
        shards: gameState.shards
    };
    (0, combat_1.setCombatContext)(ctx);
    currentBattle = (0, combat_1.startBattle)(resonantId, level, true);
    currentBattle.message = `A wild ${(0, resonants_1.getResonantTemplate)(resonantId).name} appeared!`;
    ui.showScreen('screen-battle');
    requestAnimationFrame(() => {
        ui.resizeBattle();
        if (currentBattle)
            ui.renderBattle(currentBattle);
    });
    // Update player info in battle
    updateBattlePlayerInfo();
    sounds_1.synth.playBoss();
}
function updateBattlePlayerInfo() {
    if (!currentBattle)
        return;
    const playerActive = (0, state_1.getPlayerResonant)(gameState);
    if (!playerActive)
        return;
    const template = (0, resonants_1.getResonantTemplate)(playerActive.templateId);
    if (template) {
        document.getElementById('player-resonant-name').textContent = template.name;
        document.getElementById('player-resonant-level').textContent = `Lv.${playerActive.level}`;
        const maxHp = (0, combat_1.getMaxHp)(playerActive);
        const hpPercent = (playerActive.currentHp / maxHp) * 100;
        const hpFill = document.getElementById('player-hp-fill');
        hpFill.style.width = `${hpPercent}%`;
        hpFill.className = `hp-fill h-full rounded-full transition-all duration-500 ${hpPercent > 50 ? '' : hpPercent > 20 ? 'mid' : 'low'}`;
        document.getElementById('player-hp-text').textContent = `${playerActive.currentHp}/${maxHp}`;
        document.getElementById('player-hp-max').textContent = `HP`;
        const xpNeeded = (0, types_1.calcXpToLevel)(playerActive.level + 1);
        const xpPercent = (playerActive.xp / xpNeeded) * 100;
        document.getElementById('player-xp-fill').style.width = `${xpPercent}%`;
        document.getElementById('player-xp-text').textContent = `${playerActive.xp}/${xpNeeded}`;
        if (playerActive.status) {
            document.getElementById('player-status').textContent = `Status: ${playerActive.status.toUpperCase()}`;
            document.getElementById('player-status').className = 'mt-1.5 text-xs font-bold';
        }
        else {
            document.getElementById('player-status').textContent = '';
        }
    }
}
async function handleBattleResult(result) {
    if (!currentBattle)
        return;
    const battleState = (0, combat_1.getBattleState)();
    (0, combat_1.clearCombatContext)();
    if (result === 'win') {
        // Calculate rewards
        const xpReward = Math.floor(battleState.enemy.level * 12 + Math.random() * 10);
        const shardReward = Math.floor(20 + Math.random() * 30);
        gameState.shards += shardReward;
        // Give XP to active resonant
        const playerActive = (0, state_1.getPlayerResonant)(gameState);
        if (playerActive) {
            playerActive.xp += xpReward;
            // Check for level up
            while (playerActive.xp >= (0, types_1.calcXpToLevel)(playerActive.level + 1) && playerActive.level < 25) {
                playerActive.xp -= (0, types_1.calcXpToLevel)(playerActive.level + 1);
                playerActive.level++;
                // Recalculate HP
                const template = (0, resonants_1.getResonantTemplate)(playerActive.templateId);
                if (template) {
                    const newMaxHp = (0, types_1.calcStats)(template, playerActive.level).hp;
                    const oldMaxHp = (0, combat_1.getMaxHp)(playerActive);
                    const hpRatio = playerActive.currentHp / oldMaxHp;
                    playerActive.currentHp = Math.floor(newMaxHp * hpRatio);
                }
                // Add new moves
                if (template) {
                    const newMoves = template.moves.filter(m => m.unlockLevel === playerActive.level);
                    for (const move of newMoves) {
                        if (!playerActive.movesKnown.includes(move.name)) {
                            playerActive.movesKnown.push(move.name);
                        }
                    }
                }
                // Check evolution
                const tmpl = (0, resonants_1.getResonantTemplate)(playerActive.templateId);
                if (tmpl?.evolveLevel && playerActive.level >= tmpl.evolveLevel) {
                    await handleEvolution(playerActive);
                }
            }
            // Add to dex
            if (!gameState.dexUnlocked.includes(playerActive.templateId)) {
                gameState.dexUnlocked.push(playerActive.templateId);
            }
        }
        ui.showToast(`+${xpReward} XP, +${shardReward} Shards!`, 'success');
        // Show result message then return to overworld
        battleState.message = `Won! Gained ${xpReward} XP and ${shardReward} Shards.`;
        ui.renderBattle(battleState);
        setTimeout(() => {
            ui.showScreen('screen-overworld');
            ui.renderOverworld(gameState);
            (0, state_1.saveGame)(gameState);
        }, 2000);
    }
    else if (result === 'capture') {
        // Add to box or party
        const captured = (0, combat_1.getBattleState)().enemy;
        captured.templateId = captured.templateId;
        if (gameState.party.filter(r => r.currentHp > 0).length < 6) {
            gameState.party.push(captured);
        }
        else {
            gameState.box.push(captured);
        }
        gameState.dexUnlocked.push(captured.templateId);
        ui.showToast(`Caught ${(0, resonants_1.getResonantTemplate)(captured.templateId)?.name}!`, 'success');
        setTimeout(() => {
            ui.showScreen('screen-overworld');
            ui.renderOverworld(gameState);
            (0, state_1.saveGame)(gameState);
        }, 1500);
    }
    else {
        // Fainted - check if all party fainted
        const alive = (0, state_1.getPartyAliveCount)(gameState);
        if (alive === 0) {
            ui.showToast('All Resonants fainted! Returning to last healing spring...', 'error');
            // Heal all at first healing node
            gameState.party.forEach(r => {
                const template = (0, resonants_1.getResonantTemplate)(r.templateId);
                if (template) {
                    r.currentHp = (0, types_1.calcStats)(template, r.level).hp;
                }
            });
            setTimeout(() => {
                ui.showScreen('screen-overworld');
                ui.renderOverworld(gameState);
                (0, state_1.saveGame)(gameState);
            }, 2000);
        }
        else {
            // Auto-switch to next alive resonant
            const nextAlive = gameState.party.find(r => r.currentHp > 0 && r.templateId !== (0, state_1.getPlayerResonant)(gameState)?.templateId);
            if (nextAlive) {
                ui.showToast(`Send out ${(0, resonants_1.getResonantTemplate)(nextAlive.templateId)?.name}!`, 'info');
                // In a full implementation, we'd switch the active resonant here
            }
            setTimeout(() => {
                ui.showScreen('screen-overworld');
                ui.renderOverworld(gameState);
                (0, state_1.saveGame)(gameState);
            }, 1500);
        }
    }
    currentBattle = null;
}
async function handleEvolution(resonant) {
    const template = (0, resonants_1.getResonantTemplate)(resonant.templateId);
    if (!template?.evolveLevel)
        return;
    // Find evolved form
    const evolutionTarget = resonants_1.RESONANT_DB.find(r => r.evolutionFrom === resonant.templateId);
    if (!evolutionTarget)
        return;
    ui.flashScreen('#fff', 500);
    sounds_1.synth.playEvolution();
    await ui.showEvolution(`<div class="evolution-flash">${ui.generateMiniSprite(evolutionTarget, evolutionTarget.baseHp > 80 ? 20 : 10)}</div>`, `${template.name} is evolving into ${evolutionTarget.name}!`);
    // Transform the resonant
    const oldLevel = resonant.level;
    resonant.templateId = evolutionTarget.id;
    resonant.movesKnown = evolutionTarget.moves.filter(m => m.unlockLevel <= oldLevel).map(m => m.name);
    const newStats = (0, types_1.calcStats)(evolutionTarget, oldLevel);
    resonant.currentHp = newStats.hp;
}
// === BATTLE ACTIONS ===
function handleBattleAction(action) {
    if (isProcessing || !currentBattle)
        return;
    switch (action) {
        case 'attack':
            showMoveSelect();
            break;
        case 'prism':
            showPrismMenu();
            break;
        case 'exchange':
            showExchangeMenu();
            break;
        case 'inventory':
            showInventoryMenu();
            break;
    }
}
function showMoveSelect() {
    hideSubMenus();
    const playerActive = (0, state_1.getPlayerResonant)(gameState);
    if (!playerActive)
        return;
    const template = (0, resonants_1.getResonantTemplate)(playerActive.templateId);
    if (!template)
        return;
    const container = document.getElementById('action-buttons');
    container.innerHTML = '';
    // Show up to 4 moves
    const moves = playerActive.movesKnown.slice(0, 4);
    while (moves.length < 4) {
        moves.push('Tackle'); // Filler
    }
    moves.forEach(moveName => {
        const moveDef = template.moves.find(m => m.name === moveName) ||
            { name: moveName, power: 40, accuracy: 100, element: 'cosmos' };
        const btn = document.createElement('button');
        btn.className = 'action-btn glass-card p-3 rounded-xl text-center hover:bg-white/5 transition-all border border-white/10 group';
        btn.innerHTML = `
      <span class="text-white font-bold text-sm block">${moveName}</span>
      <span class="text-slate-400 text-[10px]">Pwr: ${moveDef.power} | Acc: ${moveDef.accuracy}%</span>
      <span class="type-${moveDef.element} text-[10px] block">${moveDef.element}</span>
    `;
        btn.addEventListener('click', () => {
            sounds_1.synth.playSelect();
            (0, combat_1.executePlayerMove)(moveName);
            const bs = (0, combat_1.getBattleState)();
            if (bs)
                ui.renderBattle(bs);
            updateBattlePlayerInfo();
            // Check battle end
            setTimeout(() => checkBattleEnd(), 1000);
        });
        container.appendChild(btn);
    });
    // Add back button
    const backBtn = document.createElement('button');
    backBtn.id = 'battle-back-btn';
    backBtn.className = 'mx-auto mt-3 glass-badge px-4 py-2 rounded-xl text-xs text-slate-400 hover:text-white transition-colors border border-white/5';
    backBtn.textContent = '← Back';
    backBtn.addEventListener('click', () => { hideSubMenus(); });
    container.appendChild(backBtn);
    document.getElementById('battle-back-btn')?.remove();
}
function showPrismMenu() {
    hideSubMenus();
    document.getElementById('action-buttons').classList.add('hidden');
    document.getElementById('submenu-prism').classList.remove('hidden');
    document.getElementById('battle-back-btn')?.classList.remove('hidden');
}
async function usePrism(type) {
    hideSubMenus();
    const prismType = type === 'basic' ? 'prism_basic' : 'prism_hyper';
    if (!(0, state_1.removeInventoryItem)(gameState, prismType, 1)) {
        ui.showToast('No prisms remaining!', 'error');
        showBattleActions();
        return;
    }
    sounds_1.synth.playSelect();
    const success = (0, combat_1.attemptCapture)(type === 'basic' ? 'basic' : 'hyper');
    if (success) {
        await ui.showCaptureAnimation(true);
        handleBattleResult('capture');
    }
    else {
        await ui.showCaptureAnimation(false);
        sounds_1.synth.playCaptureFail();
        // Continue battle
        const bs = (0, combat_1.getBattleState)();
        if (bs) {
            bs.message += ' The Resonant broke free!';
            ui.renderBattle(bs);
            setTimeout(() => checkEnemyTurn(), 1000);
        }
    }
    (0, state_1.saveGame)(gameState);
}
function showExchangeMenu() {
    hideSubMenus();
    document.getElementById('action-buttons').classList.add('hidden');
    document.getElementById('submenu-exchange').classList.remove('hidden');
    document.getElementById('battle-back-btn')?.classList.remove('hidden');
    const container = document.getElementById('exchange-list');
    container.innerHTML = '';
    gameState.party.forEach((resonant, idx) => {
        if (resonant.currentHp <= 0 || resonant === (0, state_1.getPlayerResonant)(gameState))
            return;
        const template = (0, resonants_1.getResonantTemplate)(resonant.templateId);
        if (!template)
            return;
        const btn = document.createElement('button');
        btn.className = 'exchange-btn glass-card p-3 rounded-xl text-left hover:bg-white/5 transition-all border border-white/10 flex items-center gap-3';
        btn.innerHTML = `
      <span class="text-lg">${template.element === 'pyro' ? '🔥' : template.element === 'hydro' ? '💧' : template.element === 'flora' ? '🌱' : template.element === 'volt' ? '⚡' : template.element === 'zephyr' ? '🌀' : '🪨'}</span>
      <div class="flex-1">
        <span class="text-white font-bold text-sm">${template.name}</span>
        <span class="text-slate-400 text-xs ml-2">Lv.${resonant.level}</span>
      </div>
    `;
        btn.addEventListener('click', () => {
            sounds_1.synth.playSelect();
            // Swap active resonant (simplified - in full game would manage party order)
            ui.showToast(`${template.name} is now leading!`, 'info');
            hideSubMenus();
            showBattleActions();
        });
        container.appendChild(btn);
    });
}
function showInventoryMenu() {
    hideSubMenus();
    document.getElementById('action-buttons').classList.add('hidden');
    document.getElementById('submenu-inventory').classList.remove('hidden');
    document.getElementById('battle-back-btn')?.classList.remove('hidden');
    const container = document.getElementById('inventory-list');
    container.innerHTML = '';
    const usableItems = gameState.inventory.filter(i => i.count > 0);
    if (usableItems.length === 0) {
        container.innerHTML = '<p class="text-slate-400 text-sm text-center py-4">No items available.</p>';
        return;
    }
    usableItems.forEach(item => {
        const shopItem = types_1.SHOP_ITEMS.find(s => s.type === item.type);
        if (!shopItem)
            return;
        const btn = document.createElement('button');
        btn.className = 'inventory-btn glass-card p-3 rounded-xl text-left hover:bg-white/5 transition-all border border-white/10 flex items-center gap-3';
        btn.innerHTML = `
      <span class="text-lg">${shopItem.icon}</span>
      <div class="flex-1">
        <span class="text-white font-bold text-sm">${shopItem.name}</span>
        <span class="text-slate-400 text-xs ml-2">x${item.count}</span>
      </div>
    `;
        btn.addEventListener('click', () => {
            sounds_1.synth.playSelect();
            useBattleItem(item.type);
        });
        container.appendChild(btn);
    });
}
function useBattleItem(type) {
    if (!(0, state_1.removeInventoryItem)(gameState, type, 1))
        return;
    const playerActive = (0, state_1.getPlayerResonant)(gameState);
    if (!playerActive)
        return;
    switch (type) {
        case 'vital_elixir': {
            const template = (0, resonants_1.getResonantTemplate)(playerActive.templateId);
            if (template) {
                const maxHp = (0, types_1.calcStats)(template, playerActive.level).hp;
                const healAmount = Math.floor(maxHp * 0.5);
                playerActive.currentHp = Math.min(maxHp, playerActive.currentHp + healAmount);
                ui.showToast(`Healed ${playerActive.templateId} for ${healAmount} HP!`, 'success');
                sounds_1.synth.playHeal();
            }
            break;
        }
        case 'status_cleanser': {
            if (playerActive.status) {
                playerActive.status = null;
                ui.showToast(`Cleared status condition!`, 'success');
                sounds_1.synth.playHeal();
            }
            else {
                ui.showToast('No status to clear.', 'info');
            }
            break;
        }
        case 'prism_basic':
        case 'prism_hyper':
            ui.showToast('Prisms can\'t be used in battle this way.', 'info');
            (0, state_1.addInventoryItem)(gameState, type, 1); // Put it back
            break;
        default:
            break;
    }
    hideSubMenus();
    showBattleActions();
    updateBattlePlayerInfo();
    (0, state_1.saveGame)(gameState);
}
function showBattleActions() {
    hideSubMenus();
    document.getElementById('action-buttons').classList.remove('hidden');
    document.getElementById('battle-back-btn')?.classList.add('hidden');
}
function hideSubMenus() {
    document.getElementById('submenu-prism')?.classList.add('hidden');
    document.getElementById('submenu-exchange')?.classList.add('hidden');
    document.getElementById('submenu-inventory')?.classList.add('hidden');
    document.getElementById('action-buttons')?.classList.remove('hidden');
}
function checkBattleEnd() {
    const bs = (0, combat_1.getBattleState)();
    if (!bs)
        return;
    // Check if enemy fainted
    if (bs.enemy.currentHp <= 0) {
        handleBattleResult('win');
        return;
    }
    // Check if player has no alive resonants
    const alive = (0, state_1.getPartyAliveCount)(gameState);
    if (alive === 0) {
        handleBattleResult('fled');
        return;
    }
    // Enemy turn
    checkEnemyTurn();
}
function checkEnemyTurn() {
    (0, combat_1.executeEnemyTurn)();
    const bs = (0, combat_1.getBattleState)();
    if (bs)
        ui.renderBattle(bs);
    updateBattlePlayerInfo();
    setTimeout(() => checkBattleEnd(), 1000);
}
// === MYSTERY & NPC HANDLERS ===
async function handleMysteryCache(node) {
    const reward = (0, map_1.getMysteryCacheReward)(gameState.currentZone);
    gameState.shards += reward.shards;
    if (reward.item) {
        (0, state_1.addInventoryItem)(gameState, reward.item, 1);
        ui.showToast(`Found ${reward.shards} Shards and a ${reward.item}!`, 'success');
    }
    else {
        ui.showToast(`Found ${reward.shards} Aether Shards!`, 'success');
    }
    sounds_1.synth.playLevelUp();
    (0, state_1.saveGame)(gameState);
}
async function handleNPCDialog(node) {
    if (node.dialogLines) {
        for (const line of node.dialogLines) {
            await ui.showDialog(line.speaker, line.text, line.avatar || '💬');
        }
    }
    // Give small shard reward for NPC interaction
    gameState.shards += 15;
    sounds_1.synth.playSelect();
    (0, state_1.saveGame)(gameState);
}
async function handleHealingSpring() {
    ui.showEncounter('A serene healing spring glows with soft light.', async () => {
        let healed = 0;
        gameState.party.forEach(r => {
            const template = (0, resonants_1.getResonantTemplate)(r.templateId);
            if (template) {
                const maxHp = (0, types_1.calcStats)(template, r.level).hp;
                if (r.currentHp < maxHp) {
                    r.currentHp = maxHp;
                    healed++;
                }
            }
        });
        ui.showToast(`All Resonants fully restored!`, 'success');
        sounds_1.synth.playHeal();
    });
}
async function handleBossBattle(zoneIdx, node) {
    const boss = (0, map_1.getBossData)(zoneIdx);
    if (!boss)
        return;
    // Show boss dialog first
    for (const line of boss.dialog) {
        await ui.showDialog(line.speaker, line.text, line.avatar || '💬');
    }
    sounds_1.synth.playBoss();
    // Start boss battle
    await startWildBattle(boss.resonantId, boss.level);
}
function checkZoneProgress(zoneIdx) {
    const zone = map_1.ZONES[zoneIdx];
    const allCleared = zone.nodes.every(n => n.cleared || n.type === 'boss');
    if (allCleared && zoneIdx < 3) {
        gameState.unlockedZones[zoneIdx + 1] = true;
        gameState.defeatedBosses.push(zoneIdx);
        ui.showToast(`Zone unlocked: ${map_1.ZONES[zoneIdx + 1].name}!`, 'success');
        sounds_1.synth.playLevelUp();
        // Award bonus shards
        const bonusShards = [100, 200, 400][zoneIdx];
        gameState.shards += bonusShards;
        ui.showToast(`+${bonusShards} bonus Shards!`, 'success');
    }
    // Check game completion
    if (gameState.unlockedZones[3] && map_1.ZONES[3].nodes.every(n => n.cleared)) {
        setTimeout(() => {
            ui.showEncounter('🏆 Congratulations, Master Conductor! You have conquered all of Aethelgard and proven yourself the greatest Resonant handler in history!', () => { });
        }, 2000);
    }
}
// === DEX SCREEN ===
function renderDexScreen() {
    ui.renderDex(gameState.dexUnlocked);
}
// === SHOP SCREEN ===
function renderShopScreen() {
    ui.renderShop(gameState.shards);
    // Wire up shop buttons
    document.querySelectorAll('[data-shop-item]').forEach(btn => {
        btn.addEventListener('click', () => {
            const itemId = btn.dataset.shopItem;
            if (!itemId)
                return;
            const shopItem = types_1.SHOP_ITEMS.find(s => s.id === itemId);
            if (!shopItem)
                return;
            if (gameState.shards >= shopItem.cost) {
                gameState.shards -= shopItem.cost;
                (0, state_1.addInventoryItem)(gameState, shopItem.type, 1);
                ui.showToast(`Purchased ${shopItem.name}!`, 'success');
                sounds_1.synth.playSelect();
                renderShopScreen(); // Re-render to update affordability
                (0, state_1.saveGame)(gameState);
            }
            else {
                ui.showToast('Not enough Shards!', 'error');
                sounds_1.synth.playCaptureFail();
            }
        });
    });
}
// === GAME LOOP (animation frame for animated elements) ===
let lastFrame = 0;
function gameLoop(timestamp) {
    const delta = timestamp - lastFrame;
    // Redraw map if in overworld (for particle animation)
    if (!document.getElementById('screen-overworld')?.classList.contains('hidden')) {
        ui.drawMap(gameState.currentZone, gameState.currentNode);
    }
    lastFrame = timestamp;
    requestAnimationFrame(gameLoop);
}
// === INITIALIZATION ===
init();
requestAnimationFrame(gameLoop);
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SHOP_ITEMS = exports.TYPE_CHART = void 0;
exports.getTypeModifier = getTypeModifier;
exports.calcXpToLevel = calcXpToLevel;
exports.calcStats = calcStats;
exports.calcDamage = calcDamage;
exports.isCritical = isCritical;
exports.TYPE_CHART = {
    pyro: { strong: ['flora'], weak: ['hydro'] },
    hydro: { strong: ['pyro'], weak: ['flora'] },
    flora: { strong: ['hydro'], weak: ['pyro'] },
    volt: { strong: ['earth'], weak: ['zephyr'] },
    zephyr: { strong: ['volt'], weak: ['earth'] },
    earth: { strong: ['volt'], weak: ['zephyr'] },
    cosmos: { strong: [], weak: [] }
};
function getTypeModifier(attackerType, defenderType) {
    const chart = exports.TYPE_CHART[attackerType];
    if (chart.strong.includes(defenderType))
        return 1.5;
    if (chart.weak.includes(defenderType))
        return 0.75;
    return 1.0;
}
exports.SHOP_ITEMS = [
    { id: 'prism_basic', name: 'Aether Prism', description: 'Basic catch tool. Reliable capture rate.', cost: 100, type: 'prism_basic', icon: '◇' },
    { id: 'prism_hyper', name: 'Hyper Prism', description: 'Enhanced capture matrix. 2.5× catch rate.', cost: 300, type: 'prism_hyper', icon: '◆' },
    { id: 'vital_elixir', name: 'Core Restorer', description: 'Restores 50% HP to one Resonant.', cost: 150, type: 'vital_elixir', icon: '♥' },
    { id: 'status_cleanser', name: 'Resonance Elixir', description: 'Heals all status conditions.', cost: 200, type: 'status_cleanser', icon: '✦' },
    { id: 'prism_booster', name: 'Prism Booster', description: 'Next prism throw gets 1.5× rate.', cost: 250, type: 'prism_booster', icon: '⬡' }
];
function calcXpToLevel(level) {
    return Math.floor(4 * level * level + 6 * level);
}
function calcStats(template, level) {
    const hp = Math.floor(((2 * template.baseHp + 10) * level) / 100) + level + 10;
    const atk = Math.floor(((2 * template.baseAttack + 5) * level) / 100) + 5;
    const def = Math.floor(((2 * template.baseDefense + 5) * level) / 100) + 5;
    const spd = Math.floor(((2 * template.baseSpeed + 5) * level) / 100) + 5;
    return { hp, attack: atk, defense: def, speed: spd };
}
function calcDamage(attackerLevel, movePower, attackerAtk, defenderDef, typeMod) {
    const base = (((2 * attackerLevel / 5) + 2) * movePower * (attackerAtk / defenderDef)) / 50 + 2;
    const random = 0.85 + Math.random() * 0.15;
    return Math.max(1, Math.floor(base * typeMod * random));
}
function isCritical() {
    return Math.random() < 0.1;
}
};

// ── module: src/resonants.ts ──
__mods["src/resonants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RESONANT_DB = void 0;
exports.getResonantTemplate = getResonantTemplate;
exports.createResonantInstance = createResonantInstance;
const pyroMoves = [
    { name: 'Ember', power: 40, accuracy: 100, element: 'pyro', unlockLevel: 1, description: 'A small flame projectile.' },
    { name: 'Scratch', power: 50, accuracy: 100, element: 'cosmos', unlockLevel: 1, description: 'Raking claws attack.' },
    { name: 'Flame Burst', power: 70, accuracy: 95, element: 'pyro', unlockLevel: 5, description: 'Explosive fireball.' },
    { name: 'Fire Spin', power: 80, accuracy: 90, element: 'pyro', unlockLevel: 12, description: 'Trapping whirlwind of flame.' },
    { name: 'Inferno Rush', power: 110, accuracy: 85, element: 'pyro', unlockLevel: 20, description: 'Devastating charging blaze.' }
];
const hydroMoves = [
    { name: 'Aqua Jet', power: 40, accuracy: 100, element: 'hydro', unlockLevel: 1, description: 'Swift water shard strike.' },
    { name: 'Tackle', power: 50, accuracy: 100, element: 'cosmos', unlockLevel: 1, description: 'Body slam attack.' },
    { name: 'Water Pulse', power: 70, accuracy: 95, element: 'hydro', unlockLevel: 5, description: 'Rippling water wave.' },
    { name: 'Tidal Surge', power: 85, accuracy: 90, element: 'hydro', unlockLevel: 12, description: 'Massive wall of water.' },
    { name: 'Abyssal Tsunami', power: 120, accuracy: 80, element: 'hydro', unlockLevel: 20, description: 'Cataclysmic ocean wave.' }
];
const floraMoves = [
    { name: 'Vine Whip', power: 45, accuracy: 100, element: 'flora', unlockLevel: 1, description: 'Striking vine lash.' },
    { name: 'Absorb', power: 50, accuracy: 100, element: 'flora', unlockLevel: 1, description: 'Drains life from foe.' },
    { name: 'Pollen Storm', power: 70, accuracy: 95, element: 'flora', unlockLevel: 5, description: 'Dense cloud of spores.' },
    { name: 'Thorn Barrage', power: 85, accuracy: 90, element: 'flora', unlockLevel: 12, description: 'Relentless thorn volley.' },
    { name: 'World Tree Wrath', power: 120, accuracy: 80, element: 'flora', unlockLevel: 20, description: 'Primordial nature fury.' }
];
const voltMoves = [
    { name: 'Spark', power: 40, accuracy: 100, element: 'volt', unlockLevel: 1, description: 'Tiny electric discharge.' },
    { name: 'Quick Attack', power: 50, accuracy: 100, element: 'cosmos', unlockLevel: 1, description: 'Lightning-fast strike.' },
    { name: 'Thunder Shock', power: 75, accuracy: 95, element: 'volt', unlockLevel: 5, description: 'Jolting electric blast.' },
    { name: 'Chain Lightning', power: 90, accuracy: 88, element: 'volt', unlockLevel: 12, description: 'Splitting bolt of lightning.' },
    { name: 'Supernova Storm', power: 130, accuracy: 75, element: 'volt', unlockLevel: 20, description: 'Cataclysmic electric storm.' }
];
const zephyrMoves = [
    { name: 'Gust', power: 40, accuracy: 100, element: 'zephyr', unlockLevel: 1, description: 'Sharp wind blade.' },
    { name: 'Quick Attack', power: 50, accuracy: 100, element: 'cosmos', unlockLevel: 1, description: 'Swift aerial strike.' },
    { name: 'Air Cutter', power: 70, accuracy: 95, element: 'zephyr', unlockLevel: 5, description: 'Razor-sharp wind slash.' },
    { name: 'Cyclone', power: 85, accuracy: 90, element: 'zephyr', unlockLevel: 12, description: 'Devastating tornado.' },
    { name: 'Skyfall Descent', power: 115, accuracy: 82, element: 'zephyr', unlockLevel: 20, description: 'Divine wind from above.' }
];
const earthMoves = [
    { name: 'Rock Throw', power: 45, accuracy: 100, element: 'earth', unlockLevel: 1, description: 'Hurling sharp stones.' },
    { name: 'Tackle', power: 50, accuracy: 100, element: 'cosmos', unlockLevel: 1, description: 'Body slam attack.' },
    { name: 'Sand Tomb', power: 75, accuracy: 92, element: 'earth', unlockLevel: 5, description: 'Trapping sand vortex.' },
    { name: 'Earthquake', power: 90, accuracy: 88, element: 'earth', unlockLevel: 12, description: 'Shattering ground quake.' },
    { name: 'Mountain Fall', power: 125, accuracy: 78, element: 'earth', unlockLevel: 20, description: 'Colossal rock avalanche.' }
];
exports.RESONANT_DB = [
    // === PYRO FAMILY ===
    {
        id: 'emberkit', name: 'Emberkit', element: 'pyro',
        baseHp: 45, baseAttack: 38, baseDefense: 30, baseSpeed: 42,
        evolutionFrom: undefined, evolveLevel: 8,
        moves: pyroMoves.filter(m => m.unlockLevel <= 1),
        spriteParams: { shape: 'circle', colors: ['#f97316', '#ea580c'], details: 'flame-tail' }
    },
    {
        id: 'pyrowolf', name: 'Pyrowolf', element: 'pyro',
        baseHp: 65, baseAttack: 62, baseDefense: 48, baseSpeed: 58,
        evolutionFrom: 'emberkit', evolveLevel: 15,
        moves: pyroMoves.filter(m => m.unlockLevel <= 8),
        spriteParams: { shape: 'wolf', colors: ['#f97316', '#dc2626'], details: 'fire-mane' }
    },
    {
        id: 'ignisaur', name: 'Ignisaur', element: 'pyro',
        baseHp: 90, baseAttack: 88, baseDefense: 65, baseSpeed: 72,
        evolutionFrom: 'pyrowolf',
        moves: pyroMoves.filter(m => m.unlockLevel <= 15),
        spriteParams: { shape: 'dinosaur', colors: ['#ef4444', '#f97316'], details: 'lava-plates' }
    },
    // === HYDRO FAMILY ===
    {
        id: 'droplet', name: 'Droplet', element: 'hydro',
        baseHp: 50, baseAttack: 32, baseDefense: 40, baseSpeed: 38,
        evolutionFrom: undefined, evolveLevel: 8,
        moves: hydroMoves.filter(m => m.unlockLevel <= 1),
        spriteParams: { shape: 'drop', colors: ['#06b6d4', '#0891b2'], details: 'water-glow' }
    },
    {
        id: 'geoxus', name: 'Geoxus', element: 'hydro',
        baseHp: 72, baseAttack: 55, baseDefense: 60, baseSpeed: 52,
        evolutionFrom: 'droplet', evolveLevel: 15,
        moves: hydroMoves.filter(m => m.unlockLevel <= 8),
        spriteParams: { shape: 'mammal', colors: ['#06b6d4', '#3b82f6'], details: 'fin-crown' }
    },
    {
        id: 'neptunian', name: 'Neptunian', element: 'hydro',
        baseHp: 100, baseAttack: 80, baseDefense: 78, baseSpeed: 65,
        evolutionFrom: 'geoxus',
        moves: hydroMoves.filter(m => m.unlockLevel <= 15),
        spriteParams: { shape: 'leviathan', colors: ['#0e7490', '#06b6d4'], details: 'ocean-crown' }
    },
    // === FLORA FAMILY ===
    {
        id: 'seedling', name: 'Seedling', element: 'flora',
        baseHp: 55, baseAttack: 30, baseDefense: 45, baseSpeed: 35,
        evolutionFrom: undefined, evolveLevel: 8,
        moves: floraMoves.filter(m => m.unlockLevel <= 1),
        spriteParams: { shape: 'plant', colors: ['#22c55e', '#16a34a'], details: 'sprout-leaves' }
    },
    {
        id: 'thornback', name: 'Thornback', element: 'flora',
        baseHp: 78, baseAttack: 52, baseDefense: 70, baseSpeed: 45,
        evolutionFrom: 'seedling', evolveLevel: 15,
        moves: floraMoves.filter(m => m.unlockLevel <= 8),
        spriteParams: { shape: 'turtle', colors: ['#16a34a', '#15803d'], details: 'thorn-shell' }
    },
    {
        id: 'bloomgoliath', name: 'Bloomgoliath', element: 'flora',
        baseHp: 110, baseAttack: 75, baseDefense: 95, baseSpeed: 40,
        evolutionFrom: 'thornback',
        moves: floraMoves.filter(m => m.unlockLevel <= 15),
        spriteParams: { shape: 'colossus', colors: ['#15803d', '#22c55e'], details: 'flower-crown' }
    },
    // === VOLT FAMILY ===
    {
        id: 'sparky', name: 'Sparky', element: 'volt',
        baseHp: 42, baseAttack: 45, baseDefense: 28, baseSpeed: 55,
        evolutionFrom: undefined, evolveLevel: 8,
        moves: voltMoves.filter(m => m.unlockLevel <= 1),
        spriteParams: { shape: 'mouse', colors: ['#facc15', '#eab308'], details: 'lightning-tail' }
    },
    {
        id: 'boltsaber', name: 'Boltsaber', element: 'volt',
        baseHp: 62, baseAttack: 72, baseDefense: 45, baseSpeed: 70,
        evolutionFrom: 'sparky', evolveLevel: 15,
        moves: voltMoves.filter(m => m.unlockLevel <= 8),
        spriteParams: { shape: 'cat', colors: ['#eab308', '#f59e0b'], details: 'bolt-ears' }
    },
    {
        id: 'stormlord', name: 'Stormlord', element: 'volt',
        baseHp: 85, baseAttack: 95, baseDefense: 60, baseSpeed: 88,
        evolutionFrom: 'boltsaber',
        moves: voltMoves.filter(m => m.unlockLevel <= 15),
        spriteParams: { shape: 'dragon', colors: ['#f59e0b', '#ef4444'], details: 'storm-wings' }
    },
    // === ZEPHYR FAMILY ===
    {
        id: 'gusty', name: 'Gusty', element: 'zephyr',
        baseHp: 40, baseAttack: 35, baseDefense: 25, baseSpeed: 60,
        evolutionFrom: undefined, evolveLevel: 8,
        moves: zephyrMoves.filter(m => m.unlockLevel <= 1),
        spriteParams: { shape: 'spirit', colors: ['#a78bfa', '#8b5cf6'], details: 'wind-trail' }
    },
    {
        id: 'slipstream', name: 'Slipstream', element: 'zephyr',
        baseHp: 58, baseAttack: 58, baseDefense: 40, baseSpeed: 78,
        evolutionFrom: 'gusty', evolveLevel: 15,
        moves: zephyrMoves.filter(m => m.unlockLevel <= 8),
        spriteParams: { shape: 'bird', colors: ['#8b5cf6', '#a78bfa'], details: 'wind-wings' }
    },
    {
        id: 'zephyrus', name: 'Zephyrus', element: 'zephyr',
        baseHp: 78, baseAttack: 82, baseDefense: 55, baseSpeed: 95,
        evolutionFrom: 'slipstream',
        moves: zephyrMoves.filter(m => m.unlockLevel <= 15),
        spriteParams: { shape: 'angel', colors: ['#c4b5fd', '#a78bfa'], details: 'halo-wind' }
    },
    // === EARTH FAMILY ===
    {
        id: 'pebble', name: 'Pebble', element: 'earth',
        baseHp: 52, baseAttack: 35, baseDefense: 50, baseSpeed: 30,
        evolutionFrom: undefined, evolveLevel: 8,
        moves: earthMoves.filter(m => m.unlockLevel <= 1),
        spriteParams: { shape: 'rock', colors: ['#a16207', '#854d0e'], details: 'crystal-vein' }
    },
    {
        id: 'obsidian', name: 'Obsidian', element: 'earth',
        baseHp: 75, baseAttack: 60, baseDefense: 75, baseSpeed: 42,
        evolutionFrom: 'pebble', evolveLevel: 15,
        moves: earthMoves.filter(m => m.unlockLevel <= 8),
        spriteParams: { shape: 'golem', colors: ['#854d0e', '#713f12'], details: 'lava-cracks' }
    },
    {
        id: 'terrasunder', name: 'Terrasunder', element: 'earth',
        baseHp: 105, baseAttack: 85, baseDefense: 100, baseSpeed: 35,
        evolutionFrom: 'obsidian',
        moves: earthMoves.filter(m => m.unlockLevel <= 15),
        spriteParams: { shape: 'titan', colors: ['#713f12', '#a16207'], details: 'mountain-core' }
    }
];
function getResonantTemplate(id) {
    return exports.RESONANT_DB.find(r => r.id === id);
}
function createResonantInstance(templateId, level = 1) {
    const template = getResonantTemplate(templateId);
    if (!template)
        throw new Error(`Unknown resonant: ${templateId}`);
    const stats = (0, types_1.calcStats)(template, level);
    return {
        templateId,
        level,
        currentHp: stats.hp,
        xp: 0,
        status: null,
        movesKnown: template.moves.filter(m => m.unlockLevel <= level).map(m => m.name),
        caught: true
    };
}
const types_1 = require("./types");
};

// ── module: src/sounds.ts ──
__mods["src/sounds.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.synth = exports.SynthManager = void 0;
class SynthManager {
    constructor() {
        this.ctx = null;
        this.enabled = true;
        this.masterGain = null;
    }
    init() {
        if (this.ctx)
            return;
        try {
            this.ctx = new (window.AudioContext || window.webkitAudioContext)();
            this.masterGain = this.ctx.createGain();
            this.masterGain.gain.value = 0.3;
            this.masterGain.connect(this.ctx.destination);
        }
        catch {
            this.enabled = false;
        }
    }
    playTone(frequency, duration, type = 'square', volume = 0.3) {
        if (!this.enabled || !this.ctx || !this.masterGain)
            return;
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = type;
        osc.frequency.setValueAtTime(frequency, this.ctx.currentTime);
        gain.gain.setValueAtTime(volume, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + duration);
        osc.connect(gain);
        gain.connect(this.masterGain);
        osc.start();
        osc.stop(this.ctx.currentTime + duration);
    }
    playNoise(duration, volume = 0.2) {
        if (!this.enabled || !this.ctx || !this.masterGain)
            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;
        }
        const noise = this.ctx.createBufferSource();
        noise.buffer = buffer;
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(volume, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + duration);
        noise.connect(gain);
        gain.connect(this.masterGain);
        noise.start();
    }
    playHover() {
        this.playTone(800, 0.05, 'sine', 0.15);
    }
    playSelect() {
        this.playTone(600, 0.08, 'square', 0.2);
        setTimeout(() => this.playTone(900, 0.06, 'square', 0.15), 50);
    }
    playHit() {
        this.playNoise(0.15, 0.3);
        this.playTone(200, 0.1, 'sawtooth', 0.25);
    }
    playHitSuperEffective() {
        this.playNoise(0.2, 0.35);
        this.playTone(400, 0.15, 'sawtooth', 0.3);
        setTimeout(() => this.playTone(600, 0.1, 'square', 0.2), 80);
        setTimeout(() => this.playTone(800, 0.15, 'square', 0.15), 160);
    }
    playLevelUp() {
        const notes = [523, 659, 784, 1047];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.2, 'square', 0.2), i * 120);
        });
    }
    playCaptureSuccess() {
        const notes = [440, 554, 659, 880, 1108];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.25, 'sine', 0.2), i * 100);
        });
        setTimeout(() => {
            this.playTone(1318, 0.4, 'sine', 0.15);
        }, 600);
    }
    playCaptureFail() {
        this.playTone(300, 0.2, 'square', 0.2);
        setTimeout(() => this.playTone(250, 0.2, 'square', 0.15), 200);
        setTimeout(() => this.playTone(200, 0.3, 'sawtooth', 0.1), 400);
    }
    playEvolution() {
        const notes = [392, 494, 587, 659, 784, 880, 988, 1175];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.3, 'sine', 0.2), i * 100);
        });
    }
    playBoss() {
        const notes = [220, 196, 220, 262, 220, 196];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.3, 'sawtooth', 0.25), i * 200);
        });
    }
    playHeal() {
        const notes = [784, 988, 1175];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.3, 'sine', 0.15), i * 150);
        });
    }
    playDialog() {
        this.playTone(500, 0.04, 'sine', 0.1);
    }
    toggle() {
        this.enabled = !this.enabled;
        return this.enabled;
    }
}
exports.SynthManager = SynthManager;
exports.synth = new SynthManager();
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInitialState = createInitialState;
exports.getSaveData = getSaveData;
exports.saveGame = saveGame;
exports.resetSave = resetSave;
exports.hasSave = hasSave;
exports.cloneResonant = cloneResonant;
exports.addInventoryItem = addInventoryItem;
exports.removeInventoryItem = removeInventoryItem;
exports.getPlayerResonant = getPlayerResonant;
exports.getPartyAliveCount = getPartyAliveCount;
const SAVE_KEY = 'aethelgard_save_v1';
function createInitialState() {
    const starterChoices = [
        { id: 'emberkit', name: 'Emberkit' },
        { id: 'droplet', name: 'Droplet' },
        { id: 'seedling', name: 'Seedling' }
    ];
    return {
        name: 'Conductor',
        party: [],
        box: [],
        inventory: [
            { type: 'prism_basic', count: 5 },
            { type: 'vital_elixir', count: 3 },
            { type: 'status_cleanser', count: 1 }
        ],
        shards: 200,
        unlockedZones: [true, false, false, false],
        currentZone: 0,
        currentNode: 0,
        defeatedBosses: [],
        dexUnlocked: [],
        _starterChoices: starterChoices
    };
}
function getSaveData() {
    try {
        const raw = localStorage.getItem(SAVE_KEY);
        if (!raw)
            return null;
        const data = JSON.parse(raw);
        // Validate essential fields
        if (data.name && Array.isArray(data.party) && Array.isArray(data.unlockedZones)) {
            return data;
        }
    }
    catch {
        // Corrupt save, ignore
    }
    return null;
}
function saveGame(state) {
    try {
        const { _starterChoices, ...saveData } = state;
        localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
    }
    catch {
        // Storage full or unavailable
    }
}
function resetSave() {
    localStorage.removeItem(SAVE_KEY);
}
function hasSave() {
    return getSaveData() !== null;
}
// Helper to clone a ResonantInstance deeply
function cloneResonant(r) {
    return JSON.parse(JSON.stringify(r));
}
function addInventoryItem(state, type, count = 1) {
    const existing = state.inventory.find(i => i.type === type);
    if (existing) {
        existing.count += count;
    }
    else {
        state.inventory.push({ type, count });
    }
}
function removeInventoryItem(state, type, count = 1) {
    const item = state.inventory.find(i => i.type === type);
    if (!item || item.count < count)
        return false;
    item.count -= count;
    if (item.count <= 0) {
        state.inventory = state.inventory.filter(i => i.type !== type);
    }
    return true;
}
function getPlayerResonant(state) {
    const alive = state.party.filter(r => r.currentHp > 0);
    if (alive.length === 0)
        return state.party[0]; // Return first even if fainted
    return alive[0];
}
function getPartyAliveCount(state) {
    return state.party.filter(r => r.currentHp > 0).length;
}
};

// ── module: src/map.ts ──
__mods["src/map.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZONES = void 0;
exports.createZones = createZones;
exports.getBossData = getBossData;
exports.getNodeEncounter = getNodeEncounter;
exports.getMysteryCacheReward = getMysteryCacheReward;
const WILD_ENCOUNTERS = {
    0: [
        { resonantId: 'seedling', weight: 35, minLevel: 1, maxLevel: 4 },
        { resonantId: 'gusty', weight: 30, minLevel: 2, maxLevel: 5 },
        { resonantId: 'sparky', weight: 20, minLevel: 1, maxLevel: 3 },
        { resonantId: 'pebble', weight: 15, minLevel: 3, maxLevel: 5 }
    ],
    1: [
        { resonantId: 'droplet', weight: 30, minLevel: 5, maxLevel: 9 },
        { resonantId: 'pebble', weight: 25, minLevel: 5, maxLevel: 9 },
        { resonantId: 'seedling', weight: 20, minLevel: 6, maxLevel: 10 },
        { resonantId: 'gusty', weight: 15, minLevel: 5, maxLevel: 8 },
        { resonantId: 'sparky', weight: 10, minLevel: 7, maxLevel: 10 }
    ],
    2: [
        { resonantId: 'emberkit', weight: 25, minLevel: 10, maxLevel: 14 },
        { resonantId: 'sparky', weight: 25, minLevel: 10, maxLevel: 15 },
        { resonantId: 'droplet', weight: 20, minLevel: 10, maxLevel: 14 },
        { resonantId: 'obsidian', weight: 15, minLevel: 11, maxLevel: 15 },
        { resonantId: 'gusty', weight: 15, minLevel: 10, maxLevel: 13 }
    ],
    3: [
        { resonantId: 'ignisaur', weight: 15, minLevel: 15, maxLevel: 20 },
        { resonantId: 'stormlord', weight: 15, minLevel: 16, maxLevel: 22 },
        { resonantId: 'neptunian', weight: 15, minLevel: 15, maxLevel: 20 },
        { resonantId: 'zephyrus', weight: 15, minLevel: 16, maxLevel: 22 },
        { resonantId: 'terrasunder', weight: 15, minLevel: 15, maxLevel: 20 },
        { resonantId: 'bloomgoliath', weight: 15, minLevel: 16, maxLevel: 22 }
    ]
};
const BOSS_DATA = {
    0: {
        resonantId: 'thornback', level: 7,
        dialog: [
            { speaker: 'Guardian of the Canopy', text: 'Halt, young Conductor. The Verdant Canopy tests all who would pass.', avatar: '🌿' },
            { speaker: 'Thornback', text: '*rustles menacingly*', avatar: '🐢' },
            { speaker: 'Guardian of the Canopy', text: 'Defeat my champion and prove your bond with Resonants is worthy.', avatar: '🌿' }
        ]
    },
    1: {
        resonantId: 'geoxus', level: 12,
        dialog: [
            { speaker: 'Cave Keeper Neri', text: 'The Sunken Grotto holds ancient waters. Few emerge unchanged.', avatar: '🧜' },
            { speaker: 'Geoxus', text: '*surges with tidal power*', avatar: '🐋' },
            { speaker: 'Cave Keeper Neri', text: 'Show me you can navigate the depths, Conductor. Face my guardian.', avatar: '🧜' }
        ]
    },
    2: {
        resonantId: 'boltsaber', level: 17,
        dialog: [
            { speaker: 'Crag Warden Voss', text: 'The Obsidian Crags burn with raw elemental fury. Only the strong survive.', avatar: '⚡' },
            { speaker: 'Boltsaber', text: '*crackles with electric energy*', avatar: '🐱' },
            { speaker: 'Crag Warden Voss', text: 'Your Resonant sparks like a candle. Let us see if yours can weather the storm.', avatar: '⚡' }
        ]
    },
    3: {
        resonantId: 'stormlord', level: 22,
        dialog: [
            { speaker: 'The Apex Sentinel', text: 'You have reached the pinnacle of Aethelgard. Few Conductors ever stand before me.', avatar: '👁️' },
            { speaker: 'Stormlord', text: '*thunder rolls in the distance*', avatar: '🐉' },
            { speaker: 'The Apex Sentinel', text: 'I am the final test. Defeat my champion and prove yourself Master Conductor of Aethelgard.', avatar: '👁️' },
            { speaker: 'The Apex Sentinel', text: 'May the strongest resonance prevail.', avatar: '👁️' }
        ]
    }
};
const NPC_DIALOGS = {
    0: [
        [{ speaker: 'Wandering Botanist', text: 'The flora here is unusually vibrant! Would you like to tend to your Resonants at the spring ahead?', avatar: '👩‍🌾' }],
        [{ speaker: 'Young Conductor', text: 'I just caught my first Gusty! It\'s so fast, I can barely keep up!', avatar: '🧒' }]
    ],
    1: [
        [{ speaker: 'Deep Diver', text: 'Beware the currents in the lower grotto. Only Hydro-type Resonants can navigate safely.', avatar: '🤿' }],
        [{ speaker: 'Mysterious Hermit', text: 'The ancient waters hold secrets... but also dangers beyond your level.', avatar: '🧙' }]
    ],
    2: [
        [{ speaker: 'Volcanic Smith', text: 'I forge prisms from cooled obsidian. They make excellent capture tools!', avatar: '⚒️' }],
        [{ speaker: 'Exhausted Traveler', text: 'The crags are treacherous. Make sure your team is ready before pushing forward.', avatar: '🥾' }]
    ],
    3: [
        [{ speaker: 'Lost Scholar', text: 'The Apex Pillar... they say it tests the very essence of resonance between Conductor and Resonant.', avatar: '📚' }],
        [{ speaker: 'Retired Champion', text: 'I once stood where you stand. I did not succeed. But perhaps you will.', avatar: '🏆' }]
    ]
};
function createZones() {
    const zoneConfigs = [
        { name: 'The Verdant Canopy', desc: 'Ancient forest canopy alive with Flora and Zephyr energy.', levelRange: [1, 5], color: '#22c55e', nodeCount: 8 },
        { name: 'The Sunken Grotto', desc: 'Underwater caverns pulsing with Hydro and Terra resonance.', levelRange: [5, 10], color: '#06b6d4', nodeCount: 9 },
        { name: 'The Obsidian Crags', desc: 'Volcanic mountain peaks crackling with Pyro and Volt energy.', levelRange: [10, 15], color: '#ef4444', nodeCount: 9 },
        { name: 'The Apex Pillar', desc: 'The sacred summit where elemental forces converge. Final gauntlet.', levelRange: [15, 22], color: '#a855f7', nodeCount: 7 }
    ];
    return zoneConfigs.map((config, zoneIdx) => {
        const nodes = [];
        // Generate trail layout - winding path
        const cols = Math.min(config.nodeCount, 6);
        const rows = Math.ceil(config.nodeCount / cols);
        for (let i = 0; i < config.nodeCount; i++) {
            const col = i % cols;
            const row = Math.floor(i / cols);
            const x = 120 + col * ((800 - 200) / Math.max(cols - 1, 1));
            const y = 100 + row * ((500 - 150) / Math.max(rows - 1, 1)) + (col % 2 === 0 ? 30 : -20);
            let type;
            if (i === config.nodeCount - 1) {
                type = 'boss';
            }
            else if (i === 0) {
                type = 'healing';
            }
            else {
                const rand = Math.random();
                if (rand < 0.55)
                    type = 'wild';
                else if (rand < 0.75)
                    type = 'mystery';
                else if (rand < 0.92)
                    type = 'npc';
                else
                    type = 'healing';
            }
            const node = {
                id: `zone${zoneIdx}_node${i}`,
                x, y, type, cleared: false
            };
            if (type === 'wild') {
                const encounters = WILD_ENCOUNTERS[zoneIdx];
                const totalWeight = encounters.reduce((s, e) => s + e.weight, 0);
                let roll = Math.random() * totalWeight;
                let selected = encounters[0];
                for (const enc of encounters) {
                    roll -= enc.weight;
                    if (roll <= 0) {
                        selected = enc;
                        break;
                    }
                }
                node.encounterId = `${selected.resonantId}_${selected.minLevel}_${selected.maxLevel}`;
            }
            if (type === 'npc') {
                const dialogs = NPC_DIALOGS[zoneIdx];
                node.dialogLines = dialogs[Math.floor(Math.random() * dialogs.length)];
            }
            nodes.push(node);
        }
        return {
            name: config.name,
            description: config.desc,
            levelRange: config.levelRange,
            elementWeights: zoneIdx === 0 ? { flora: 35, zephyr: 30, pyro: 15, hydro: 10, volt: 5, earth: 5, cosmos: 0 } :
                zoneIdx === 1 ? { hydro: 35, earth: 25, flora: 15, zephyr: 10, pyro: 10, volt: 5, cosmos: 0 } :
                    zoneIdx === 2 ? { pyro: 30, volt: 30, hydro: 15, earth: 10, zephyr: 10, flora: 5, cosmos: 0 } :
                        { cosmos: 20, pyro: 20, volt: 20, hydro: 15, flora: 15, zephyr: 5, earth: 5 },
            nodes,
            bossId: BOSS_DATA[zoneIdx]?.resonantId,
            color: config.color
        };
    });
}
exports.ZONES = createZones();
function getBossData(zoneIdx) {
    return BOSS_DATA[zoneIdx];
}
function getNodeEncounter(encounterId) {
    if (!encounterId)
        return null;
    const parts = encounterId.split('_');
    if (parts.length !== 3)
        return null;
    return {
        resonantId: parts[0],
        level: parseInt(parts[1]) + Math.floor(Math.random() * (parseInt(parts[2]) - parseInt(parts[1]) + 1))
    };
}
function getMysteryCacheReward(zoneIdx) {
    const baseShards = [30, 60, 100, 150][zoneIdx];
    const variance = Math.floor(Math.random() * 20) - 10;
    const shards = baseShards + variance;
    if (Math.random() < 0.3) {
        const items = ['prism_basic', 'vital_elixir', 'status_cleanser'];
        return { shards, item: items[Math.floor(Math.random() * items.length)] };
    }
    return { shards };
}
};

// ── module: src/combat.ts ──
__mods["src/combat.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startBattle = startBattle;
exports.getPlayerActive = getPlayerActive;
exports.getStat = getStat;
exports.getMaxHp = getMaxHp;
exports.executePlayerMove = executePlayerMove;
exports.executeEnemyTurn = executeEnemyTurn;
exports.attemptCapture = attemptCapture;
exports.getBattleState = getBattleState;
exports.setCombatContext = setCombatContext;
exports.clearCombatContext = clearCombatContext;
const types_1 = require("./types");
const resonants_1 = require("./resonants");
let battleState = null;
let combatCtx = null;
let battleCallback = null;
function startBattle(enemyTemplateId, enemyLevel, isWild) {
    const template = (0, resonants_1.getResonantTemplate)(enemyTemplateId);
    if (!template)
        throw new Error(`Unknown resonant template: ${enemyTemplateId}`);
    const stats = (0, types_1.calcStats)(template, enemyLevel);
    const enemy = {
        templateId: enemyTemplateId,
        level: enemyLevel,
        currentHp: stats.hp,
        xp: 0,
        status: null,
        movesKnown: template.moves.filter(m => m.unlockLevel <= enemyLevel).map(m => m.name),
        caught: false
    };
    const playerActive = getPlayerActive(combatCtx.playerParty);
    battleState = {
        enemy,
        isWild,
        turnOrder: [],
        currentTurn: 0,
        message: `A wild ${template.name} appeared!`,
        phase: 'idle'
    };
    // Determine initial turn order based on speed
    updateTurnOrder();
    return battleState;
}
function getPlayerActive(party) {
    const alive = party.filter(r => r.currentHp > 0);
    if (alive.length === 0)
        return party[0]; // All fainted
    return alive[0];
}
function updateTurnOrder() {
    if (!battleState || !combatCtx)
        return;
    const playerActive = getPlayerActive(combatCtx.playerParty);
    const pSpeed = getStat(playerActive, 'speed');
    const eSpeed = getStat(battleState.enemy, 'speed');
    battleState.turnOrder = pSpeed >= eSpeed ? ['player', 'enemy'] : ['enemy', 'player'];
    battleState.currentTurn = 0;
    battleState.phase = 'action-select';
}
function getStat(resonant, stat) {
    const template = (0, resonants_1.getResonantTemplate)(resonant.templateId);
    if (!template)
        return 0;
    const baseStats = (0, types_1.calcStats)(template, resonant.level);
    switch (stat) {
        case 'hp': return baseStats.hp;
        case 'attack': return baseStats.attack;
        case 'defense': return baseStats.defense;
        case 'speed': return baseStats.speed;
    }
}
function getMaxHp(resonant) {
    const template = (0, resonants_1.getResonantTemplate)(resonant.templateId);
    if (!template)
        return 0;
    return (0, types_1.calcStats)(template, resonant.level).hp;
}
function executePlayerMove(moveName) {
    if (!battleState || !combatCtx)
        return;
    const playerActive = getPlayerActive(combatCtx.playerParty);
    const template = (0, resonants_1.getResonantTemplate)(playerActive.templateId);
    if (!template)
        return;
    const move = template.moves.find(m => m.name === moveName);
    if (!move)
        return;
    // Check accuracy
    if (Math.random() * 100 > move.accuracy) {
        battleState.message = `${playerActive.templateId} used ${move.name}... but it missed!`;
        endPlayerTurn();
        return;
    }
    const enemyDef = getStat(battleState.enemy, 'defense');
    let typeMod = (0, types_1.getTypeModifier)(move.element, battleState.enemy.templateId);
    // Check for critical hit
    const crit = (0, types_1.isCritical)();
    if (crit)
        typeMod *= 1.5;
    let damage = (0, types_1.calcDamage)(playerActive.level, move.power, getStat(playerActive, 'attack'), enemyDef, typeMod);
    battleState.enemy.currentHp = Math.max(0, battleState.enemy.currentHp - damage);
    const isSuperEffective = (0, types_1.getTypeModifier)(move.element, battleState.enemy.templateId) > 1;
    if (isSuperEffective) {
        battleState.message = `${move.name} is super effective! ${damage} damage!`;
    }
    else if (crit) {
        battleState.message = `Critical hit! ${move.name} dealt ${damage} damage!`;
    }
    else {
        battleState.message = `${playerActive.templateId} used ${move.name} for ${damage} damage!`;
    }
    // Check if enemy fainted
    if (battleState.enemy.currentHp <= 0) {
        battleState.phase = 'result';
        battleState.message += ` ${battleState.enemy.templateId} fainted!`;
        return;
    }
    endPlayerTurn();
}
function endPlayerTurn() {
    if (!battleState || !combatCtx)
        return;
    // Move to next turn
    const nextIdx = battleState.currentTurn + 1;
    if (nextIdx >= battleState.turnOrder.length) {
        // Enemy's turn
        battleState.phase = 'enemy-turn';
        setTimeout(() => executeEnemyTurn(), 800);
    }
    else {
        battleState.currentTurn = nextIdx;
        const turnHolder = battleState.turnOrder[nextIdx];
        if (turnHolder === 'player') {
            // Player goes again (shouldn't normally happen in 1v1)
            battleState.phase = 'action-select';
        }
        else {
            battleState.phase = 'enemy-turn';
            setTimeout(() => executeEnemyTurn(), 800);
        }
    }
}
function executeEnemyTurn() {
    if (!battleState || !combatCtx)
        return;
    const enemy = battleState.enemy;
    const playerActive = getPlayerActive(combatCtx.playerParty);
    // Pick a random move the enemy knows
    const availableMoves = enemy.movesKnown.map(name => (0, resonants_1.getResonantTemplate)(enemy.templateId)?.moves.find(m => m.name === name)).filter((m) => !!m);
    if (availableMoves.length === 0) {
        battleState.message = `${enemy.templateId} has no moves!`;
        endEnemyTurn();
        return;
    }
    const move = availableMoves[Math.floor(Math.random() * availableMoves.length)];
    // Check accuracy
    if (Math.random() * 100 > move.accuracy) {
        battleState.message = `${enemy.templateId} used ${move.name}... but it missed!`;
        endBattleResult();
        return;
    }
    const playerDef = getStat(playerActive, 'defense');
    let typeMod = (0, types_1.getTypeModifier)(move.element, playerActive.templateId);
    const crit = (0, types_1.isCritical)();
    if (crit)
        typeMod *= 1.5;
    let damage = (0, types_1.calcDamage)(enemy.level, move.power, getStat(enemy, 'attack'), playerDef, typeMod);
    // Status effect chance
    if (Math.random() < 0.1 && !playerActive.status) {
        const statuses = ['burn', 'paralysis', 'poison'];
        playerActive.status = statuses[Math.floor(Math.random() * statuses.length)];
    }
    playerActive.currentHp = Math.max(0, playerActive.currentHp - damage);
    const isSuperEffective = (0, types_1.getTypeModifier)(move.element, playerActive.templateId) > 1;
    if (isSuperEffective) {
        battleState.message = `${move.name} is super effective! ${damage} damage!`;
    }
    else if (crit) {
        battleState.message = `Critical hit! ${enemy.templateId}'s ${move.name} dealt ${damage} damage!`;
    }
    else {
        battleState.message = `${enemy.templateId} used ${move.name} for ${damage} damage!`;
    }
    if (playerActive.currentHp <= 0) {
        // Check if any party members left
        const aliveCount = combatCtx.playerParty.filter(r => r.currentHp > 0).length;
        if (aliveCount === 0) {
            battleState.phase = 'result';
            battleState.message += ` All your Resonants fainted! You blacked out...`;
            return;
        }
        else {
            battleState.phase = 'result';
            battleState.message += ` ${playerActive.templateId} fainted!`;
            return;
        }
    }
    endBattleResult();
}
function endBattleResult() {
    if (!battleState || !combatCtx)
        return;
    // Check if enemy fainted or player has no more alive
    const enemyAlive = battleState.enemy.currentHp > 0;
    const playerAlive = combatCtx.playerParty.filter(r => r.currentHp > 0).length;
    if (!enemyAlive) {
        battleState.phase = 'result';
        // Calculate XP reward
        const xpReward = Math.floor(battleState.enemy.level * 12 + Math.random() * 10);
        const shardReward = Math.floor(20 + Math.random() * 30);
        if (battleCallback) {
            battleCallback('win');
        }
        return;
    }
    if (playerAlive === 0) {
        battleState.phase = 'result';
        battleState.message += ` All your Resonants fainted! You blacked out...`;
        if (battleCallback) {
            battleCallback('win'); // Will handle defeat in caller
        }
        return;
    }
    battleState.phase = 'action-select';
}
function endEnemyTurn() {
    if (!battleState)
        return;
    // Check for status damage
    const playerActive = getPlayerActive(combatCtx.playerParty);
    if (playerActive.status === 'poison') {
        const poisonDmg = Math.floor(getMaxHp(playerActive) * 0.1);
        playerActive.currentHp = Math.max(0, playerActive.currentHp - poisonDmg);
        battleState.message += ` ${playerActive.templateId} took ${poisonDmg} poison damage!`;
    }
    else if (playerActive.status === 'burn') {
        const burnDmg = Math.floor(getMaxHp(playerActive) * 0.06);
        playerActive.currentHp = Math.max(0, playerActive.currentHp - burnDmg);
        battleState.message += ` ${playerActive.templateId} is burned! Took ${burnDmg} damage!`;
    }
    if (playerActive.currentHp <= 0) {
        const aliveCount = combatCtx.playerParty.filter(r => r.currentHp > 0).length;
        if (aliveCount === 0) {
            battleState.phase = 'result';
            battleState.message += ` All your Resonants fainted!`;
        }
        else {
            // Auto-switch to next alive
            const nextAlive = combatCtx.playerParty.find(r => r.currentHp > 0 && r.templateId !== playerActive.templateId);
            if (nextAlive) {
                battleState.message += ` Send out ${nextAlive.templateId}!`;
            }
        }
        return;
    }
    battleState.phase = 'action-select';
}
function attemptCapture(prismType) {
    if (!battleState || !combatCtx)
        return false;
    const enemy = battleState.enemy;
    const hpRatio = enemy.currentHp / getMaxHp(enemy);
    let catchRate;
    switch (prismType) {
        case 'basic':
            catchRate = 0.5;
            break;
        case 'hyper':
            catchRate = 1.25;
            break;
    }
    // Lower HP = higher catch rate
    const hpModifier = (1 - hpRatio) * 1.5 + 0.3;
    const finalRate = Math.min(0.95, catchRate * hpModifier);
    return Math.random() < finalRate;
}
function getBattleState() {
    return battleState;
}
function setCombatContext(ctx) {
    combatCtx = ctx;
}
function clearCombatContext() {
    combatCtx = null;
    battleState = null;
}
};

// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.showScreen = showScreen;
exports.initMenuScreen = initMenuScreen;
exports.renderOverworld = renderOverworld;
exports.initMap = initMap;
exports.resizeMap = resizeMap;
exports.drawMap = drawMap;
exports.setupMapClickHandler = setupMapClickHandler;
exports.initBattle = initBattle;
exports.resizeBattle = resizeBattle;
exports.renderBattle = renderBattle;
exports.drawBattleSprite = drawBattleSprite;
exports.generateMiniSprite = generateMiniSprite;
exports.renderDex = renderDex;
exports.renderShop = renderShop;
exports.showEncounter = showEncounter;
exports.showLevelUp = showLevelUp;
exports.showEvolution = showEvolution;
exports.showCaptureAnimation = showCaptureAnimation;
exports.showDialog = showDialog;
exports.showToast = showToast;
exports.spawnBattleParticle = spawnBattleParticle;
exports.screenShake = screenShake;
exports.flashScreen = flashScreen;
const resonants_1 = require("./resonants");
const map_1 = require("./map");
const combat_1 = require("./combat");
const types_1 = require("./types");
// === SCREEN MANAGEMENT ===
function showScreen(screenId) {
    document.querySelectorAll('.screen').forEach(s => s.classList.add('hidden'));
    const screen = document.getElementById(screenId);
    if (screen) {
        screen.classList.remove('hidden');
        // Trigger reflow for transition
        void screen.offsetWidth;
    }
}
// === MENU SCREEN ===
function initMenuScreen(hasSave) {
    const loadBtn = document.getElementById('btn-load-game');
    if (!hasSave) {
        loadBtn.style.opacity = '0.3';
        loadBtn.style.pointerEvents = 'none';
        loadBtn.textContent = 'No Save Found';
    }
}
// === OVERWORLD RENDERING ===
function renderOverworld(state) {
    const zone = map_1.ZONES[state.currentZone];
    // Update HUD
    document.getElementById('player-name').textContent = state.name;
    document.getElementById('zone-name').textContent = zone.name;
    document.getElementById('shard-count').textContent = state.shards.toString();
    // Update party display
    renderPartyDisplay(state);
    // Update zone nav
    updateZoneNav(state);
    // Draw map
    drawMap(state.currentZone, state.currentNode);
}
function renderPartyDisplay(state) {
    const container = document.getElementById('party-display');
    container.innerHTML = '';
    state.party.forEach((resonant, idx) => {
        const template = (0, resonants_1.getResonantTemplate)(resonant.templateId);
        if (!template)
            return;
        const slot = document.createElement('div');
        slot.className = `party-slot ${idx === 0 ? 'active' : ''} ${resonant.currentHp <= 0 ? 'fainted' : ''}`;
        slot.innerHTML = generateMiniSprite(template, resonant.level);
        slot.title = `${template.name} Lv.${resonant.level}`;
        const hpPercent = (resonant.currentHp / (0, combat_1.getMaxHp)(resonant)) * 100;
        const hpColor = hpPercent > 50 ? '#22c55e' : hpPercent > 20 ? '#eab308' : '#ef4444';
        slot.innerHTML += `
      <div class="absolute bottom-0.5 left-1 right-1 h-1 bg-slate-700 rounded-full overflow-hidden">
        <div class="h-full rounded-full" style="width:${hpPercent}%;background:${hpColor}"></div>
      </div>`;
        container.appendChild(slot);
    });
    // Fill empty slots
    for (let i = state.party.length; i < 3; i++) {
        const slot = document.createElement('div');
        slot.className = 'party-slot opacity-20';
        slot.innerHTML = '<svg class="w-6 h-6 text-slate-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/></svg>';
        container.appendChild(slot);
    }
}
function updateZoneNav(state) {
    const nav = document.getElementById('zone-nav');
    if (!nav)
        return;
    const buttons = nav.querySelectorAll('.zone-btn');
    buttons.forEach((btn, idx) => {
        const unlocked = state.unlockedZones[idx];
        btn.disabled = !unlocked;
        btn.classList.toggle('opacity-50', !unlocked);
        btn.classList.toggle('cursor-not-allowed', !unlocked);
        btn.classList.toggle('active', idx === state.currentZone);
    });
}
// === MAP RENDERING (Canvas) ===
let mapCtx = null;
let mapCanvas = null;
function initMap() {
    mapCanvas = document.getElementById('map-canvas');
    if (!mapCanvas)
        return;
    // Use a reasonable default size if container has no dimensions yet
    const container = document.getElementById('map-container');
    const w = container?.clientWidth || 800;
    const h = container?.clientHeight || 500;
    mapCanvas.width = Math.max(w, 400);
    mapCanvas.height = Math.max(h, 300);
    mapCtx = mapCanvas.getContext('2d');
}
function resizeMap() {
    if (!mapCanvas)
        return;
    const container = document.getElementById('map-container');
    if (container) {
        mapCanvas.width = container.clientWidth;
        mapCanvas.height = container.clientHeight;
        mapCtx = mapCanvas.getContext('2d');
    }
}
function drawMap(zoneIdx, currentNode) {
    if (!mapCtx || !mapCanvas)
        return;
    const ctx = mapCtx;
    const canvas = mapCanvas;
    const w = canvas.width;
    const h = canvas.height;
    const zone = map_1.ZONES[zoneIdx];
    // Clear with gradient background
    const bgGrad = ctx.createLinearGradient(0, 0, 0, h);
    bgGrad.addColorStop(0, '#0f172a');
    bgGrad.addColorStop(1, '#1e1b4b');
    mapCtx.fillStyle = bgGrad;
    mapCtx.fillRect(0, 0, w, h);
    // Draw trail connections
    const nodes = zone.nodes;
    ctx.strokeStyle = 'rgba(139, 92, 246, 0.2)';
    ctx.lineWidth = 3;
    ctx.setLineDash([8, 4]);
    ctx.beginPath();
    for (let i = 0; i < nodes.length - 1; i++) {
        if (!nodes[i].cleared && !nodes[i + 1].cleared) {
            ctx.moveTo(nodes[i].x, nodes[i].y);
            ctx.lineTo(nodes[i + 1].x, nodes[i + 1].y);
        }
    }
    ctx.stroke();
    ctx.setLineDash([]);
    // Draw nodes
    nodes.forEach((node, idx) => {
        drawNode(ctx, node, idx === currentNode, zoneIdx);
    });
    // Draw current position indicator
    if (nodes[currentNode]) {
        const cn = nodes[currentNode];
        ctx.save();
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(cn.x, cn.y, 28, 0, Math.PI * 2);
        ctx.stroke();
        // Pulsing ring
        const pulse = (Math.sin(Date.now() / 300) + 1) / 2;
        ctx.strokeStyle = `rgba(139, 92, 246, ${0.3 + pulse * 0.4})`;
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(cn.x, cn.y, 28 + pulse * 8, 0, Math.PI * 2);
        ctx.stroke();
        ctx.restore();
    }
    // Zone label
    ctx.save();
    ctx.font = '14px Inter';
    ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
    ctx.textAlign = 'center';
    ctx.fillText(zone.name, w / 2, h - 20);
    ctx.restore();
}
function drawAmbientParticles(zoneIdx) {
    if (!mapCtx || !mapCanvas)
        return;
    const ctx = mapCtx;
    const canvas = mapCanvas;
    const colors = [
        ['#22c55e', '#16a34a'],
        ['#06b6d4', '#0891b2'],
        ['#ef4444', '#f97316'],
        ['#a855f7', '#8b5cf6']
    ];
    const [c1, c2] = colors[zoneIdx];
    const time = Date.now() / 1000;
    for (let i = 0; i < 30; i++) {
        const x = (Math.sin(time * 0.3 + i * 1.7) + 1) / 2 * canvas.width;
        const y = (Math.cos(time * 0.2 + i * 2.3) + 1) / 2 * canvas.height;
        const size = 1.5 + Math.sin(time + i) * 1;
        const alpha = 0.15 + Math.sin(time * 0.5 + i) * 0.1;
        ctx.save();
        ctx.fillStyle = i % 2 === 0 ? c1 : c2;
        ctx.globalAlpha = alpha;
        ctx.beginPath();
        ctx.arc(x, y, size, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
    }
}
function drawNode(ctx, node, isCurrent, zoneIdx) {
    const { x, y, type, cleared } = node;
    ctx.save();
    if (cleared) {
        ctx.globalAlpha = 0.4;
    }
    // Node glow
    const glowColors = {
        wild: '#22c55e', mystery: '#facc15', npc: '#a78bfa',
        healing: '#06b6d4', boss: '#ef4444'
    };
    const glowColor = glowColors[type] || '#ffffff';
    if (isCurrent && !cleared) {
        ctx.shadowColor = glowColor;
        ctx.shadowBlur = 20;
    }
    // Node body
    const size = type === 'boss' ? 24 : 18;
    ctx.fillStyle = cleared ? '#334155' : glowColor;
    if (type === 'wild') {
        // Grass tuft shape
        drawGrassNode(ctx, x, y, size);
    }
    else if (type === 'mystery') {
        // Diamond shape
        ctx.beginPath();
        ctx.moveTo(x, y - size);
        ctx.lineTo(x + size * 0.7, y);
        ctx.lineTo(x, y + size);
        ctx.lineTo(x - size * 0.7, y);
        ctx.closePath();
        ctx.fill();
    }
    else if (type === 'npc') {
        // Circle with person icon
        ctx.beginPath();
        ctx.arc(x, y, size, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#ffffff';
        ctx.font = `${size}px serif`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText('👤', x, y + 1);
    }
    else if (type === 'healing') {
        // Cross shape
        drawCrossNode(ctx, x, y, size);
    }
    else if (type === 'boss') {
        // Star shape
        drawStarNode(ctx, x, y, size);
    }
    ctx.restore();
}
function drawGrassNode(ctx, x, y, size) {
    ctx.beginPath();
    ctx.moveTo(x, y + size);
    ctx.quadraticCurveTo(x - size, y + size * 0.3, x - size * 0.5, y - size * 0.2);
    ctx.quadraticCurveTo(x - size * 0.3, y - size * 0.8, x, y - size);
    ctx.quadraticCurveTo(x + size * 0.3, y - size * 0.8, x + size * 0.5, y - size * 0.2);
    ctx.quadraticCurveTo(x + size, y + size * 0.3, x, y + size);
    ctx.closePath();
    ctx.fill();
}
function drawCrossNode(ctx, x, y, size) {
    const w = size * 0.35;
    ctx.beginPath();
    ctx.moveTo(x - w, y - size);
    ctx.lineTo(x + w, y - size);
    ctx.lineTo(x + w, y - w);
    ctx.lineTo(x + size, y - w);
    ctx.lineTo(x + size, y + w);
    ctx.lineTo(x + w, y + w);
    ctx.lineTo(x + w, y + size);
    ctx.lineTo(x - w, y + size);
    ctx.lineTo(x - w, y + w);
    ctx.lineTo(x - size, y + w);
    ctx.lineTo(x - size, y - w);
    ctx.lineTo(x - w, y - w);
    ctx.closePath();
    ctx.fill();
}
function drawStarNode(ctx, x, y, size) {
    const spikes = 5;
    const outerRadius = size;
    const innerRadius = size * 0.4;
    ctx.beginPath();
    for (let i = 0; i < spikes * 2; i++) {
        const radius = i % 2 === 0 ? outerRadius : innerRadius;
        const angle = (Math.PI / spikes) * i - Math.PI / 2;
        const px = x + Math.cos(angle) * radius;
        const py = y + Math.sin(angle) * radius;
        if (i === 0)
            ctx.moveTo(px, py);
        else
            ctx.lineTo(px, py);
    }
    ctx.closePath();
    ctx.fill();
}
// Click handler for map nodes
function setupMapClickHandler(onNodeClick) {
    const canvas = document.getElementById('map-canvas');
    if (!canvas)
        return;
    canvas.addEventListener('click', (e) => {
        const rect = canvas.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        // Find closest node
        let closestIdx = -1;
        let closestDist = Infinity;
        const currentZoneIdx = 0; // Will be set by game state
        map_1.ZONES[currentZoneIdx]?.nodes?.forEach((node, nodeIdx) => {
            const dist = Math.sqrt((x - node.x) ** 2 + (y - node.y) ** 2);
            if (dist < 30 && dist < closestDist) {
                closestDist = dist;
                closestIdx = nodeIdx;
            }
        });
        if (closestIdx >= 0) {
            onNodeClick(currentZoneIdx, closestIdx);
        }
    });
}
// === BATTLE RENDERING ===
let battleAnimFrame = null;
let battleCtx = null;
let battleCanvas = null;
function initBattle() {
    battleCanvas = document.getElementById('battle-canvas');
    if (!battleCanvas)
        return;
    const arena = document.getElementById('battle-arena');
    const w = arena?.clientWidth || 800;
    const h = arena?.clientHeight || 500;
    battleCanvas.width = Math.max(w, 400);
    battleCanvas.height = Math.max(h, 300);
    battleCtx = battleCanvas.getContext('2d');
}
function resizeBattle() {
    if (!battleCanvas)
        return;
    const arena = document.getElementById('battle-arena');
    if (arena) {
        battleCanvas.width = arena.clientWidth;
        battleCanvas.height = arena.clientHeight;
        battleCtx = battleCanvas.getContext('2d');
    }
}
function renderBattle(state) {
    // Update enemy info
    const enemyTemplate = (0, resonants_1.getResonantTemplate)(state.enemy.templateId);
    if (enemyTemplate) {
        document.getElementById('enemy-name').textContent = enemyTemplate.name;
        document.getElementById('enemy-level').textContent = `Lv.${state.enemy.level}`;
        const maxHp = (0, combat_1.getMaxHp)(state.enemy);
        const hpPercent = (state.enemy.currentHp / maxHp) * 100;
        const hpFill = document.getElementById('enemy-hp-fill');
        hpFill.style.width = `${hpPercent}%`;
        hpFill.className = `hp-fill h-full rounded-full transition-all duration-500 ${hpPercent > 50 ? '' : hpPercent > 20 ? 'mid' : 'low'}`;
        document.getElementById('enemy-hp-text').textContent = `${state.enemy.currentHp}/${maxHp}`;
        document.getElementById('enemy-hp-max').textContent = `HP`;
        const statusEl = document.getElementById('enemy-status');
        if (state.enemy.status) {
            statusEl.textContent = `Status: ${state.enemy.status.toUpperCase()}`;
            statusEl.className = 'mt-2 text-xs font-bold';
        }
        else {
            statusEl.textContent = '';
        }
    }
    // Update player info
    const playerActive = state.enemy ? null : null; // Will be set by caller
    document.getElementById('battle-msg-text').textContent = state.message;
    // Draw battle scene
    drawBattleScene(state);
}
function drawBattleScene(state) {
    if (!battleCtx || !battleCanvas)
        return;
    const w = battleCanvas.width;
    const h = battleCanvas.height;
    // Background gradient
    const bgGrad = battleCtx.createLinearGradient(0, 0, 0, h);
    bgGrad.addColorStop(0, '#1e1b4b');
    bgGrad.addColorStop(0.5, '#0f172a');
    bgGrad.addColorStop(1, '#172554');
    battleCtx.fillStyle = bgGrad;
    battleCtx.fillRect(0, 0, w, h);
    // Ground plane
    const groundY = h * 0.65;
    const groundGrad = battleCtx.createLinearGradient(0, groundY, 0, h);
    groundGrad.addColorStop(0, 'rgba(34, 197, 94, 0.1)');
    groundGrad.addColorStop(1, 'rgba(15, 23, 42, 0.3)');
    battleCtx.fillStyle = groundGrad;
    battleCtx.beginPath();
    battleCtx.moveTo(0, groundY);
    battleCtx.quadraticCurveTo(w / 2, groundY - 30, w, groundY);
    battleCtx.lineTo(w, h);
    battleCtx.lineTo(0, h);
    battleCtx.closePath();
    battleCtx.fill();
    // Draw enemy sprite (right side)
    const enemyTemplate = (0, resonants_1.getResonantTemplate)(state.enemy.templateId);
    if (enemyTemplate) {
        drawBattleSprite(battleCtx, w * 0.7, h * 0.35, enemyTemplate, state.enemy.level, false);
    }
    // Draw player sprite (left side) - placeholder for now
    const playerActive = document.getElementById('player-resonant-name')?.textContent;
    if (playerActive) {
        const pTemplate = resonants_1.RESONANT_DB.find(r => r.name === playerActive);
        if (pTemplate) {
            drawBattleSprite(battleCtx, w * 0.3, h * 0.55, pTemplate, 1, true);
        }
    }
    // Animated particles in background
    const time = Date.now() / 1000;
    for (let i = 0; i < 15; i++) {
        const px = (Math.sin(time * 0.2 + i) + 1) / 2 * w;
        const py = (Math.cos(time * 0.15 + i * 1.3) + 1) / 2 * h * 0.6;
        battleCtx.save();
        battleCtx.fillStyle = 'rgba(139, 92, 246, 0.15)';
        battleCtx.beginPath();
        battleCtx.arc(px, py, 2 + Math.sin(time + i) * 1, 0, Math.PI * 2);
        battleCtx.fill();
        battleCtx.restore();
    }
}
function drawBattleSprite(ctx, x, y, template, level, isPlayer) {
    const size = 60 + level * 1.5;
    ctx.save();
    // Bounce animation for player sprites
    if (isPlayer) {
        const bounce = Math.sin(Date.now() / 500) * 5;
        y += bounce;
    }
    const colors = template.spriteParams.colors;
    // Glow effect
    ctx.shadowColor = colors[0];
    ctx.shadowBlur = 15;
    // Main body based on shape type
    switch (template.spriteParams.shape) {
        case 'circle':
            drawCircleSprite(ctx, x, y, size, colors);
            break;
        case 'drop':
            drawDropSprite(ctx, x, y, size, colors);
            break;
        case 'plant':
            drawPlantSprite(ctx, x, y, size, colors);
            break;
        case 'mouse':
            drawMouseSprite(ctx, x, y, size, colors);
            break;
        case 'spirit':
            drawSpiritSprite(ctx, x, y, size, colors);
            break;
        case 'rock':
            drawRockSprite(ctx, x, y, size, colors);
            break;
        case 'wolf':
            drawWolfSprite(ctx, x, y, size, colors);
            break;
        case 'mammal':
            drawMammalSprite(ctx, x, y, size, colors);
            break;
        case 'turtle':
            drawTurtleSprite(ctx, x, y, size, colors);
            break;
        case 'cat':
            drawCatSprite(ctx, x, y, size, colors);
            break;
        case 'bird':
            drawBirdSprite(ctx, x, y, size, colors);
            break;
        case 'golem':
            drawGolemSprite(ctx, x, y, size, colors);
            break;
        case 'dinosaur':
            drawDinoSprite(ctx, x, y, size, colors);
            break;
        case 'leviathan':
            drawLeviathanSprite(ctx, x, y, size, colors);
            break;
        case 'colossus':
            drawColossusSprite(ctx, x, y, size, colors);
            break;
        case 'dragon':
            drawDragonSprite(ctx, x, y, size, colors);
            break;
        case 'angel':
            drawAngelSprite(ctx, x, y, size, colors);
            break;
        case 'titan':
            drawTitanSprite(ctx, x, y, size, colors);
            break;
        default:
            // Fallback circle
            drawCircleSprite(ctx, x, y, size, ['#888', '#666']);
    }
    ctx.restore();
}
function drawCircleSprite(ctx, x, y, size, colors) {
    const grad = ctx.createRadialGradient(x - size * 0.2, y - size * 0.2, 0, x, y, size);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.arc(x, y, size, 0, Math.PI * 2);
    ctx.fill();
    // Eyes
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.ellipse(x - size * 0.25, y - size * 0.1, size * 0.12, size * 0.15, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.ellipse(x + size * 0.25, y - size * 0.1, size * 0.12, size * 0.15, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#1e1b4b';
    ctx.beginPath();
    ctx.arc(x - size * 0.2, y - size * 0.08, size * 0.06, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.3, y - size * 0.08, size * 0.06, 0, Math.PI * 2);
    ctx.fill();
}
function drawDropSprite(ctx, x, y, size, colors) {
    const grad = ctx.createLinearGradient(x, y - size, x, y + size);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.moveTo(x, y - size);
    ctx.quadraticCurveTo(x + size * 0.8, y - size * 0.2, x + size * 0.6, y + size * 0.3);
    ctx.quadraticCurveTo(x + size * 0.3, y + size, x, y + size * 0.8);
    ctx.quadraticCurveTo(x - size * 0.3, y + size, x - size * 0.6, y + size * 0.3);
    ctx.quadraticCurveTo(x - size * 0.8, y - size * 0.2, x, y - size);
    ctx.closePath();
    ctx.fill();
    // Highlight
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    ctx.beginPath();
    ctx.ellipse(x - size * 0.15, y - size * 0.2, size * 0.15, size * 0.25, -0.3, 0, Math.PI * 2);
    ctx.fill();
    // Eyes
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x - size * 0.2, y + size * 0.05, size * 0.1, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.2, y + size * 0.05, size * 0.1, 0, Math.PI * 2);
    ctx.fill();
}
function drawPlantSprite(ctx, x, y, size, colors) {
    // Pot/body
    const grad = ctx.createRadialGradient(x, y + size * 0.3, 0, x, y + size * 0.3, size * 0.7);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.ellipse(x, y + size * 0.3, size * 0.6, size * 0.5, 0, 0, Math.PI * 2);
    ctx.fill();
    // Leaves on top
    const leafAngles = [-0.8, -0.3, 0.3, 0.8];
    leafAngles.forEach(angle => {
        ctx.save();
        ctx.translate(x, y - size * 0.2);
        ctx.rotate(angle);
        ctx.fillStyle = colors[1];
        ctx.beginPath();
        ctx.ellipse(0, -size * 0.4, size * 0.15, size * 0.35, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
    });
    // Eyes
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x - size * 0.2, y + size * 0.15, size * 0.08, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.2, y + size * 0.15, size * 0.08, 0, Math.PI * 2);
    ctx.fill();
}
function drawMouseSprite(ctx, x, y, size, colors) {
    // Body
    const grad = ctx.createRadialGradient(x, y, 0, x, y, size * 0.8);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.ellipse(x, y + size * 0.1, size * 0.55, size * 0.65, 0, 0, Math.PI * 2);
    ctx.fill();
    // Ears
    ctx.fillStyle = colors[0];
    ctx.beginPath();
    ctx.moveTo(x - size * 0.4, y - size * 0.3);
    ctx.lineTo(x - size * 0.6, y - size * 0.8);
    ctx.lineTo(x - size * 0.1, y - size * 0.5);
    ctx.closePath();
    ctx.fill();
    ctx.beginPath();
    ctx.moveTo(x + size * 0.4, y - size * 0.3);
    ctx.lineTo(x + size * 0.6, y - size * 0.8);
    ctx.lineTo(x + size * 0.1, y - size * 0.5);
    ctx.closePath();
    ctx.fill();
    // Tail (lightning bolt)
    ctx.strokeStyle = colors[1];
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(x + size * 0.3, y + size * 0.6);
    ctx.lineTo(x + size * 0.6, y + size * 0.4);
    ctx.lineTo(x + size * 0.4, y + size * 0.7);
    ctx.stroke();
    // Eyes
    ctx.fillStyle = '#1e1b4b';
    ctx.beginPath();
    ctx.arc(x - size * 0.2, y, size * 0.08, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.2, y, size * 0.08, 0, Math.PI * 2);
    ctx.fill();
}
function drawSpiritSprite(ctx, x, y, size, colors) {
    // Ghostly body
    const grad = ctx.createRadialGradient(x, y - size * 0.2, 0, x, y, size);
    grad.addColorStop(0, 'rgba(167, 139, 250, 0.8)');
    grad.addColorStop(0.7, colors[0]);
    grad.addColorStop(1, 'rgba(139, 92, 246, 0.2)');
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.arc(x, y - size * 0.1, size * 0.7, Math.PI, 0);
    // Wavy bottom
    for (let i = 0; i < 5; i++) {
        const wx = x - size * 0.7 + (i + 0.5) * size * 0.35;
        const wy = y + size * 0.5 + (i % 2 === 0 ? -size * 0.15 : size * 0.05);
        ctx.quadraticCurveTo(wx, wy, x - size * 0.7 + (i + 1) * size * 0.35, y + size * 0.5);
    }
    ctx.closePath();
    ctx.fill();
    // Eyes
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.ellipse(x - size * 0.2, y - size * 0.1, size * 0.1, size * 0.13, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.ellipse(x + size * 0.2, y - size * 0.1, size * 0.1, size * 0.13, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#4c1d95';
    ctx.beginPath();
    ctx.arc(x - size * 0.15, y - size * 0.08, size * 0.05, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.25, y - size * 0.08, size * 0.05, 0, Math.PI * 2);
    ctx.fill();
}
function drawRockSprite(ctx, x, y, size, colors) {
    // Irregular rock shape
    const grad = ctx.createRadialGradient(x - size * 0.2, y - size * 0.2, 0, x, y, size);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.moveTo(x - size * 0.5, y + size * 0.3);
    ctx.lineTo(x - size * 0.7, y + size * 0.1);
    ctx.lineTo(x - size * 0.4, y - size * 0.6);
    ctx.lineTo(x, y - size * 0.8);
    ctx.lineTo(x + size * 0.5, y - size * 0.5);
    ctx.lineTo(x + size * 0.7, y + size * 0.1);
    ctx.lineTo(x + size * 0.3, y + size * 0.4);
    ctx.closePath();
    ctx.fill();
    // Crystal veins
    ctx.strokeStyle = 'rgba(250, 204, 21, 0.6)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(x - size * 0.1, y - size * 0.3);
    ctx.lineTo(x + size * 0.1, y);
    ctx.lineTo(x - size * 0.05, y + size * 0.3);
    ctx.stroke();
    // Eyes
    ctx.fillStyle = '#facc15';
    ctx.beginPath();
    ctx.arc(x - size * 0.2, y - size * 0.05, size * 0.07, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.2, y - size * 0.05, size * 0.07, 0, Math.PI * 2);
    ctx.fill();
}
function drawWolfSprite(ctx, x, y, size, colors) {
    // Body
    const grad = ctx.createRadialGradient(x, y + size * 0.1, 0, x, y + size * 0.1, size * 0.8);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.ellipse(x, y + size * 0.2, size * 0.65, size * 0.5, 0, 0, Math.PI * 2);
    ctx.fill();
    // Head
    ctx.fillStyle = colors[0];
    ctx.beginPath();
    ctx.arc(x - size * 0.15, y - size * 0.35, size * 0.4, 0, Math.PI * 2);
    ctx.fill();
    // Ears (pointed, fiery)
    ctx.fillStyle = colors[1];
    ctx.beginPath();
    ctx.moveTo(x - size * 0.4, y - size * 0.55);
    ctx.lineTo(x - size * 0.25, y - size * 0.9);
    ctx.lineTo(x - size * 0.05, y - size * 0.55);
    ctx.closePath();
    ctx.fill();
    ctx.beginPath();
    ctx.moveTo(x + size * 0.1, y - size * 0.55);
    ctx.lineTo(x + size * 0.25, y - size * 0.85);
    ctx.lineTo(x + size * 0.35, y - size * 0.5);
    ctx.closePath();
    ctx.fill();
    // Fire mane
    const flameColors = ['#f97316', '#ef4444', '#facc15'];
    for (let i = 0; i < 5; i++) {
        ctx.fillStyle = flameColors[i % 3];
        ctx.globalAlpha = 0.7;
        const fx = x + size * 0.2 + Math.sin(i) * size * 0.15;
        const fy = y - size * 0.6 + i * size * 0.1;
        ctx.beginPath();
        ctx.moveTo(fx, fy);
        ctx.quadraticCurveTo(fx + size * 0.15, fy - size * 0.2, fx + size * 0.05, fy - size * 0.3);
        ctx.quadraticCurveTo(fx - size * 0.05, fy - size * 0.15, fx, fy);
        ctx.fill();
    }
    ctx.globalAlpha = 1;
    // Eyes (glowing)
    ctx.fillStyle = '#facc15';
    ctx.shadowColor = '#f97316';
    ctx.shadowBlur = 8;
    ctx.beginPath();
    ctx.ellipse(x - size * 0.25, y - size * 0.35, size * 0.08, size * 0.1, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.ellipse(x + size * 0.05, y - size * 0.35, size * 0.08, size * 0.1, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;
}
function drawMammalSprite(ctx, x, y, size, colors) {
    // Body (streamlined fish-like)
    const grad = ctx.createLinearGradient(x - size * 0.6, y, x + size * 0.6, y);
    grad.addColorStop(0, colors[0]);
    grad.addColorStop(1, colors[1]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.moveTo(x - size * 0.7, y);
    ctx.quadraticCurveTo(x - size * 0.8, y + size * 0.3, x - size * 0.4, y + size * 0.5);
    ctx.quadraticCurveTo(x, y + size * 0.6, x + size * 0.5, y + size * 0.3);
    ctx.lineTo(x + size * 0.8, y + size * 0.1);
    // Tail
    ctx.moveTo(x + size * 0.7, y + size * 0.05);
    ctx.lineTo(x + size * 1.0, y - size * 0.2);
    ctx.lineTo(x + size * 0.85, y);
    ctx.lineTo(x + size * 1.0, y + size * 0.2);
    ctx.lineTo(x + size * 0.7, y + size * 0.15);
    ctx.closePath();
    ctx.fill();
    // Crown fin
    ctx.fillStyle = colors[1];
    ctx.beginPath();
    ctx.moveTo(x - size * 0.2, y - size * 0.3);
    ctx.quadraticCurveTo(x, y - size * 0.8, x + size * 0.2, y - size * 0.3);
    ctx.fill();
    // Eye
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x - size * 0.35, y - size * 0.05, size * 0.1, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#0e7490';
    ctx.beginPath();
    ctx.arc(x - size * 0.32, y - size * 0.03, size * 0.05, 0, Math.PI * 2);
    ctx.fill();
}
function drawTurtleSprite(ctx, x, y, size, colors) {
    // Shell (hexagonal)
    const grad = ctx.createRadialGradient(x, y + size * 0.1, 0, x, y + size * 0.1, size * 0.7);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    for (let i = 0; i < 6; i++) {
        const angle = (Math.PI / 3) * i - Math.PI / 6;
        const px = x + Math.cos(angle) * size * 0.7;
        const py = y + size * 0.1 + Math.sin(angle) * size * 0.6;
        if (i === 0)
            ctx.moveTo(px, py);
        else
            ctx.lineTo(px, py);
    }
    ctx.closePath();
    ctx.fill();
    // Thorn patterns on shell
    ctx.strokeStyle = 'rgba(0,0,0,0.3)';
    ctx.lineWidth = 2;
    for (let i = 0; i < 6; i++) {
        const angle = (Math.PI / 3) * i;
        ctx.beginPath();
        ctx.moveTo(x + Math.cos(angle) * size * 0.2, y + size * 0.1 + Math.sin(angle) * size * 0.15);
        ctx.lineTo(x + Math.cos(angle) * size * 0.5, y + size * 0.1 + Math.sin(angle) * size * 0.4);
        ctx.stroke();
    }
    // Head
    ctx.fillStyle = colors[0];
    ctx.beginPath();
    ctx.arc(x - size * 0.6, y - size * 0.15, size * 0.25, 0, Math.PI * 2);
    ctx.fill();
    // Thorns on back
    ctx.fillStyle = '#15803d';
    for (let i = 0; i < 4; i++) {
        const tx = x - size * 0.3 + i * size * 0.25;
        const ty = y - size * 0.35;
        ctx.beginPath();
        ctx.moveTo(tx - size * 0.08, ty);
        ctx.lineTo(tx, ty - size * 0.25);
        ctx.lineTo(tx + size * 0.08, ty);
        ctx.closePath();
        ctx.fill();
    }
    // Eyes
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x - size * 0.68, y - size * 0.2, size * 0.06, 0, Math.PI * 2);
    ctx.fill();
}
function drawCatSprite(ctx, x, y, size, colors) {
    // Body
    const grad = ctx.createRadialGradient(x, y + size * 0.1, 0, x, y + size * 0.1, size * 0.7);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.ellipse(x, y + size * 0.2, size * 0.5, size * 0.6, 0, 0, Math.PI * 2);
    ctx.fill();
    // Head
    ctx.fillStyle = colors[0];
    ctx.beginPath();
    ctx.arc(x, y - size * 0.35, size * 0.42, 0, Math.PI * 2);
    ctx.fill();
    // Lightning bolt ears
    ctx.fillStyle = colors[1];
    // Left ear
    ctx.beginPath();
    ctx.moveTo(x - size * 0.35, y - size * 0.5);
    ctx.lineTo(x - size * 0.2, y - size * 0.9);
    ctx.lineTo(x - size * 0.05, y - size * 0.48);
    ctx.closePath();
    ctx.fill();
    // Right ear
    ctx.beginPath();
    ctx.moveTo(x + size * 0.35, y - size * 0.5);
    ctx.lineTo(x + size * 0.2, y - size * 0.9);
    ctx.lineTo(x + size * 0.05, y - size * 0.48);
    ctx.closePath();
    ctx.fill();
    // Eyes (slitted, electric)
    ctx.fillStyle = '#ef4444';
    ctx.shadowColor = '#f59e0b';
    ctx.shadowBlur = 10;
    ctx.beginPath();
    ctx.ellipse(x - size * 0.15, y - size * 0.35, size * 0.06, size * 0.12, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.ellipse(x + size * 0.15, y - size * 0.35, size * 0.06, size * 0.12, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;
    // Tail (lightning)
    ctx.strokeStyle = colors[1];
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(x + size * 0.3, y + size * 0.6);
    ctx.quadraticCurveTo(x + size * 0.7, y + size * 0.4, x + size * 0.5, y + size * 0.1);
    ctx.stroke();
}
function drawBirdSprite(ctx, x, y, size, colors) {
    // Body
    const grad = ctx.createRadialGradient(x, y, 0, x, y, size * 0.6);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.ellipse(x, y + size * 0.1, size * 0.45, size * 0.55, 0, 0, Math.PI * 2);
    ctx.fill();
    // Wings (wind effect)
    const time = Date.now() / 200;
    ctx.fillStyle = colors[0];
    ctx.globalAlpha = 0.8;
    // Left wing
    ctx.beginPath();
    ctx.moveTo(x - size * 0.3, y);
    ctx.quadraticCurveTo(x - size * 0.9, y - size * 0.5 + Math.sin(time) * 10, x - size * 0.7, y - size * 0.8);
    ctx.quadraticCurveTo(x - size * 0.4, y - size * 0.3, x - size * 0.2, y + size * 0.1);
    ctx.fill();
    // Right wing
    ctx.beginPath();
    ctx.moveTo(x + size * 0.3, y);
    ctx.quadraticCurveTo(x + size * 0.9, y - size * 0.5 + Math.cos(time) * 10, x + size * 0.7, y - size * 0.8);
    ctx.quadraticCurveTo(x + size * 0.4, y - size * 0.3, x + size * 0.2, y + size * 0.1);
    ctx.fill();
    ctx.globalAlpha = 1;
    // Head
    ctx.fillStyle = colors[1];
    ctx.beginPath();
    ctx.arc(x, y - size * 0.45, size * 0.3, 0, Math.PI * 2);
    ctx.fill();
    // Beak
    ctx.fillStyle = '#f59e0b';
    ctx.beginPath();
    ctx.moveTo(x + size * 0.2, y - size * 0.4);
    ctx.lineTo(x + size * 0.45, y - size * 0.3);
    ctx.lineTo(x + size * 0.2, y - size * 0.2);
    ctx.closePath();
    ctx.fill();
    // Eyes
    ctx.fillStyle = '#1e1b4b';
    ctx.beginPath();
    ctx.arc(x - size * 0.1, y - size * 0.48, size * 0.06, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.1, y - size * 0.48, size * 0.06, 0, Math.PI * 2);
    ctx.fill();
}
function drawGolemSprite(ctx, x, y, size, colors) {
    // Body (blocky)
    const grad = ctx.createLinearGradient(x - size * 0.5, y, x + size * 0.5, y + size);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    // Torso
    roundRect(ctx, x - size * 0.45, y - size * 0.2, size * 0.9, size * 0.8, 8);
    ctx.fill();
    // Head
    ctx.fillStyle = colors[1];
    roundRect(ctx, x - size * 0.3, y - size * 0.75, size * 0.6, size * 0.45, 6);
    ctx.fill();
    // Arms
    ctx.fillStyle = colors[0];
    roundRect(ctx, x - size * 0.75, y - size * 0.1, size * 0.25, size * 0.6, 4);
    ctx.fill();
    roundRect(ctx, x + size * 0.5, y - size * 0.1, size * 0.25, size * 0.6, 4);
    ctx.fill();
    // Lava cracks
    ctx.strokeStyle = '#ef4444';
    ctx.lineWidth = 2;
    ctx.shadowColor = '#ef4444';
    ctx.shadowBlur = 8;
    ctx.beginPath();
    ctx.moveTo(x - size * 0.1, y - size * 0.1);
    ctx.lineTo(x + size * 0.05, y + size * 0.15);
    ctx.lineTo(x - size * 0.05, y + size * 0.35);
    ctx.stroke();
    ctx.shadowBlur = 0;
    // Eyes (glowing orange)
    ctx.fillStyle = '#ef4444';
    ctx.shadowColor = '#ef4444';
    ctx.shadowBlur = 12;
    ctx.beginPath();
    ctx.arc(x - size * 0.12, y - size * 0.5, size * 0.06, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.12, y - size * 0.5, size * 0.06, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;
}
function drawDinoSprite(ctx, x, y, size, colors) {
    // Body
    const grad = ctx.createRadialGradient(x, y + size * 0.1, 0, x, y + size * 0.1, size * 0.7);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.ellipse(x, y + size * 0.15, size * 0.6, size * 0.5, 0, 0, Math.PI * 2);
    ctx.fill();
    // Neck and head
    ctx.fillStyle = colors[0];
    ctx.beginPath();
    ctx.moveTo(x + size * 0.3, y - size * 0.1);
    ctx.quadraticCurveTo(x + size * 0.5, y - size * 0.5, x + size * 0.4, y - size * 0.7);
    ctx.quadraticCurveTo(x + size * 0.6, y - size * 0.8, x + size * 0.5, y - size * 0.9);
    ctx.quadraticCurveTo(x + size * 0.7, y - size * 0.7, x + size * 0.55, y - size * 0.5);
    ctx.lineTo(x + size * 0.35, y - size * 0.2);
    ctx.closePath();
    ctx.fill();
    // Lava plates on back
    ctx.fillStyle = '#ef4444';
    for (let i = 0; i < 4; i++) {
        const px = x - size * 0.3 + i * size * 0.2;
        const py = y - size * 0.25 - Math.sin(i * 1.5) * size * 0.15;
        ctx.beginPath();
        ctx.moveTo(px - size * 0.06, py);
        ctx.lineTo(px, py - size * 0.2);
        ctx.lineTo(px + size * 0.06, py);
        ctx.closePath();
        ctx.fill();
    }
    // Tail
    ctx.fillStyle = colors[1];
    ctx.beginPath();
    ctx.moveTo(x - size * 0.5, y + size * 0.1);
    ctx.quadraticCurveTo(x - size * 0.9, y + size * 0.3, x - size * 0.8, y + size * 0.6);
    ctx.quadraticCurveTo(x - size * 0.7, y + size * 0.4, x - size * 0.5, y + size * 0.2);
    ctx.closePath();
    ctx.fill();
    // Eyes (fierce)
    ctx.fillStyle = '#facc15';
    ctx.shadowColor = '#ef4444';
    ctx.shadowBlur = 8;
    ctx.beginPath();
    ctx.ellipse(x + size * 0.5, y - size * 0.7, size * 0.06, size * 0.08, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;
}
function drawLeviathanSprite(ctx, x, y, size, colors) {
    // Massive body
    const grad = ctx.createLinearGradient(x - size * 0.7, y, x + size * 0.7, y);
    grad.addColorStop(0, colors[0]);
    grad.addColorStop(1, colors[1]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.moveTo(x - size * 0.7, y + size * 0.1);
    ctx.quadraticCurveTo(x - size * 0.8, y + size * 0.4, x - size * 0.5, y + size * 0.5);
    ctx.quadraticCurveTo(x, y + size * 0.6, x + size * 0.6, y + size * 0.3);
    // Tail fin
    ctx.lineTo(x + size * 0.9, y + size * 0.1);
    ctx.lineTo(x + size * 0.7, y - size * 0.1);
    ctx.quadraticCurveTo(x + size * 0.3, y - size * 0.2, x - size * 0.3, y - size * 0.3);
    ctx.quadraticCurveTo(x - size * 0.6, y - size * 0.2, x - size * 0.7, y + size * 0.1);
    ctx.closePath();
    ctx.fill();
    // Crown (kingly)
    ctx.fillStyle = '#facc15';
    ctx.beginPath();
    ctx.moveTo(x - size * 0.3, y - size * 0.25);
    ctx.lineTo(x - size * 0.25, y - size * 0.55);
    ctx.lineTo(x - size * 0.1, y - size * 0.35);
    ctx.lineTo(x, y - size * 0.65);
    ctx.lineTo(x + size * 0.1, y - size * 0.35);
    ctx.lineTo(x + size * 0.25, y - size * 0.55);
    ctx.lineTo(x + size * 0.3, y - size * 0.25);
    ctx.closePath();
    ctx.fill();
    // Eyes (regal blue)
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x - size * 0.15, y - size * 0.08, size * 0.07, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.15, y - size * 0.08, size * 0.07, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#0e7490';
    ctx.beginPath();
    ctx.arc(x - size * 0.13, y - size * 0.06, size * 0.04, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.17, y - size * 0.06, size * 0.04, 0, Math.PI * 2);
    ctx.fill();
}
function drawColossusSprite(ctx, x, y, size, colors) {
    // Massive body
    const grad = ctx.createRadialGradient(x, y + size * 0.1, 0, x, y + size * 0.1, size * 0.8);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    // Body (large oval)
    ctx.beginPath();
    ctx.ellipse(x, y + size * 0.2, size * 0.7, size * 0.65, 0, 0, Math.PI * 2);
    ctx.fill();
    // Arms (thick)
    ctx.fillStyle = colors[0];
    roundRect(ctx, x - size * 0.9, y - size * 0.1, size * 0.25, size * 0.7, 6);
    ctx.fill();
    roundRect(ctx, x + size * 0.65, y - size * 0.1, size * 0.25, size * 0.7, 6);
    ctx.fill();
    // Flower crown
    const flowerColors = ['#f472b6', '#c084fc', '#fb923c'];
    for (let i = 0; i < 5; i++) {
        const angle = -Math.PI / 2 + (i - 2) * 0.4;
        const fx = x + Math.cos(angle) * size * 0.45;
        const fy = y - size * 0.4 + Math.sin(angle) * size * 0.15;
        ctx.fillStyle = flowerColors[i % 3];
        for (let p = 0; p < 5; p++) {
            const pa = (Math.PI * 2 / 5) * p;
            ctx.beginPath();
            ctx.ellipse(fx + Math.cos(pa) * size * 0.06, fy + Math.sin(pa) * size * 0.06, size * 0.04, size * 0.04, pa, 0, Math.PI * 2);
            ctx.fill();
        }
        ctx.fillStyle = '#facc15';
        ctx.beginPath();
        ctx.arc(fx, fy, size * 0.04, 0, Math.PI * 2);
        ctx.fill();
    }
    // Eyes (calm green)
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x - size * 0.2, y + size * 0.0, size * 0.08, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.2, y + size * 0.0, size * 0.08, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#15803d';
    ctx.beginPath();
    ctx.arc(x - size * 0.18, y + size * 0.02, size * 0.04, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.22, y + size * 0.02, size * 0.04, 0, Math.PI * 2);
    ctx.fill();
}
function drawDragonSprite(ctx, x, y, size, colors) {
    // Body
    const grad = ctx.createRadialGradient(x, y + size * 0.1, 0, x, y + size * 0.1, size * 0.6);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.ellipse(x, y + size * 0.15, size * 0.45, size * 0.55, 0, 0, Math.PI * 2);
    ctx.fill();
    // Wings (electric storm)
    const wingGrad = ctx.createLinearGradient(x - size * 0.8, y - size * 0.8, x + size * 0.8, y);
    wingGrad.addColorStop(0, 'rgba(239, 68, 68, 0.6)');
    wingGrad.addColorStop(1, 'rgba(245, 158, 11, 0.3)');
    ctx.fillStyle = wingGrad;
    // Left wing
    ctx.beginPath();
    ctx.moveTo(x - size * 0.3, y);
    ctx.quadraticCurveTo(x - size * 0.9, y - size * 0.8, x - size * 0.6, y - size * 1.0);
    ctx.quadraticCurveTo(x - size * 0.3, y - size * 0.6, x - size * 0.15, y - size * 0.2);
    ctx.closePath();
    ctx.fill();
    // Right wing
    ctx.beginPath();
    ctx.moveTo(x + size * 0.3, y);
    ctx.quadraticCurveTo(x + size * 0.9, y - size * 0.8, x + size * 0.6, y - size * 1.0);
    ctx.quadraticCurveTo(x + size * 0.3, y - size * 0.6, x + size * 0.15, y - size * 0.2);
    ctx.closePath();
    ctx.fill();
    // Head
    ctx.fillStyle = colors[1];
    ctx.beginPath();
    ctx.arc(x, y - size * 0.45, size * 0.32, 0, Math.PI * 2);
    ctx.fill();
    // Horns
    ctx.fillStyle = '#ef4444';
    ctx.beginPath();
    ctx.moveTo(x - size * 0.2, y - size * 0.65);
    ctx.lineTo(x - size * 0.3, y - size * 0.95);
    ctx.lineTo(x - size * 0.05, y - size * 0.65);
    ctx.closePath();
    ctx.fill();
    ctx.beginPath();
    ctx.moveTo(x + size * 0.2, y - size * 0.65);
    ctx.lineTo(x + size * 0.3, y - size * 0.95);
    ctx.lineTo(x + size * 0.05, y - size * 0.65);
    ctx.closePath();
    ctx.fill();
    // Eyes (fierce yellow)
    ctx.fillStyle = '#facc15';
    ctx.shadowColor = '#ef4444';
    ctx.shadowBlur = 12;
    ctx.beginPath();
    ctx.ellipse(x - size * 0.12, y - size * 0.48, size * 0.07, size * 0.1, -0.2, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.ellipse(x + size * 0.12, y - size * 0.48, size * 0.07, size * 0.1, 0.2, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;
    // Tail
    ctx.strokeStyle = colors[0];
    ctx.lineWidth = 5;
    ctx.beginPath();
    ctx.moveTo(x + size * 0.3, y + size * 0.5);
    ctx.quadraticCurveTo(x + size * 0.8, y + size * 0.6, x + size * 0.7, y + size * 0.9);
    ctx.stroke();
}
function drawAngelSprite(ctx, x, y, size, colors) {
    // Ethereal body
    const grad = ctx.createRadialGradient(x, y, 0, x, y, size * 0.7);
    grad.addColorStop(0, 'rgba(196, 181, 253, 0.9)');
    grad.addColorStop(0.6, colors[0]);
    grad.addColorStop(1, 'rgba(139, 92, 246, 0.3)');
    ctx.fillStyle = grad;
    // Flowing body (teardrop shape)
    ctx.beginPath();
    ctx.moveTo(x, y - size * 0.7);
    ctx.quadraticCurveTo(x + size * 0.5, y - size * 0.2, x + size * 0.4, y + size * 0.3);
    ctx.quadraticCurveTo(x + size * 0.2, y + size * 0.7, x, y + size * 0.6);
    ctx.quadraticCurveTo(x - size * 0.2, y + size * 0.7, x - size * 0.4, y + size * 0.3);
    ctx.quadraticCurveTo(x - size * 0.5, y - size * 0.2, x, y - size * 0.7);
    ctx.closePath();
    ctx.fill();
    // Halo (wind ring)
    ctx.strokeStyle = colors[1];
    ctx.lineWidth = 3;
    ctx.shadowColor = colors[1];
    ctx.shadowBlur = 15;
    const haloY = y - size * 0.8;
    ctx.beginPath();
    ctx.ellipse(x, haloY, size * 0.4, size * 0.12, 0, 0, Math.PI * 2);
    ctx.stroke();
    ctx.shadowBlur = 0;
    // Wind wisps around body
    const time = Date.now() / 300;
    ctx.strokeStyle = 'rgba(167, 139, 250, 0.4)';
    ctx.lineWidth = 2;
    for (let i = 0; i < 4; i++) {
        const wx = x + Math.sin(time + i * 1.5) * size * 0.5;
        const wy = y + Math.cos(time + i * 2) * size * 0.3;
        ctx.beginPath();
        ctx.moveTo(wx - size * 0.1, wy);
        ctx.quadraticCurveTo(wx, wy - size * 0.1, wx + size * 0.1, wy);
        ctx.stroke();
    }
    // Eyes (serene purple)
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x - size * 0.12, y - size * 0.15, size * 0.07, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.12, y - size * 0.15, size * 0.07, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#7c3aed';
    ctx.beginPath();
    ctx.arc(x - size * 0.1, y - size * 0.13, size * 0.04, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.14, y - size * 0.13, size * 0.04, 0, Math.PI * 2);
    ctx.fill();
}
function drawTitanSprite(ctx, x, y, size, colors) {
    // Colossal body
    const grad = ctx.createLinearGradient(x - size * 0.6, y - size * 0.5, x + size * 0.6, y + size * 0.8);
    grad.addColorStop(0, colors[1]);
    grad.addColorStop(1, colors[0]);
    ctx.fillStyle = grad;
    // Body (massive rectangular)
    roundRect(ctx, x - size * 0.55, y - size * 0.3, size * 1.1, size * 1.1, 12);
    ctx.fill();
    // Mountain core on chest (glowing)
    const coreGrad = ctx.createRadialGradient(x, y + size * 0.05, 0, x, y + size * 0.05, size * 0.25);
    coreGrad.addColorStop(0, '#f97316');
    coreGrad.addColorStop(0.5, '#ef4444');
    coreGrad.addColorStop(1, 'rgba(239, 68, 68, 0)');
    ctx.fillStyle = coreGrad;
    ctx.shadowColor = '#f97316';
    ctx.shadowBlur = 25;
    ctx.beginPath();
    ctx.arc(x, y + size * 0.05, size * 0.2, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;
    // Arms (thick pillars)
    ctx.fillStyle = colors[0];
    roundRect(ctx, x - size * 0.85, y - size * 0.1, size * 0.25, size * 0.8, 6);
    ctx.fill();
    roundRect(ctx, x + size * 0.6, y - size * 0.1, size * 0.25, size * 0.8, 6);
    ctx.fill();
    // Head (small relative to body)
    ctx.fillStyle = colors[1];
    roundRect(ctx, x - size * 0.2, y - size * 0.75, size * 0.4, size * 0.35, 6);
    ctx.fill();
    // Eyes (molten)
    ctx.fillStyle = '#f97316';
    ctx.shadowColor = '#ef4444';
    ctx.shadowBlur = 15;
    ctx.beginPath();
    ctx.arc(x - size * 0.1, y - size * 0.55, size * 0.05, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x + size * 0.1, y - size * 0.55, size * 0.05, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;
    // Crack lines
    ctx.strokeStyle = 'rgba(234, 88, 12, 0.5)';
    ctx.lineWidth = 1.5;
    for (let i = 0; i < 6; i++) {
        const sx = x - size * 0.4 + Math.random() * size * 0.8;
        const sy = y - size * 0.2 + Math.random() * size * 0.8;
        ctx.beginPath();
        ctx.moveTo(sx, sy);
        ctx.lineTo(sx + (Math.random() - 0.5) * size * 0.2, sy + Math.random() * size * 0.2);
        ctx.stroke();
    }
}
function roundRect(ctx, x, y, w, h, r) {
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y);
    ctx.quadraticCurveTo(x + w, y, x + w, y + r);
    ctx.lineTo(x + w, y + h - r);
    ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
    ctx.lineTo(x + r, y + h);
    ctx.quadraticCurveTo(x, y + h, x, y + h - r);
    ctx.lineTo(x, y + r);
    ctx.quadraticCurveTo(x, y, x + r, y);
    ctx.closePath();
}
// === SPRITE GENERATOR (for dex and party display) ===
function generateMiniSprite(template, level) {
    const colors = template.spriteParams.colors;
    const size = 36 + Math.min(level, 20);
    return `<svg width="${size}" height="${size}" viewBox="0 0 100 100" class="battle-sprite float">
    <defs>
      <radialGradient id="grad_${template.id}" cx="40%" cy="35%">
        <stop offset="0%" stop-color="${colors[1]}"/>
        <stop offset="100%" stop-color="${colors[0]}"/>
      </radialGradient>
    </defs>
    <circle cx="50" cy="55" r="35" fill="url(#grad_${template.id})" filter="drop-shadow(0 2px 6px rgba(0,0,0,0.3))"/>
    <circle cx="40" cy="48" r="6" fill="#fff"/>
    <circle cx="60" cy="48" r="6" fill="#fff"/>
    <circle cx="41" cy="49" r="3" fill="#1e1b4b"/>
    <circle cx="61" cy="49" r="3" fill="#1e1b4b"/>
    <path d="M 42 65 Q 50 72 58 65" stroke="#fff" stroke-width="2.5" fill="none" opacity="0.7"/>
  </svg>`;
}
// === DEX RENDERING ===
function renderDex(unlockedIds) {
    const grid = document.getElementById('dex-grid');
    if (!grid)
        return;
    grid.innerHTML = '';
    resonants_1.RESONANT_DB.forEach(template => {
        const unlocked = unlockedIds.includes(template.id);
        const card = document.createElement('div');
        card.className = `dex-card ${unlocked ? '' : 'locked'}`;
        if (unlocked) {
            const stats = calcStatsForDex(template, 1);
            card.innerHTML = `
        <div class="w-full flex justify-center">${generateMiniSprite(template, 1)}</div>
        <span class="text-white font-bold text-xs truncate w-full text-center">${template.name}</span>
        <span class="type-${template.element} text-[10px] uppercase tracking-wider font-semibold">${template.element}</span>
        <div class="w-full space-y-0.5 mt-1">
          <div class="flex justify-between text-[9px] text-slate-400"><span>HP</span><span>${stats.hp}</span></div>
          <div class="flex justify-between text-[9px] text-slate-400"><span>ATK</span><span>${stats.attack}</span></div>
          <div class="flex justify-between text-[9px] text-slate-400"><span>DEF</span><span>${stats.defense}</span></div>
          <div class="flex justify-between text-[9px] text-slate-400"><span>SPD</span><span>${stats.speed}</span></div>
        </div>`;
        }
        else {
            card.innerHTML = `
        <div class="w-full flex justify-center opacity-30">
          <svg width="48" height="48" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#334155"/><text x="50" y="55" text-anchor="middle" fill="#64748b" font-size="20">?</text></svg>
        </div>
        <span class="text-slate-500 font-bold text-xs truncate w-full text-center">???</span>
        <span class="text-slate-600 text-[10px] uppercase tracking-wider">Unknown</span>`;
        }
        grid.appendChild(card);
    });
}
function calcStatsForDex(template, level) {
    const hp = Math.floor(((2 * template.baseHp + 10) * level) / 100) + level + 10;
    const atk = Math.floor(((2 * template.baseAttack + 5) * level) / 100) + 5;
    const def = Math.floor(((2 * template.baseDefense + 5) * level) / 100) + 5;
    const spd = Math.floor(((2 * template.baseSpeed + 5) * level) / 100) + 5;
    return { hp, attack: atk, defense: def, speed: spd };
}
// === SHOP RENDERING ===
function renderShop(shards) {
    const grid = document.getElementById('shop-grid');
    if (!grid)
        return;
    document.getElementById('shop-shards').textContent = `${shards} Shards`;
    grid.innerHTML = '';
    types_1.SHOP_ITEMS.forEach(item => {
        const div = document.createElement('div');
        div.className = 'shop-item';
        const canAfford = shards >= item.cost;
        div.innerHTML = `
      <div class="flex items-center gap-3">
        <span class="text-2xl">${item.icon}</span>
        <div class="flex-1">
          <h3 class="text-white font-bold text-sm">${item.name}</h3>
          <p class="text-slate-400 text-xs">${item.description}</p>
        </div>
      </div>
      <button data-shop-item="${item.id}" class="w-full py-2 rounded-xl text-sm font-semibold transition-all ${canAfford ? 'bg-gradient-to-r from-amber-600 to-yellow-600 hover:from-amber-500 hover:to-yellow-500 text-white' : 'bg-slate-700 text-slate-500 cursor-not-allowed'}">
        ${item.cost} Shards
      </button>`;
        grid.appendChild(div);
    });
}
// === OVERLAYS & DIALOGS ===
function showEncounter(text, onContinue) {
    const overlay = document.getElementById('encounter-overlay');
    if (!overlay)
        return;
    document.getElementById('encounter-text').textContent = text;
    overlay.classList.remove('hidden');
    const btn = document.getElementById('encounter-btn');
    if (btn) {
        btn.onclick = () => {
            overlay.classList.add('hidden');
            onContinue();
        };
    }
}
function showLevelUp(text, onContinue) {
    const overlay = document.getElementById('levelup-overlay');
    if (!overlay)
        return;
    document.getElementById('levelup-text').textContent = text;
    overlay.classList.remove('hidden');
    const btn = document.getElementById('levelup-btn');
    if (btn) {
        btn.onclick = () => {
            overlay.classList.add('hidden');
            onContinue();
        };
    }
}
function showEvolution(spriteHtml, text) {
    return new Promise(resolve => {
        const overlay = document.getElementById('evolution-overlay');
        if (!overlay) {
            resolve();
            return;
        }
        document.getElementById('evolution-sprite-container').innerHTML = spriteHtml;
        document.getElementById('evolution-text').textContent = text;
        overlay.classList.remove('hidden');
        const btn = document.getElementById('evolution-btn');
        if (btn) {
            btn.onclick = () => {
                overlay.classList.add('hidden');
                resolve();
            };
        }
    });
}
function showCaptureAnimation(success) {
    return new Promise(resolve => {
        const overlay = document.getElementById('capture-overlay');
        if (!overlay) {
            resolve();
            return;
        }
        const prismEl = document.getElementById('capture-prism');
        const textEl = document.getElementById('capture-text');
        if (success) {
            prismEl.innerHTML = `<svg width="128" height="128" viewBox="0 0 128 128">
        <defs><linearGradient id="cg" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stop-color="#06b6d4"/><stop offset="100%" stop-color="#8b5cf6"/>
        </linearGradient></defs>
        <polygon points="64,8 120,40 120,88 64,120 8,88 8,40" fill="none" stroke="url(#cg)" stroke-width="3">
          <animate attributeName="opacity" values="1;0.5;1" dur="1s" repeatCount="indefinite"/>
        </polygon>
        <polygon points="64,24 100,48 100,80 64,104 28,80 28,48" fill="url(#cg)" opacity="0.3">
          <animate attributeName="opacity" values="0.3;0.8;0.3" dur="1.5s" repeatCount="indefinite"/>
        </polygon>
      </svg>`;
            textEl.textContent = 'Capture Successful!';
            prismEl.className = 'w-32 h-32 mx-auto mb-6 prism-spin';
        }
        else {
            prismEl.innerHTML = `<svg width="128" height="128" viewBox="0 0 128 128">
        <polygon points="64,8 120,40 120,88 64,120 8,88 8,40" fill="none" stroke="#ef4444" stroke-width="3" opacity="0.5"/>
        <text x="64" y="72" text-anchor="middle" fill="#ef4444" font-size="40">✕</text>
      </svg>`;
            textEl.textContent = 'It broke free!';
        }
        overlay.classList.remove('hidden');
        setTimeout(() => {
            overlay.classList.add('hidden');
            resolve();
        }, 2500);
    });
}
function showDialog(speaker, text, avatar = '💬') {
    return new Promise(resolve => {
        const overlay = document.getElementById('dialog-overlay');
        if (!overlay) {
            resolve();
            return;
        }
        document.getElementById('dialog-speaker').textContent = speaker;
        document.getElementById('dialog-text').textContent = text;
        document.getElementById('dialog-avatar').textContent = avatar;
        overlay.classList.remove('hidden');
        overlay.style.animation = 'none';
        void overlay.offsetWidth;
        overlay.style.animation = '';
        const btn = document.getElementById('dialog-next');
        if (btn) {
            btn.onclick = () => {
                overlay.classList.add('hidden');
                resolve();
            };
        }
    });
}
// === TOAST NOTIFICATIONS ===
function showToast(message, type = 'info') {
    const container = document.getElementById('toast-container');
    if (!container)
        return;
    const toast = document.createElement('div');
    toast.className = `toast`;
    const colors = { success: 'border-emerald-500/30 text-emerald-400', error: 'border-red-500/30 text-red-400', info: 'border-cyan-500/30 text-cyan-400' };
    toast.style.borderLeft = `3px solid ${type === 'success' ? '#22c55e' : type === 'error' ? '#ef4444' : '#06b6d4'}`;
    toast.textContent = message;
    container.appendChild(toast);
    setTimeout(() => {
        toast.style.opacity = '0';
        toast.style.transform = 'translateX(100%)';
        toast.style.transition = 'all 0.3s ease';
        setTimeout(() => toast.remove(), 300);
    }, 3000);
}
// === BATTLE PARTICLES ===
function spawnBattleParticle(x, y, color, count = 8) {
    const container = document.getElementById('battle-particles');
    if (!container)
        return;
    for (let i = 0; i < count; i++) {
        const particle = document.createElement('div');
        particle.className = 'particle';
        const size = 3 + Math.random() * 5;
        const drift = (Math.random() - 0.5) * 100;
        particle.style.cssText = `
      width: ${size}px; height: ${size}px;
      background: ${color};
      left: ${x}px; top: ${y}px;
      --drift: ${drift}px;
      box-shadow: 0 0 6px ${color};
    `;
        container.appendChild(particle);
        setTimeout(() => particle.remove(), 1500);
    }
}
// === SCREEN SHAKE ===
function screenShake(duration = 400) {
    const app = document.getElementById('app');
    if (!app)
        return;
    app.classList.add('shake');
    setTimeout(() => app.classList.remove('shake'), duration);
}
// === FLASH EFFECT ===
function flashScreen(color = '#fff', duration = 300) {
    const flash = document.createElement('div');
    flash.style.cssText = `
    position: fixed; inset: 0; background: ${color}; 
    opacity: 0.3; pointer-events: none; z-index: 100;
    transition: opacity ${duration}ms ease-out;
  `;
    document.body.appendChild(flash);
    requestAnimationFrame(() => {
        flash.style.opacity = '0';
        setTimeout(() => flash.remove(), duration);
    });
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>