← Aethelgard results

Aethelgard

Qwen 3.6 27b MTP · composer

Initial view of Aethelgard

Reviews & generation details

Reviews

Human review · Jul 16, 2026 · rubric 1

Historical path-based review. Association with these exact bytes is unverified.

Visual design
3 / 5
UX / feel
3 / 5
Works
4 / 5
Prompt spirit
3 / 5
Delight
2 / 5
boring design with basic fonts; decent creature images; good animations; no sound

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 85,269 bytes · SHA-256 f6b170d14123
<!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>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;family=Press+Start+2P&amp;display=swap">
<style>
/* ── css: base ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; }
body {
  font-family: 'Inter', sans-serif;
  background: linear-gradient(135deg, #0f0a1e 0%, #1a1040 30%, #0d1b2a 70%, #0a0618 100%);
  color: #e2e8f0;
}
#game-container { width: 100%; height: 100%; position: relative; overflow: hidden; }

.screen {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: none; flex-direction: column; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 0.4s ease, transform 0.4s ease;
}
.screen.active { display: flex; opacity: 1; }

.glass-card {
  background: rgba(255,255,255,0.06);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 16px; padding: 24px;
}

/* Element glow colors */
.pyro-glow { box-shadow: 0 0 20px rgba(255,87,34,0.4), inset 0 0 20px rgba(255,87,34,0.1); border-color: rgba(255,87,34,0.5) !important; }
.hydro-glow { box-shadow: 0 0 20px rgba(33,150,243,0.4), inset 0 0 20px rgba(33,150,243,0.1); border-color: rgba(33,150,243,0.5) !important; }
.flora-glow { box-shadow: 0 0 20px rgba(76,175,80,0.4), inset 0 0 20px rgba(76,175,80,0.1); border-color: rgba(76,175,80,0.5) !important; }
.volt-glow { box-shadow: 0 0 20px rgba(255,235,59,0.4), inset 0 0 20px rgba(255,235,59,0.1); border-color: rgba(255,235,59,0.5) !important; }
.zephyr-glow { box-shadow: 0 0 20px rgba(0,230,118,0.4), inset 0 0 20px rgba(0,230,118,0.1); border-color: rgba(0,230,118,0.5) !important; }
.earth-glow { box-shadow: 0 0 20px rgba(141,110,99,0.4), inset 0 0 20px rgba(141,110,99,0.1); border-color: rgba(141,110,99,0.5) !important; }
.cosmos-glow { box-shadow: 0 0 20px rgba(167,139,250,0.4), inset 0 0 20px rgba(167,139,250,0.1); border-color: rgba(167,139,250,0.5) !important; }

.pyro-text { color: #ff5722; }
.hydro-text { color: #2196f3; }
.flora-text { color: #4caf50; }
.volt-text { color: #ebd841; }
.zephyr-text { color: #00e676; }
.earth-text { color: #a1887f; }
.cosmos-text { color: #a78bfa; }

.hidden { display: none !important; }
/* ── css: menu ── */
.menu-content { text-align: center; }
.game-title {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(1.5rem, 4vw, 3rem);
  background: linear-gradient(90deg, #a78bfa, #60a5fa, #f472b6, #a78bfa);
  background-size: 200% auto;
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  animation: shimmer 3s linear infinite;
  margin-bottom: 8px; letter-spacing: 4px;
}
.game-subtitle {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(0.5rem, 1.5vw, 0.9rem);
  color: #94a3b8; margin-bottom: 40px; letter-spacing: 2px;
}
.prism-logo {
  width: 80px; height: 80px; margin: 0 auto 40px;
  background: linear-gradient(135deg, #a78bfa, #60a5fa);
  clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
  animation: float 3s ease-in-out infinite;
}
.menu-btn {
  display: block; width: 260px; margin: 10px auto; padding: 14px 24px;
  border: 1px solid rgba(255,255,255,0.15); border-radius: 12px;
  font-family: 'Inter', sans-serif; font-size: 1rem; font-weight: 600;
  cursor: pointer; transition: all 0.2s ease; letter-spacing: 1px;
}
.menu-btn.primary { background: linear-gradient(135deg, #7c3aed, #4f46e5); color: white; }
.menu-btn.secondary { background: rgba(255,255,255,0.08); color: #cbd5e1; border-color: rgba(255,255,255,0.2); }
.menu-btn.danger { background: rgba(239,68,68,0.15); color: #fca5a5; border-color: rgba(239,68,68,0.3); }
.menu-btn.small { width: auto; padding: 8px 24px; font-size: 0.85rem; margin-top: 12px; }
.menu-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 20px rgba(124,58,237,0.3); }
.menu-btn:active { transform: scale(0.97); }

@keyframes shimmer { to { background-position: 200% center; } }
@keyframes float { 0%,100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-10px) rotate(3deg); } }
/* ── css: map ── */
.map-header {
  width: 100%; padding: 12px 24px; display: flex; justify-content: space-between; align-items: center;
  background: rgba(0,0,0,0.3); backdrop-filter: blur(10px); border-bottom: 1px solid rgba(255,255,255,0.08);
}
.map-header h2 { font-family: 'Press Start 2P', monospace; font-size: clamp(0.6rem, 1.5vw, 0.9rem); color: #a78bfa; }
.header-stats { display: flex; gap: 8px; align-items: center; }
.icon-btn {
  background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1);
  color: #cbd5e1; padding: 6px 12px; border-radius: 8px; cursor: pointer; font-size: 0.8rem;
  transition: all 0.2s ease;
}
.icon-btn:hover { background: rgba(255,255,255,0.12); transform: translateY(-1px); }

.map-area {
  flex: 1; width: 100%; overflow-y: auto; padding: 24px;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.zone-path {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; padding: 20px;
  position: relative;
}
.map-node {
  width: 72px; height: 72px; border-radius: 50%; cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 1.8rem;
  transition: all 0.3s ease; position: relative;
  background: rgba(255,255,255,0.06); border: 2px solid rgba(255,255,255,0.15);
}
.map-node:hover:not(.locked) { transform: scale(1.15); box-shadow: 0 0 24px rgba(124,58,237,0.4); }
.map-node.locked { opacity: 0.3; cursor: not-allowed; filter: grayscale(1); }
.map-node.completed { border-color: #4ade80; background: rgba(74,222,128,0.15); }
.map-node.current { animation: nodePulse 2s ease-in-out infinite; border-color: #a78bfa; }
.map-node .node-label {
  position: absolute; bottom: -22px; font-size: 0.6rem; white-space: nowrap; color: #94a3b8;
}

@keyframes nodePulse {
  0%,100% { box-shadow: 0 0 10px rgba(167,139,250,0.3); }
  50% { box-shadow: 0 0 25px rgba(167,139,250,0.6); }
}

.dialog-overlay {
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
  background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 100;
}
.dialog-box {
  background: rgba(20,15,40,0.95); backdrop-filter: blur(20px);
  border: 1px solid rgba(255,255,255,0.15); border-radius: 16px; padding: 32px;
  max-width: 400px; text-align: center;
}
.dialog-box p { margin-bottom: 16px; font-size: 0.95rem; line-height: 1.6; color: #cbd5e1; }

/* Zone selector */
.zone-selector { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; justify-content: center; }
.zone-tab {
  padding: 8px 16px; border-radius: 20px; cursor: pointer; font-size: 0.75rem;
  background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); color: #94a3b8;
  transition: all 0.2s ease; font-weight: 600;
}
.zone-tab.active { background: rgba(124,58,237,0.2); border-color: #7c3aed; color: #c4b5fd; }
.zone-tab.locked { opacity: 0.3; cursor: not-allowed; }
/* ── css: battle ── */
#screen-battle { justify-content: flex-start; }
.battle-scene {
  width: 100%; flex: 1; display: flex; padding: 20px; gap: 40px;
  align-items: center; justify-content: center; position: relative; overflow: hidden;
}
.enemy-area, .player-area {
  display: flex; flex-direction: column; align-items: center; gap: 16px; width: 280px;
}
.resonant-card {
  background: rgba(255,255,255,0.06); backdrop-filter: blur(12px);
  border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; padding: 14px; width: 100%;
}
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.card-header span:first-child { font-weight: 600; font-size: 0.9rem; }
.level-badge { background: rgba(124,58,237,0.3); padding: 2px 8px; border-radius: 8px; font-size: 0.7rem; color: #c4b5fd; }

.hp-bar-container { width: 100%; height: 10px; background: rgba(0,0,0,0.4); border-radius: 5px; overflow: hidden; margin-bottom: 4px; }
.hp-bar { height: 100%; border-radius: 5px; transition: width 0.5s ease, background-color 0.3s ease; }
#enemy-hp-text, #player-hp-text { font-size: 0.7rem; color: #94a3b8; text-align: right; display: block; }

.status-effects { display: flex; gap: 4px; margin-top: 6px; }
.status-badge { font-size: 0.65rem; padding: 2px 6px; border-radius: 4px; background: rgba(239,68,68,0.2); color: #fca5a5; }

.resonant-sprite {
  width: 120px; height: 120px; display: flex; align-items: center; justify-content: center;
}
.enemy-sprite { animation: enemyIdle 3s ease-in-out infinite; }
.player-sprite { animation: playerIdle 3s ease-in-out infinite; transform: scaleX(-1); }

@keyframes enemyIdle { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
@keyframes playerIdle { 0%,100% { transform: scaleX(-1) translateY(0); } 50% { transform: scaleX(-1) translateY(-6px); } }

.battle-ui { width: 100%; padding: 12px 24px; background: rgba(0,0,0,0.3); backdrop-filter: blur(10px); border-top: 1px solid rgba(255,255,255,0.08); }
.battle-log { height: 60px; overflow-y: auto; margin-bottom: 12px; padding: 8px 12px; background: rgba(0,0,0,0.3); border-radius: 8px; font-size: 0.8rem; color: #94a3b8; line-height: 1.5; }
.battle-log p { margin-bottom: 2px; }

.battle-actions { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.action-btn {
  padding: 12px 8px; border-radius: 10px; cursor: pointer; font-size: 0.8rem; font-weight: 600;
  transition: all 0.2s ease; text-align: center; border: 1px solid rgba(255,255,255,0.1);
}
.action-btn.primary { background: linear-gradient(135deg, #dc2626, #b91c1c); color: white; }
.action-btn.secondary { background: linear-gradient(135deg, #7c3aed, #6d28d9); color: white; }
.action-btn.tertiary { background: rgba(255,255,255,0.08); color: #cbd5e1; }
.action-btn.quaternary { background: linear-gradient(135deg, #059669, #047857); color: white; }
.action-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.3); }

.move-select, .item-select, .exchange-panel { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-top: 8px; }
.move-btn, .prism-btn, .inv-item-btn {
  padding: 10px; border-radius: 8px; cursor: pointer; font-size: 0.75rem; text-align: left;
  background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); color: #cbd5e1;
  transition: all 0.2s ease;
}
.move-btn:hover, .prism-btn:hover, .inv-item-btn:hover { background: rgba(255,255,255,0.12); }
.move-btn .move-type { font-size: 0.6rem; opacity: 0.7; display: block; margin-top: 2px; }

.exchange-panel { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.exchange-slot {
  padding: 12px; border-radius: 8px; cursor: pointer; background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.1); text-align: center; transition: all 0.2s ease;
}
.exchange-slot:hover { background: rgba(124,58,237,0.2); border-color: #7c3aed; }
.exchange-slot.fainted { opacity: 0.4; cursor: not-allowed; }

/* Battle animations */
@keyframes shakeScreen { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-8px)} 75%{transform:translateX(8px)} }
.shake { animation: shakeScreen 0.3s ease-in-out; }

@keyframes flashWhite { 0%{filter:brightness(1)} 50%{filter:brightness(2.5)} 100%{filter:brightness(1)} }
.flash-hit { animation: flashWhite 0.2s ease; }

@keyframes attackLunge { 0%{transform:translateX(0)} 40%{transform:translateX(60px) scaleX(-1)} 100%{transform:translateX(0) scaleX(-1)} }
.attack-anim { animation: attackLunge 0.4s ease; }

@keyframes captureShake { 0%,100%{transform:rotate(0)} 25%{transform:rotate(-15deg)} 75%{transform:rotate(15deg)} }
.capture-shake { animation: captureShake 0.3s ease-in-out; }

/* Overlays */
.overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 200; }
.levelup-box, .evolution-box, .victory-box, .defeat-box { text-align: center; padding: 32px; background: rgba(20,15,40,0.95); backdrop-filter: blur(20px); border: 1px solid rgba(255,255,255,0.15); border-radius: 16px; max-width: 360px; }
.levelup-box h3, .evolution-box h3 { font-family: 'Press Start 2P', monospace; font-size: 0.9rem; color: #fbbf24; margin-bottom: 12px; }
.victory-box h3 { font-family: 'Press Start 2P', monospace; font-size: 1rem; color: #4ade80; margin-bottom: 12px; }
.defeat-box h3 { font-family: 'Press Start 2P', monospace; font-size: 0.9rem; color: #f87171; margin-bottom: 12px; }

.capture-animation { width: 150px; height: 150px; position: relative; }
.capture-prism {
  width: 60px; height: 60px; background: linear-gradient(135deg, #a78bfa, #60a5fa);
  clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
  position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
}

/* Shop */
.shop-container, .dex-container, .party-container { width: 90%; max-width: 700px; text-align: center; }
.shop-container h2, .dex-container h2, .party-container h2 { font-family: 'Press Start 2P', monospace; font-size: 1rem; color: #a78bfa; margin-bottom: 16px; }
.shard-display { color: #fbbf24; margin-bottom: 16px; font-weight: 600; }
.shop-grid, .dex-grid, .party-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-bottom: 20px; max-height: 50vh; overflow-y: auto; padding: 4px; }
.shop-item, .dex-entry, .party-slot {
  background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; padding: 14px; text-align: left;
}
.shop-item h4 { font-size: 0.85rem; margin-bottom: 4px; }
.shop-item p { font-size: 0.7rem; color: #94a3b8; margin-bottom: 8px; }
.buy-btn { padding: 6px 12px; border-radius: 6px; background: rgba(124,58,237,0.3); border: 1px solid #7c3aed; color: #c4b5fd; cursor: pointer; font-size: 0.7rem; }
.buy-btn:hover { background: rgba(124,58,237,0.5); }

.dex-entry { text-align: center; padding: 16px; }
.dex-entry .dex-icon { width: 60px; height: 60px; margin: 0 auto 8px; }
.dex-entry h4 { font-size: 0.8rem; margin-bottom: 2px; }
.dex-entry p { font-size: 0.65rem; color: #94a3b8; }
.dex-undiscovered { opacity: 0.3; filter: blur(1px); }

.party-slot { text-align: center; padding: 12px; cursor: default; }
.party-slot .party-icon { width: 50px; height: 50px; margin: 0 auto 6px; }
.party-slot h4 { font-size: 0.8rem; }
.party-slot p { font-size: 0.65rem; color: #94a3b8; }

.gameover-content { text-align: center; }
.gameover-content h1 { font-family: 'Press Start 2P', monospace; font-size: 1.5rem; margin-bottom: 16px; }
#gameover-title.win { color: #4ade80; }
#gameover-title.lose { color: #f87171; }

/* Particles */
.particle { position: absolute; pointer-events: none; border-radius: 50%; animation: particleFade 1s ease-out forwards; }
@keyframes particleFade { 0%{opacity:1;transform:translateY(0) scale(1)} 100%{opacity:0;transform:translateY(-40px) scale(0.3)} }

/* Responsive */
@media (max-width: 600px) {
  .battle-scene { flex-direction: column; gap: 12px; padding: 8px; }
  .enemy-area, .player-area { width: 90%; }
  .resonant-sprite { width: 80px; height: 80px; }
  .battle-actions { grid-template-columns: repeat(2, 1fr); }
}
</style>
</head>
<body>
<div id="game-container">
  <!-- MAIN MENU -->
  <div id="screen-menu" class="screen active">
    <div class="menu-content">
      <h1 class="game-title">AETHELGARD</h1>
      <p class="game-subtitle">Resonant Chronicles</p>
      <div class="prism-logo"></div>
      <button id="btn-new-game" class="menu-btn primary">New Game</button>
      <button id="btn-continue" class="menu-btn secondary hidden">Continue</button>
      <button id="btn-reset" class="menu-btn danger hidden">Reset Progress</button>
    </div>
  </div>

  <!-- OVERWORLD MAP -->
  <div id="screen-map" class="screen">
    <div class="map-header">
      <h2 id="zone-title">The Verdant Canopy</h2>
      <div class="header-stats">
        <span id="shard-count">💎 0</span>
        <button id="btn-shop" class="icon-btn">🏪 Shop</button>
        <button id="btn-dex" class="icon-btn">📖 Dex</button>
        <button id="btn-party" class="icon-btn">👥 Party</button>
        <button id="btn-menu-back" class="icon-btn">☰ Menu</button>
      </div>
    </div>
    <div id="map-area" class="map-area"></div>
    <div id="map-dialog" class="dialog-overlay hidden">
      <div class="dialog-box">
        <p id="dialog-text"></p>
        <button id="dialog-ok" class="menu-btn primary small">OK</button>
      </div>
    </div>
  </div>

  <!-- BATTLE SCREEN -->
  <div id="screen-battle" class="screen">
    <div class="battle-scene">
      <div class="enemy-area">
        <div class="resonant-card enemy-card">
          <div class="card-header">
            <span id="enemy-name">Wild Emberkit</span>
            <span id="enemy-level" class="level-badge">Lv 3</span>
          </div>
          <div class="hp-bar-container">
            <div id="enemy-hp-bar" class="hp-bar"></div>
          </div>
          <span id="enemy-hp-text">20/20 HP</span>
        </div>
        <div id="enemy-sprite" class="resonant-sprite enemy-sprite"></div>
      </div>
      <div class="player-area">
        <div id="player-sprite" class="resonant-sprite player-sprite"></div>
        <div class="resonant-card player-card">
          <div class="card-header">
            <span id="player-name">Emberkit</span>
            <span id="player-level" class="level-badge">Lv 3</span>
          </div>
          <div class="hp-bar-container">
            <div id="player-hp-bar" class="hp-bar"></div>
          </div>
          <span id="player-hp-text">20/20 HP</span>
          <div id="player-status-effects" class="status-effects"></div>
        </div>
      </div>
    </div>
    <div class="battle-ui">
      <div id="battle-log" class="battle-log"></div>
      <div id="battle-actions" class="battle-actions">
        <button id="btn-attack" class="action-btn primary">⚔️ Attack</button>
        <button id="btn-prism" class="action-btn secondary">🔮 Prism</button>
        <button id="btn-exchange" class="action-btn tertiary">🔄 Exchange</button>
        <button id="btn-inventory" class="action-btn quaternary">🎒 Items</button>
      </div>
      <div id="move-select" class="move-select hidden"></div>
      <div id="prism-select" class="item-select hidden"></div>
      <div id="exchange-panel" class="exchange-panel hidden"></div>
      <div id="inventory-panel" class="item-select hidden"></div>
    </div>
  </div>

  <!-- SHOP SCREEN -->
  <div id="screen-shop" class="screen">
    <div class="shop-container">
      <h2>Camp Shop</h2>
      <p class="shard-display">💎 Aether Shards: <span id="shop-shards">0</span></p>
      <div id="shop-items" class="shop-grid"></div>
      <button id="btn-shop-close" class="menu-btn secondary">Close Shop</button>
    </div>
  </div>

  <!-- DEX SCREEN -->
  <div id="screen-dex" class="screen">
    <div class="dex-container">
      <h2>Conductors Dex</h2>
      <p class="dex-info">Discovered: <span id="dex-count">0</span>/18</p>
      <div id="dex-grid" class="dex-grid"></div>
      <button id="btn-dex-close" class="menu-btn secondary">Close Dex</button>
    </div>
  </div>

  <!-- PARTY SCREEN -->
  <div id="screen-party" class="screen">
    <div class="party-container">
      <h2>Your Resonants</h2>
      <div id="party-grid" class="party-grid"></div>
      <button id="btn-party-close" class="menu-btn secondary">Close</button>
    </div>
  </div>

  <!-- CAPTURE ANIMATION -->
  <div id="capture-overlay" class="overlay hidden">
    <div class="capture-animation">
      <div id="capture-prism" class="capture-prism"></div>
    </div>
  </div>

  <!-- LEVEL UP OVERLAY -->
  <div id="levelup-overlay" class="overlay hidden">
    <div class="levelup-box">
      <h3 id="levelup-name">Emberkit</h3>
      <p id="levelup-text">Reached Level 4!</p>
      <button id="btn-levelup-ok" class="menu-btn primary small">OK</button>
    </div>
  </div>

  <!-- EVOLUTION OVERLAY -->
  <div id="evolution-overlay" class="overlay hidden">
    <div class="evolution-box">
      <h3>Evolving...</h3>
      <div id="evo-sprite" class="evo-sprite"></div>
      <p id="evo-text">Emberkit is evolving!</p>
      <button id="btn-evo-ok" class="menu-btn primary small hidden">OK</button>
    </div>
  </div>

  <!-- VICTORY OVERLAY -->
  <div id="victory-overlay" class="overlay hidden">
    <div class="victory-box">
      <h3>Battle Victory!</h3>
      <p id="victory-text"></p>
      <button id="btn-victory-ok" class="menu-btn primary small">Continue</button>
    </div>
  </div>

  <!-- DEFEAT OVERLAY -->
  <div id="defeat-overlay" class="overlay hidden">
    <div class="defeat-box">
      <h3>All Resonants Fainted!</h3>
      <p>You've been returned to the last healing spring.</p>
      <button id="btn-defeat-ok" class="menu-btn primary small">Continue</button>
    </div>
  </div>

  <!-- GAME OVER / WIN -->
  <div id="screen-gameover" class="screen">
    <div class="gameover-content">
      <h1 id="gameover-title">Victory!</h1>
      <p id="gameover-text"></p>
      <button id="btn-gameover-menu" class="menu-btn primary">Return to Menu</button>
    </div>
  </div>
</div>
<script>
'use strict';

// ── class: SoundManager ──
class SoundManager {
  constructor() { this.ctx = null; }
  init() {
    if (!this.ctx) this.ctx = new (window.AudioContext || window.webkitAudioContext)();
    if (this.ctx.state === 'suspended') this.ctx.resume();
  }
  play(type) {
    if (!this.enabled && this.enabled !== false) return;
    if (!this.ctx) return;
    const t = this.ctx.currentTime;
    switch(type) {
      case 'hover': this._tone(800, 0.05, 'sine', 0.1); break;
      case 'select': this._tone(600, 0.08, 'square', 0.15); setTimeout(() => this._tone(900, 0.06, 'square', 0.1), 50); break;
      case 'hit': this._noise(0.12, 0.3); break;
      case 'hitelem': this._tone(400, 0.15, 'sawtooth', 0.25); setTimeout(() => this._tone(600, 0.1, 'sawtooth', 0.2), 80); break;
      case 'supereff': [300,500,700].forEach((f,i) => setTimeout(() => this._tone(f, 0.12, 'square', 0.25), i*60)); break;
      case 'levelup': [523,659,784,1047].forEach((f,i) => setTimeout(() => this._tone(f, 0.15, 'sine', 0.2), i*100)); break;
      case 'capture': [400,500,600,800,1000].forEach((f,i) => setTimeout(() => this._tone(f, 0.1, 'sine', 0.15), i*80)); break;
      case 'victory': [523,659,784,1047,784,1047].forEach((f,i) => setTimeout(() => this._tone(f, 0.12, 'square', 0.15), i*120)); break;
      case 'defeat': [400,350,300,250].forEach((f,i) => setTimeout(() => this._tone(f, 0.2, 'sine', 0.15), i*200)); break;
      case 'evolve': [400,500,600,700,800,900,1000].forEach((f,i) => setTimeout(() => this._tone(f, 0.08, 'sine', 0.2), i*70)); break;
      case 'boss': [200,250,300,250,200].forEach((f,i) => setTimeout(() => this._tone(f, 0.15, 'sawtooth', 0.15), i*150)); break;
    }
  }
  _tone(freq, dur, wave='square', vol=0.1) {
    const o = this.ctx.createOscillator(), g = this.ctx.createGain();
    o.type = wave; o.frequency.value = freq;
    g.gain.setValueAtTime(vol, this.ctx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + dur);
    o.connect(g); g.connect(this.ctx.destination); o.start(); o.stop(this.ctx.currentTime + dur);
  }
  _noise(dur, vol) {
    const buf = this.ctx.createBuffer(1, this.ctx.sampleRate * dur, this.ctx.sampleRate);
    const d = buf.getChannelData(0);
    for (let i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1;
    const src = this.ctx.createBufferSource(), g = this.ctx.createGain();
    src.buffer = buf; g.gain.setValueAtTime(vol, this.ctx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + dur);
    src.connect(g); g.connect(this.ctx.destination); src.start();
  }
}

// ── const: ELEMENTS ──
const ELEMENTS = {
  pyro: { name:'Pyro', strongAgainst:['flora'], weakAgainst:['hydro'], color:'#ff5722' },
  hydro: { name:'Hydro', strongAgainst:['pyro'], weakAgainst:['flora'], color:'#2196f3' },
  flora: { name:'Flora', strongAgainst:['hydro'], weakAgainst:['pyro'], color:'#4caf50' },
  zephyr: { name:'Zephyr', strongAgainst:['earth'], weakAgainst:['volt'], color:'#00e676' },
  earth: { name:'Earth', strongAgainst:['volt'], weakAgainst:['zephyr'], color:'#a1887f' },
  volt: { name:'Volt', strongAgainst:['zephyr'], weakAgainst:['earth'], color:'#ebd841' },
  cosmos: { name:'Cosmos', strongAgainst:[], weakAgainst:[], color:'#a78bfa' }
};

// ── const: MOVES_DB ──
const MOVES_DB = {
  ember_blast:{name:'Ember Blast',power:40,accuracy:100,type:'pyro'},
  flame_wheel:{name:'Flame Wheel',power:60,accuracy:95,type:'pyro'},
  inferno:{name:'Inferno',power:90,accuracy:85,type:'pyro'},
  magma_fist:{name:'Magma Fist',power:75,accuracy:90,type:'pyro'},
  water_jet:{name:'Water Jet',power:40,accuracy:100,type:'hydro'},
  tidal_wave:{name:'Tidal Wave',power:65,accuracy:90,type:'hydro'},
  abyssal_rage:{name:'Abyssal Rage',power:90,accuracy:85,type:'hydro'},
  aqua_slash:{name:'Aqua Slash',power:70,accuracy:95,type:'hydro'},
  vine_whip:{name:'Vine Whip',power:40,accuracy:100,type:'flora'},
  thorn_barrage:{name:'Thorn Barrage',power:60,accuracy:95,type:'flora'},
  bloom_storm:{name:'Bloom Storm',power:85,accuracy:85,type:'flora'},
  root_crush:{name:'Root Crush',power:70,accuracy:90,type:'flora'},
  spark_bolt:{name:'Spark Bolt',power:40,accuracy:100,type:'volt'},
  thunder_claw:{name:'Thunder Claw',power:65,accuracy:95,type:'volt'},
  lightning_storm:{name:'Lightning Storm',power:90,accuracy:80,type:'volt'},
  volt_strike:{name:'Volt Strike',power:75,accuracy:90,type:'volt'},
  gust_blast:{name:'Gust Blast',power:40,accuracy:100,type:'zephyr'},
  cyclone:{name:'Cyclone',power:65,accuracy:90,type:'zephyr'},
  tempest_fury:{name:'Tempest Fury',power:85,accuracy:85,type:'zephyr'},
  wind_shear:{name:'Wind Shear',power:70,accuracy:95,type:'zephyr'},
  rock_throw:{name:'Rock Throw',power:40,accuracy:100,type:'earth'},
  stone_slam:{name:'Stone Slam',power:65,accuracy:90,type:'earth'},
  quake_rage:{name:'Quake Rage',power:85,accuracy:85,type:'earth'},
  boulder_crush:{name:'Boulder Crush',power:75,accuracy:90,type:'earth'},
  tackle:{name:'Tackle',power:30,accuracy:100,type:'cosmos'},
  quick_attack:{name:'Quick Attack',power:35,accuracy:100,type:'cosmos'},
  body_slam:{name:'Body Slam',power:55,accuracy:95,type:'cosmos'},
  headbutt:{name:'Headbutt',power:60,accuracy:90,type:'cosmos'},
  heal_wave:{name:'Heal Wave',power:0,accuracy:100,type:'cosmos',effect:'heal30'}
};

// ── const: RESONANTS_DB ──
const RESONANTS_DB = [
  {id:1,name:'Emberkit',type:'pyro',baseHp:35,baseAtk:28,baseDef:22,baseSpd:24,evoLevel:8,evoId:2,moves:[{move:'tackle',level:1},{move:'ember_blast',level:3},{move:'flame_wheel',level:7},{move:'body_slam',level:10}]},
  {id:2,name:'Pyrowolf',type:'pyro',baseHp:55,baseAtk:42,baseDef:35,baseSpd:38,evoLevel:15,evoId:3,moves:[{move:'tackle',level:1},{move:'ember_blast',level:1},{move:'flame_wheel',level:1},{move:'magma_fist',level:10},{move:'body_slam',level:12}]},
  {id:3,name:'Ignisaur',type:'pyro',baseHp:80,baseAtk:62,baseDef:50,baseSpd:55,evoLevel:null,moves:[{move:'ember_blast',level:1},{move:'flame_wheel',level:1},{move:'magma_fist',level:1},{move:'inferno',level:18}]},
  {id:4,name:'Droplet',type:'hydro',baseHp:38,baseAtk:25,baseDef:20,baseSpd:26,evoLevel:8,evoId:5,moves:[{move:'tackle',level:1},{move:'water_jet',level:3},{move:'tidal_wave',level:7},{move:'body_slam',level:10}]},
  {id:5,name:'Geoxus',type:'hydro',baseHp:58,baseAtk:40,baseDef:36,baseSpd:35,evoLevel:15,evoId:6,moves:[{move:'tackle',level:1},{move:'water_jet',level:1},{move:'tidal_wave',level:1},{move:'aqua_slash',level:10},{move:'body_slam',level:12}]},
  {id:6,name:'Neptunian',type:'hydro',baseHp:85,baseAtk:58,baseDef:52,baseSpd:48,evoLevel:null,moves:[{move:'water_jet',level:1},{move:'tidal_wave',level:1},{move:'aqua_slash',level:1},{move:'abyssal_rage',level:18}]},
  {id:7,name:'Seedling',type:'flora',baseHp:40,baseAtk:22,baseDef:25,baseSpd:20,evoLevel:8,evoId:8,moves:[{move:'tackle',level:1},{move:'vine_whip',level:3},{move:'thorn_barrage',level:7},{move:'heal_wave',level:10}]},
  {id:8,name:'Thornback',type:'flora',baseHp:60,baseAtk:38,baseDef:42,baseSpd:30,evoLevel:15,evoId:9,moves:[{move:'tackle',level:1},{move:'vine_whip',level:1},{move:'thorn_barrage',level:1},{move:'root_crush',level:10},{move:'heal_wave',level:12}]},
  {id:9,name:'Bloomgoliath',type:'flora',baseHp:90,baseAtk:55,baseDef:65,baseSpd:35,evoLevel:null,moves:[{move:'vine_whip',level:1},{move:'thorn_barrage',level:1},{move:'root_crush',level:1},{move:'bloom_storm',level:18}]},
  {id:10,name:'Sparky',type:'volt',baseHp:32,baseAtk:30,baseDef:18,baseSpd:30,evoLevel:8,evoId:11,moves:[{move:'quick_attack',level:1},{move:'spark_bolt',level:3},{move:'thunder_claw',level:7},{move:'body_slam',level:10}]},
  {id:11,name:'Boltsaber',type:'volt',baseHp:52,baseAtk:45,baseDef:30,baseSpd:48,evoLevel:15,evoId:12,moves:[{move:'quick_attack',level:1},{move:'spark_bolt',level:1},{move:'thunder_claw',level:1},{move:'volt_strike',level:10},{move:'body_slam',level:12}]},
  {id:12,name:'Stormlord',type:'volt',baseHp:75,baseAtk:65,baseDef:45,baseSpd:68,evoLevel:null,moves:[{move:'spark_bolt',level:1},{move:'thunder_claw',level:1},{move:'volt_strike',level:1},{move:'lightning_storm',level:18}]},
  {id:13,name:'Gusty',type:'zephyr',baseHp:30,baseAtk:26,baseDef:20,baseSpd:32,evoLevel:8,evoId:14,moves:[{move:'quick_attack',level:1},{move:'gust_blast',level:3},{move:'cyclone',level:7},{move:'body_slam',level:10}]},
  {id:14,name:'Slipstream',type:'zephyr',baseHp:50,baseAtk:42,baseDef:32,baseSpd:52,evoLevel:15,evoId:15,moves:[{move:'quick_attack',level:1},{move:'gust_blast',level:1},{move:'cyclone',level:1},{move:'wind_shear',level:10},{move:'body_slam',level:12}]},
  {id:15,name:'Zephyrus',type:'zephyr',baseHp:72,baseAtk:60,baseDef:48,baseSpd:75,evoLevel:null,moves:[{move:'gust_blast',level:1},{move:'cyclone',level:1},{move:'wind_shear',level:1},{move:'tempest_fury',level:18}]},
  {id:16,name:'Pebble',type:'earth',baseHp:42,baseAtk:24,baseDef:28,baseSpd:16,evoLevel:8,evoId:17,moves:[{move:'tackle',level:1},{move:'rock_throw',level:3},{move:'stone_slam',level:7},{move:'headbutt',level:10}]},
  {id:17,name:'Obsidian',type:'earth',baseHp:65,baseAtk:40,baseDef:48,baseSpd:25,evoLevel:15,evoId:18,moves:[{move:'tackle',level:1},{move:'rock_throw',level:1},{move:'stone_slam',level:1},{move:'boulder_crush',level:10},{move:'headbutt',level:12}]},
  {id:18,name:'Terrasunder',type:'earth',baseHp:95,baseAtk:56,baseDef:70,baseSpd:30,evoLevel:null,moves:[{move:'rock_throw',level:1},{move:'stone_slam',level:1},{move:'boulder_crush',level:1},{move:'quake_rage',level:18}]}
];

// ── const: RESONANT_MAP ──
const RESONANT_MAP = Object.fromEntries(RESONANTS_DB.map(r => [r.id, r]));

// ── function: generateSpriteSVG ──
function generateSpriteSVG(resonantId, size = 120) {
  const templates = {
    1: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#ff6b35"/><stop offset="100%" stop-color="#ff9800"/></linearGradient></defs><ellipse cx="50" cy="55" rx="28" ry="24" fill="url(#g1)"/><circle cx="50" cy="38" r="18" fill="url(#g1)"/><circle cx="42" cy="35" r="3" fill="#fff"/><circle cx="58" cy="35" r="3" fill="#fff"/><circle cx="42" cy="35" r="1.5" fill="#000"/><circle cx="58" cy="35" r="1.5" fill="#000"/><path d="M46 42 Q50 46 54 42" stroke="#c62828" stroke-width="1.5" fill="none"/><ellipse cx="35" cy="30" rx="5" ry="7" fill="#ff6b35"/><ellipse cx="65" cy="30" rx="5" ry="7" fill="#ff6b35"/><path d="M48 20 Q50 10 52 20" fill="#ff9800"/></svg>`,
    2: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g2" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#d32f2f"/><stop offset="100%" stop-color="#ff5722"/></linearGradient></defs><ellipse cx="50" cy="58" rx="32" ry="26" fill="url(#g2)"/><circle cx="50" cy="36" r="20" fill="url(#g2)"/><polygon points="40,28 44,18 48,28" fill="#ff9800"/><polygon points="52,28 56,18 60,28" fill="#ff9800"/><circle cx="43" cy="33" r="3.5" fill="#fff"/><circle cx="57" cy="33" r="3.5" fill="#fff"/><circle cx="43" cy="33" r="1.8" fill="#b71c1c"/><circle cx="57" cy="33" r="1.8" fill="#b71c1c"/><path d="M44 42 L46 40 L48 42 L50 40 L52 42 L54 40 L56 42" stroke="#fff" stroke-width="1.5" fill="none"/></svg>`,
    3: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g3" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#b71c1c"/><stop offset="100%" stop-color="#ff6b35"/></linearGradient></defs><ellipse cx="50" cy="55" rx="35" ry="30" fill="url(#g3)"/><circle cx="50" cy="32" r="22" fill="url(#g3)"/><path d="M38 16 Q42 4 46 16" fill="#ff9800"/><path d="M54 16 Q58 4 62 16" fill="#ff9800"/><circle cx="42" cy="29" r="4" fill="#fff"/><circle cx="58" cy="29" r="4" fill="#fff"/><circle cx="42" cy="29" r="2" fill="#b71c1c"/><circle cx="58" cy="29" r="2" fill="#b71c1c"/><path d="M30 55 Q20 65 25 75" stroke="#ff6b35" stroke-width="4" fill="none"/><path d="M70 55 Q80 65 75 75" stroke="#ff6b35" stroke-width="4" fill="none"/></svg>`,
    4: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g4" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#42a5f5"/><stop offset="100%" stop-color="#1e88e5"/></linearGradient></defs><ellipse cx="50" cy="55" rx="26" ry="22" fill="url(#g4)"/><circle cx="50" cy="38" r="17" fill="url(#g4)"/><circle cx="43" cy="35" r="3" fill="#fff"/><circle cx="57" cy="35" r="3" fill="#fff"/><circle cx="43" cy="35" r="1.5" fill="#0d47a1"/><circle cx="57" cy="35" r="1.5" fill="#0d47a1"/><path d="M46 42 Q50 45 54 42" stroke="#0d47a1" stroke-width="1.5" fill="none"/><ellipse cx="50" cy="80" rx="12" ry="6" fill="#90caf9" opacity="0.6"/></svg>`,
    5: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g5" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#1565c0"/><stop offset="100%" stop-color="#42a5f5"/></linearGradient></defs><ellipse cx="50" cy="58" rx="30" ry="25" fill="url(#g5)"/><circle cx="50" cy="36" r="19" fill="url(#g5)"/><path d="M42 22 Q50 12 58 22" fill="#1e88e5"/><circle cx="43" cy="33" r="3.5" fill="#fff"/><circle cx="57" cy="33" r="3.5" fill="#fff"/><circle cx="43" cy="33" r="1.8" fill="#0d47a1"/><circle cx="57" cy="33" r="1.8" fill="#0d47a1"/><path d="M28 60 Q20 70 25 80" stroke="#42a5f5" stroke-width="3" fill="none"/><path d="M72 60 Q80 70 75 80" stroke="#42a5f5" stroke-width="3" fill="none"/></svg>`,
    6: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g6" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#0d47a1"/><stop offset="100%" stop-color="#1e88e5"/></linearGradient></defs><ellipse cx="50" cy="55" rx="36" ry="30" fill="url(#g6)"/><circle cx="50" cy="32" r="22" fill="url(#g6)"/><path d="M38 14 Q50 2 62 14" fill="#1565c0"/><circle cx="42" cy="29" r="4" fill="#fff"/><circle cx="58" cy="29" r="4" fill="#fff"/><circle cx="42" cy="29" r="2" fill="#0d47a1"/><circle cx="58" cy="29" r="2" fill="#0d47a1"/><path d="M30 55 Q15 65 20 80" stroke="#42a5f5" stroke-width="5" fill="none"/><path d="M70 55 Q85 65 80 80" stroke="#42a5f5" stroke-width="5" fill="none"/><circle cx="35" cy="50" r="3" fill="#90caf9" opacity="0.5"/><circle cx="65" cy="50" r="3" fill="#90caf9" opacity="0.5"/></svg>`,
    7: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g7" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#66bb6a"/><stop offset="100%" stop-color="#43a047"/></linearGradient></defs><ellipse cx="50" cy="58" rx="24" ry="20" fill="url(#g7)"/><circle cx="50" cy="40" r="16" fill="url(#g7)"/><path d="M50 24 Q48 14 50 8 Q52 14 50 24" fill="#8bc34a"/><circle cx="44" cy="37" r="2.5" fill="#fff"/><circle cx="56" cy="37" r="2.5" fill="#fff"/><circle cx="44" cy="37" r="1.2" fill="#1b5e20"/><circle cx="56" cy="37" r="1.2" fill="#1b5e20"/><path d="M46 44 Q50 47 54 44" stroke="#1b5e20" stroke-width="1.5" fill="none"/></svg>`,
    8: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g8" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#2e7d32"/><stop offset="100%" stop-color="#4caf50"/></linearGradient></defs><ellipse cx="50" cy="58" rx="28" ry="24" fill="url(#g8)"/><circle cx="50" cy="36" r="18" fill="url(#g8)"/><path d="M44 20 Q50 10 56 20" fill="#66bb6a"/><polygon points="38,30 36,22 40,28" fill="#ffeb3b"/><polygon points="62,30 64,22 60,28" fill="#ffeb3b"/><circle cx="43" cy="34" r="3" fill="#fff"/><circle cx="57" cy="34" r="3" fill="#fff"/><circle cx="43" cy="34" r="1.5" fill="#1b5e20"/><circle cx="57" cy="34" r="1.5" fill="#1b5e20"/></svg>`,
    9: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g9" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#1b5e20"/><stop offset="100%" stop-color="#388e3c"/></linearGradient></defs><ellipse cx="50" cy="55" rx="34" ry="28" fill="url(#g9)"/><circle cx="50" cy="32" r="20" fill="url(#g9)"/><path d="M40 16 Q50 4 60 16" fill="#4caf50"/><circle cx="42" cy="29" r="4" fill="#fff"/><circle cx="58" cy="29" r="4" fill="#fff"/><circle cx="42" cy="29" r="2" fill="#1b5e20"/><circle cx="58" cy="29" r="2" fill="#1b5e20"/><path d="M30 55 Q18 60 22 72" stroke="#4caf50" stroke-width="4" fill="none"/><path d="M70 55 Q82 60 78 72" stroke="#4caf50" stroke-width="4" fill="none"/><circle cx="38" cy="48" r="4" fill="#e91e63" opacity="0.7"/><circle cx="62" cy="48" r="4" fill="#ffeb3b" opacity="0.7"/></svg>`,
    10: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g10" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#fdd835"/><stop offset="100%" stop-color="#ffb300"/></linearGradient></defs><ellipse cx="50" cy="56" rx="24" ry="22" fill="url(#g10)"/><circle cx="50" cy="38" r="17" fill="url(#g10)"/><polygon points="48,22 50,10 52,22" fill="#ffeb3b"/><circle cx="43" cy="35" r="3" fill="#fff"/><circle cx="57" cy="35" r="3" fill="#fff"/><circle cx="43" cy="35" r="1.5" fill="#f57f17"/><circle cx="57" cy="35" r="1.5" fill="#f57f17"/><path d="M46 42 Q50 45 54 42" stroke="#f57f17" stroke-width="1.5" fill="none"/></svg>`,
    11: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g11" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#f9a825"/><stop offset="100%" stop-color="#ff8f00"/></linearGradient></defs><ellipse cx="50" cy="58" rx="30" ry="24" fill="url(#g11)"/><circle cx="50" cy="36" r="19" fill="url(#g11)"/><polygon points="44,20 47,8 50,20" fill="#ffeb3b"/><polygon points="50,20 53,8 56,20" fill="#ffeb3b"/><circle cx="43" cy="33" r="3.5" fill="#fff"/><circle cx="57" cy="33" r="3.5" fill="#fff"/><circle cx="43" cy="33" r="1.8" fill="#f57f17"/><circle cx="57" cy="33" r="1.8" fill="#f57f17"/><path d="M28 60 Q20 68 24 78" stroke="#ffb300" stroke-width="3" fill="none"/></svg>`,
    12: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g12" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#f57f17"/><stop offset="100%" stop-color="#ff8f00"/></linearGradient></defs><ellipse cx="50" cy="55" rx="36" ry="30" fill="url(#g12)"/><circle cx="50" cy="32" r="22" fill="url(#g12)"/><polygon points="40,14 44,2 48,14" fill="#ffeb3b"/><polygon points="52,14 56,2 60,14" fill="#ffeb3b"/><circle cx="42" cy="29" r="4" fill="#fff"/><circle cx="58" cy="29" r="4" fill="#fff"/><circle cx="42" cy="29" r="2" fill="#f57f17"/><circle cx="58" cy="29" r="2" fill="#f57f17"/><path d="M30 55 Q16 62 20 78" stroke="#ffb300" stroke-width="5" fill="none"/><path d="M70 55 Q84 62 80 78" stroke="#ffb300" stroke-width="5" fill="none"/></svg>`,
    13: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g13" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#b9f6ca"/><stop offset="100%" stop-color="#00e676"/></linearGradient></defs><ellipse cx="50" cy="58" rx="24" ry="20" fill="url(#g13)"/><circle cx="50" cy="40" r="16" fill="url(#g13)"/><path d="M44 26 Q50 18 56 26" fill="#69f0ae"/><circle cx="44" cy="37" r="2.5" fill="#fff"/><circle cx="56" cy="37" r="2.5" fill="#fff"/><circle cx="44" cy="37" r="1.2" fill="#00c853"/><circle cx="56" cy="37" r="1.2" fill="#00c853"/><path d="M46 44 Q50 47 54 44" stroke="#00c853" stroke-width="1.5" fill="none"/></svg>`,
    14: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g14" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00c853"/><stop offset="100%" stop-color="#69f0ae"/></linearGradient></defs><ellipse cx="50" cy="58" rx="28" ry="24" fill="url(#g14)"/><circle cx="50" cy="36" r="18" fill="url(#g14)"/><path d="M42 22 Q50 12 58 22" fill="#b9f6ca"/><circle cx="43" cy="33" r="3" fill="#fff"/><circle cx="57" cy="33" r="3" fill="#fff"/><circle cx="43" cy="33" r="1.5" fill="#00c853"/><circle cx="57" cy="33" r="1.5" fill="#00c853"/><path d="M26 60 Q18 68 22 78" stroke="#00e676" stroke-width="3" fill="none"/></svg>`,
    15: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g15" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00c853"/><stop offset="100%" stop-color="#b9f6ca"/></linearGradient></defs><ellipse cx="50" cy="55" rx="34" ry="28" fill="url(#g15)"/><circle cx="50" cy="32" r="20" fill="url(#g15)"/><path d="M40 16 Q50 4 60 16" fill="#69f0ae"/><circle cx="42" cy="29" r="4" fill="#fff"/><circle cx="58" cy="29" r="4" fill="#fff"/><circle cx="42" cy="29" r="2" fill="#00c853"/><circle cx="58" cy="29" r="2" fill="#00c853"/><path d="M30 55 Q16 62 20 78" stroke="#b9f6ca" stroke-width="4" fill="none"/><path d="M70 55 Q84 62 80 78" stroke="#b9f6ca" stroke-width="4" fill="none"/></svg>`,
    16: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g16" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#a1887f"/><stop offset="100%" stop-color="#8d6e63"/></linearGradient></defs><ellipse cx="50" cy="58" rx="24" ry="20" fill="url(#g16)"/><circle cx="50" cy="40" r="17" fill="url(#g16)"/><path d="M44 26 Q50 18 56 26" fill="#bcaaa4"/><circle cx="43" cy="37" r="2.5" fill="#fff"/><circle cx="57" cy="37" r="2.5" fill="#fff"/><circle cx="43" cy="37" r="1.2" fill="#4e342e"/><circle cx="57" cy="37" r="1.2" fill="#4e342e"/><path d="M46 44 Q50 47 54 44" stroke="#4e342e" stroke-width="1.5" fill="none"/></svg>`,
    17: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g17" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#5d4037"/><stop offset="100%" stop-color="#8d6e63"/></linearGradient></defs><ellipse cx="50" cy="58" rx="30" ry="24" fill="url(#g17)"/><circle cx="50" cy="36" r="19" fill="url(#g17)"/><path d="M42 22 Q50 12 58 22" fill="#a1887f"/><polygon points="38,30 36,22 40,28" fill="#d7ccc8"/><polygon points="62,30 64,22 60,28" fill="#d7ccc8"/><circle cx="43" cy="33" r="3" fill="#fff"/><circle cx="57" cy="33" r="3" fill="#fff"/><circle cx="43" cy="33" r="1.5" fill="#4e342e"/><circle cx="57" cy="33" r="1.5" fill="#4e342e"/></svg>`,
    18: `<svg width="${size}" height="${size}" viewBox="0 0 100 100"><defs><linearGradient id="g18" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#3e2723"/><stop offset="100%" stop-color="#5d4037"/></linearGradient></defs><ellipse cx="50" cy="55" rx="36" ry="30" fill="url(#g18)"/><circle cx="50" cy="32" r="22" fill="url(#g18)"/><path d="M40 16 Q50 4 60 16" fill="#795548"/><circle cx="42" cy="29" r="4" fill="#fff"/><circle cx="58" cy="29" r="4" fill="#fff"/><circle cx="42" cy="29" r="2" fill="#3e2723"/><circle cx="58" cy="29" r="2" fill="#3e2723"/><path d="M30 55 Q16 62 20 78" stroke="#8d6e63" stroke-width="5" fill="none"/><path d="M70 55 Q84 62 80 78" stroke="#8d6e63" stroke-width="5" fill="none"/></svg>`
  };
  return templates[resonantId] || `<svg width="${size}" height="${size}"><circle cx="${size/2}" cy="${size/2}" r="${size/3}" fill="#a78bfa"/></svg>`;
}

// ── function: calcStats ──
function calcStats(template, level) {
  const hp = Math.floor((template.baseHp * 2 + level * 0.5) * (1 + level / 100));
  const atk = Math.floor(template.baseAtk * (1 + level / 50));
  const def = Math.floor(template.baseDef * (1 + level / 50));
  const spd = Math.floor(template.baseSpd * (1 + level / 50));
  return { hp, maxHp: hp, atk, def, spd };
}

// ── function: calcDamage ──
function calcDamage(attacker, moveKey, defender) {
  const move = MOVES_DB[moveKey];
  if (!move || move.power === 0) return null;
  const typeMod = TYPE_CHART[move.type]?.[defender.type] || 1;
  const baseDmg = (((2 * attacker.level / 5 + 2) * move.power * (attacker.atk / defender.def)) / 50 + 2);
  const randomFactor = 0.85 + Math.random() * 0.15;
  let dmg = Math.max(1, Math.floor(baseDmg * typeMod * randomFactor));
  const isCrit = Math.random() < (attacker.critBoost ? 0.3 : 0.1);
  if (isCrit) dmg = Math.floor(dmg * 1.5);
  return { damage: dmg, typeMod, isCrit };
}

// ── function: xpForLevel ──
function xpForLevel(level) { return Math.floor(20 + level * level * 1.5); }

// ── const: ZONES ──
const ZONES = [
  {
    id: 'verdant_canopy', name: 'The Verdant Canopy', icon: '🌿', levelRange: [1, 5],
    types: ['flora', 'zephyr'],
    nodes: [
      { type:'wild', label:'Tall Grass', icon:'🌱', resonants:[7,13] },
      { type:'cache', label:'Mystery Cache', icon:'📦', reward:{shards:25} },
      { type:'wild', label:'Deep Bushes', icon:'🌿', resonants:[7,8,13] },
      { type:'heal', label:'Healing Spring', icon:'💧' },
      { type:'npc', label:'Conductor Mira', icon:'⚔️', name:'Mira', team:[{id:7,level:4},{id:13,level:3}] },
      { type:'wild', label:'Ancient Grove', icon:'🌳', resonants:[8,13] },
      { type:'cache', label:'Hidden Cache', icon:'📦', reward:{shards:35} },
      { type:'boss', label:'Gatekeeper', icon:'👑', name:'Thornwarden', team:[{id:9,level:6}], dialog:'You dare enter the Verdant Canopy? Prove your worth, Conductor!' }
    ]
  },
  {
    id: 'sunken_grotto', name: 'The Sunken Grotto', icon: '🌊', levelRange: [5, 10],
    types: ['hydro', 'earth'],
    nodes: [
      { type:'wild', label:'Shallow Pool', icon:'💧', resonants:[4,16] },
      { type:'cache', label:'Sunken Chest', icon:'📦', reward:{shards:40} },
      { type:'npc', label:'Conductor Kai', icon:'⚔️', name:'Kai', team:[{id:5,level:7},{id:16,level:6}] },
      { type:'wild', label:'Deep Caverns', icon:'🕳️', resonants:[4,5,16] },
      { type:'heal', label:'Crystal Spring', icon:'💧' },
      { type:'wild', label:'Underwater Ruins', icon:'🏛️', resonants:[5,17] },
      { type:'cache', label:'Treasure Vault', icon:'📦', reward:{shards:50} },
      { type:'boss', label:'Gatekeeper', icon:'👑', name:'Abyssal Maw', team:[{id:6,level:10},{id:17,level:9}], dialog:'The depths claim all who trespass. Turn back... or be consumed!' }
    ]
  },
  {
    id: 'obsidian_crags', name: 'The Obsidian Crags', icon: '🔥', levelRange: [10, 15],
    types: ['pyro', 'volt'],
    nodes: [
      { type:'wild', label:'Lava Fields', icon:'🌋', resonants:[1,10] },
      { type:'npc', label:'Conductor Rex', icon:'⚔️', name:'Rex', team:[{id:2,level:11},{id:11,level:10}] },
      { type:'cache', label:'Magma Cache', icon:'📦', reward:{shards:60} },
      { type:'wild', label:'Volcanic Core', icon:'💥', resonants:[2,3,11] },
      { type:'heal', label:'Hot Springs', icon:'♨️' },
      { type:'wild', label:'Thunder Peak', icon:'⚡', resonants:[3,12] },
      { type:'cache', label:'Sky Cache', icon:'📦', reward:{shards:75} },
      { type:'boss', label:'Gatekeeper', icon:'👑', name:'Inferno Titan', team:[{id:3,level:14},{id:12,level:13}], dialog:'The crags burn with ancient fury! Only the strongest Conductors may pass!' }
    ]
  },
  {
    id: 'apex_pillar', name: 'The Apex Pillar', icon: '⭐', levelRange: [15, 20],
    types: ['pyro','hydro','flora','volt','zephyr','earth'],
    nodes: [
      { type:'wild', label:'Cosmic Field', icon:'✨', resonants:[3,6,9,12,15,18] },
      { type:'npc', label:'Conductor Sage', icon:'⚔️', name:'Sage', team:[{id:3,level:15},{id:6,level:15}] },
      { type:'wild', label:'Starfall Arena', icon:'🌟', resonants:[9,12,18] },
      { type:'heal', label:'Eternal Spring', icon:'💧' },
      { type:'npc', label:'Conductor Prime', icon:'⚔️', name:'Prime', team:[{id:6,level:16},{id:9,level:15},{id:12,level:16}] },
      { type:'wild', label:'Final Challenge', icon:'💫', resonants:[3,6,9,12,15,18] },
      { type:'boss', label:'FINAL BOSS', icon:'👑', name:'The Conductor Supreme', team:[{id:3,level:18},{id:6,level:18},{id:9,level:17},{id:12,level:18}], dialog:'I am the apex of all Conductors. If you can defeat my Resonants, Aethelgard will sing your name forever!' }
    ]
  }
];

// ── const: SHOP_ITEMS ──
const SHOP_ITEMS = [
  { id:'prism_basic', name:'Harmonizer Prism', desc:'Basic capture prism. Good catch rate.', price:30, type:'prism', tier:'basic' },
  { id:'prism_hyper', name:'Hyper-Conductive Prism', desc:'Advanced prism with high catch rate.', price:80, type:'prism', tier:'hyper' },
  { id:'elixir_hp', name:'Core Restorer', desc:'Restores 50% HP to active Resonant.', price:25, type:'item', effect:'heal50' },
  { id:'elixir_full', name:'Vital Elixir', desc:'Fully restores HP of active Resonant.', price:60, type:'item', effect:'healfull' },
  { id:'status_cure', name:'Resonance Cleanser', desc:'Heals all status conditions.', price:35, type:'item', effect:'cureall' }
];

// ── function: createPlayerResonant ──
function createPlayerResonant(templateId, level = 1) {
  const template = RESONANT_MAP[templateId];
  const stats = calcStats(template, level);
  return {
    id: Date.now() + Math.random(),
    templateId,
    name: template.name,
    type: template.type,
    level,
    xp: 0,
    xpToNext: xpForLevel(level),
    hp: stats.hp,
    maxHp: stats.maxHp,
    atk: stats.atk,
    def: stats.def,
    spd: stats.spd,
    moves: template.moves.filter(m => m.level <= level).map(m => m.move),
    statusEffects: [],
    critBoost: false
  };
}

// ── function: createEnemyResonant ──
function createEnemyResonant(templateId, level) {
  const template = RESONANT_MAP[templateId];
  const stats = calcStats(template, level);
  return {
    id: 'enemy_' + Math.random(),
    templateId,
    name: template.name,
    type: template.type,
    level,
    hp: stats.hp,
    maxHp: stats.maxHp,
    atk: stats.atk,
    def: stats.def,
    spd: stats.spd,
    moves: template.moves.filter(m => m.level <= level).map(m => m.move),
    statusEffects: [],
    critBoost: false
  };
}

// ── function: getAvailableMoves ──
function getAvailableMoves(resonant) {
  const template = RESONANT_MAP[resonant.templateId];
  return resonant.moves.map(k => ({ key: k, ...MOVES_DB[k] }));
}

// ── class: GameEngine ──
class GameEngine {
  constructor() {
    this.sound = new SoundManager();
    this.state = null;
    this.currentScreen = 'menu';
    this.battleState = null;
    this.isProcessing = false;
    this._prevScreen = 'map';
  }

  init() {
    this.loadGame();
    this.setupEventListeners();
    if (this.state) {
      document.getElementById('btn-continue').classList.remove('hidden');
      document.getElementById('btn-reset').classList.remove('hidden');
    }
    this.showScreen('menu');
  }

  setupEventListeners() {
    const self = this;
    document.getElementById('btn-new-game').onclick = () => self.startNewGame();
    document.getElementById('btn-continue').onclick = () => self.continueGame();
    document.getElementById('btn-reset').onclick = () => { if(confirm('Reset all progress?')) { localStorage.removeItem('aethelgard_save'); location.reload(); } };
    document.getElementById('dialog-ok').onclick = () => self.closeDialog();
    document.getElementById('btn-shop-close').onclick = () => self.showScreen(self._prevScreen);
    document.getElementById('btn-dex-close').onclick = () => self.showScreen(self._prevScreen);
    document.getElementById('btn-party-close').onclick = () => self.showScreen(self._prevScreen);
    document.getElementById('btn-menu-back').onclick = () => { self._prevScreen = 'menu'; self.showScreen('menu'); };
    document.getElementById('btn-shop').onclick = () => { self._prevScreen = 'shop'; self.renderShop(); self.showScreen('shop'); };
    document.getElementById('btn-dex').onclick = () => { self._prevScreen = 'dex'; self.renderDex(); self.showScreen('dex'); };
    document.getElementById('btn-party').onclick = () => { self._prevScreen = 'party'; self.renderParty(); self.showScreen('party'); };
    document.getElementById('btn-attack').onclick = () => self.openMoveSelect();
    document.getElementById('btn-prism').onclick = () => self.openPrismSelect();
    document.getElementById('btn-exchange').onclick = () => self.openExchangePanel();
    document.getElementById('btn-inventory').onclick = () => self.openInventoryPanel();
    document.getElementById('btn-levelup-ok').onclick = () => { document.getElementById('levelup-overlay').classList.add('hidden'); };
    document.getElementById('btn-evo-ok').onclick = () => { document.getElementById('evolution-overlay').classList.add('hidden'); };
    document.getElementById('btn-victory-ok').onclick = () => self.afterVictory();
    document.getElementById('btn-defeat-ok').onclick = () => self.afterDefeat();
    document.getElementById('btn-gameover-menu').onclick = () => { localStorage.removeItem('aethelgard_save'); location.reload(); };

    document.querySelectorAll('.menu-btn, .icon-btn, .action-btn, .buy-btn').forEach(b => {
      b.addEventListener('mouseenter', () => self.sound.play('hover'));
    });
  }

  startNewGame() {
    this.sound.init();
    const starterChoice = prompt("Choose your starter Resonant:\n1. Emberkit (Pyro)\n2. Droplet (Hydro)\n3. Seedling (Flora)", "1");
    let starterId;
    switch(starterChoice) {
      case '1': starterId = 1; break;
      case '2': starterId = 4; break;
      case '3': starterId = 7; break;
      default: starterId = 1;
    }
    this.state = {
      party: [createPlayerResonant(starterId, 1)],
      inventory: { prism_basic: 5, prism_hyper: 0, elixir_hp: 3, elixir_full: 1, status_cure: 2 },
      shards: 50,
      currentZone: 0,
      zoneProgress: [0, -1, -1, -1],
      discoveredResonants: new Set([starterId]),
      bossesDefeated: [],
      gameWon: false
    };
    this.saveGame();
    this.showDialog("Welcome to Aethelgard! Your journey as a Conductor begins. Explore the zones, collect Resonants, and become the greatest Conductor!");
    this._prevScreen = 'map';
    this.renderMap();
    this.showScreen('map');
  }

  continueGame() {
    this.sound.init();
    this._prevScreen = 'map';
    this.renderMap();
    this.showScreen('map');
  }

  saveGame() {
    if (!this.state) return;
    const s = JSON.parse(JSON.stringify(this.state));
    s.discoveredResonants = Array.from(s.discoveredResonants);
    localStorage.setItem('aethelgard_save', JSON.stringify(s));
  }

  loadGame() {
    try {
      const raw = localStorage.getItem('aethelgard_save');
      if (raw) {
        const s = JSON.parse(raw);
        s.discoveredResonants = new Set(s.discoveredResonants);
        this.state = s;
      }
    } catch(e) {}
  }

  showScreen(name) {
    document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
    const el = document.getElementById('screen-' + name);
    if (el) { el.style.display = 'flex'; requestAnimationFrame(() => el.classList.add('active')); }
    this.currentScreen = name;
  }

  showDialog(text, callback) {
    document.getElementById('dialog-text').textContent = text;
    const overlay = document.getElementById('map-dialog');
    overlay.classList.remove('hidden');
    const self = this;
    const okBtn = document.getElementById('dialog-ok');
    okBtn.onclick = () => { self.closeDialog(); if (callback) callback(); };
  }

  closeDialog() {
    document.getElementById('map-dialog').classList.add('hidden');
  }

  renderMap() {
    if (!this.state) return;
    const zoneIdx = this.state.currentZone;
    const zone = ZONES[zoneIdx];
    document.getElementById('zone-title').textContent = zone.icon + ' ' + zone.name;
    document.getElementById('shard-count').textContent = '\uD83D\uDC8E ' + this.state.shards;

    const area = document.getElementById('map-area');
    area.innerHTML = '';

    const tabContainer = document.createElement('div');
    tabContainer.className = 'zone-selector';
    ZONES.forEach((z, i) => {
      const tab = document.createElement('div');
      tab.className = 'zone-tab' + (i === zoneIdx ? ' active' : '') + (this.state.zoneProgress[i] < 0 && i > 0 ? ' locked' : '');
      tab.textContent = z.icon + ' ' + z.name;
      if (this.state.zoneProgress[i] >= 0 && i !== zoneIdx) {
        const zi = i;
        tab.onclick = () => { this.state.currentZone = zi; this.saveGame(); this.renderMap(); };
      }
      tabContainer.appendChild(tab);
    });
    area.appendChild(tabContainer);

    const path = document.createElement('div');
    path.className = 'zone-path';
    zone.nodes.forEach((node, i) => {
      const el = document.createElement('div');
      el.className = 'map-node';
      const progress = this.state.zoneProgress[zoneIdx];
      if (i < progress) el.classList.add('completed');
      else if (i === progress) el.classList.add('current');
      else if (progress < i - 1) el.classList.add('locked');

      el.innerHTML = node.icon + '<span class="node-label">' + node.label + '</span>';
      if (!el.classList.contains('locked')) {
        const ni = i;
        el.onclick = () => this.interactNode(zoneIdx, ni);
      }
      path.appendChild(el);
    });
    area.appendChild(path);
  }

  interactNode(zoneIdx, nodeIdx) {
    if (nodeIdx !== this.state.zoneProgress[zoneIdx]) return;
    const zone = ZONES[zoneIdx];
    const node = zone.nodes[nodeIdx];

    switch(node.type) {
      case 'wild': this.startWildBattle(zoneIdx, node); break;
      case 'cache': this.openCache(node); break;
      case 'heal': this.healParty(); break;
      case 'npc': this.startNPBBattle(zoneIdx, node); break;
      case 'boss': this.startBossBattle(zoneIdx, node); break;
    }
  }

  openCache(node) {
    const shards = node.reward.shards || 20;
    this.state.shards += shards;
    this.saveGame();
    this.showDialog('You found a Mystery Cache! It contained ' + shards + ' Aether Shards!');
    this.advanceNode();
  }

  healParty() {
    this.state.party.forEach(r => { r.hp = r.maxHp; r.statusEffects = []; r.critBoost = false; });
    this.saveGame();
    this.showDialog('Your Resonants have been fully restored at the Healing Spring!');
    this.advanceNode();
  }

  advanceNode() {
    const zoneIdx = this.state.currentZone;
    if (this.state.zoneProgress[zoneIdx] < ZONES[zoneIdx].nodes.length - 1) {
      this.state.zoneProgress[zoneIdx]++;
    }
    this.saveGame();
    this.renderMap();
  }

  startWildBattle(zoneIdx, node) {
    const zone = ZONES[zoneIdx];
    const minLvl = zone.levelRange[0], maxLvl = zone.levelRange[1];
    const lvl = minLvl + Math.floor(Math.random() * (maxLvl - minLvl + 1));
    const resonantPool = node.resonants || zone.types.map(t => {
      return RESONANTS_DB.filter(r => r.type === t && !r.evoLevel).map(r => r.id);
    }).flat();
    const templateId = resonantPool[Math.floor(Math.random() * resonantPool.length)];
    const enemy = createEnemyResonant(templateId, lvl);
    this.state.discoveredResonants.add(templateId);
    this.saveGame();
    this.startBattle(enemy, 'wild', zoneIdx);
  }

  startNPBBattle(zoneIdx, node) {
    const enemies = node.team.map(t => createEnemyResonant(t.id, t.level));
    this.state.discoveredResonants.add(enemies[0].templateId);
    this.saveGame();
    this.startBattle(enemies, 'npc', zoneIdx, node.name || 'Conductor');
  }

  startBossBattle(zoneIdx, node) {
    const enemies = node.team.map(t => createEnemyResonant(t.id, t.level));
    enemies.forEach(e => this.state.discoveredResonants.add(e.templateId));
    this.saveGame();
    this.sound.play('boss');
    this.startBattle(enemies, 'boss', zoneIdx, node.name, node.dialog);
  }

  startBattle(enemyOrEnemies, battleType, zoneIdx, opponentName, dialog) {
    const enemies = Array.isArray(enemyOrEnemies) ? enemyOrEnemies : [enemyOrEnemies];
    this.battleState = {
      type: battleType,
      zoneIdx: zoneIdx,
      opponentName: opponentName || 'Wild Resonant',
      enemies: enemies,
      currentEnemyIdx: 0,
      playerActiveIdx: 0,
      turn: 'player',
      log: [],
      isCapturing: false
    };

    if (dialog) {
      this.showScreen('battle');
      this.renderBattle();
      setTimeout(() => {
        this.addLog(dialog);
        this.showDialog(dialog, () => {});
      }, 300);
    } else {
      this.showScreen('battle');
      this.renderBattle();
      if (battleType === 'wild') {
        this.addLog('A wild ' + enemies[0].name + ' appeared!');
      } else {
        this.addLog((opponentName || 'The opponent') + ' sent out ' + enemies[0].name + '!');
      }
    }
  }

  getActiveEnemy() { return this.battleState.enemies[this.battleState.currentEnemyIdx]; }
  getActivePlayer() { return this.state.party[this.battleState.playerActiveIdx]; }

  renderBattle() {
    const enemy = this.getActiveEnemy();
    const player = this.getActivePlayer();
    if (!enemy || !player) return;

    document.getElementById('enemy-name').textContent = enemy.name;
    document.getElementById('enemy-level').textContent = 'Lv ' + enemy.level;
    document.getElementById('enemy-hp-text').textContent = Math.max(0,enemy.hp) + '/' + enemy.maxHp + ' HP';
    this.updateHPBar('enemy-hp-bar', enemy.hp, enemy.maxHp);

    document.getElementById('player-name').textContent = player.name;
    document.getElementById('player-level').textContent = 'Lv ' + player.level;
    document.getElementById('player-hp-text').textContent = Math.max(0,player.hp) + '/' + player.maxHp + ' HP';
    this.updateHPBar('player-hp-bar', player.hp, player.maxHp);

    document.getElementById('enemy-sprite').innerHTML = generateSpriteSVG(enemy.templateId);
    document.getElementById('player-sprite').innerHTML = generateSpriteSVG(player.templateId);

    const statusEl = document.getElementById('player-status-effects');
    let statusHTML = (player.statusEffects || []).map(s => '<span class="status-badge">' + s + '</span>').join('');
    if (player.critBoost) statusHTML += '<span class="status-badge" style="background:rgba(251,191,36,0.2);color:#fbbf24">Focus</span>';
    statusEl.innerHTML = statusHTML;

    const logEl = document.getElementById('battle-log');
    logEl.innerHTML = this.battleState.log.map(l => '<p>' + l + '</p>').join('');
    logEl.scrollTop = logEl.scrollHeight;

    document.getElementById('battle-actions').classList.remove('hidden');
    document.getElementById('move-select').classList.add('hidden');
    document.getElementById('prism-select').classList.add('hidden');
    document.getElementById('exchange-panel').classList.add('hidden');
    document.getElementById('inventory-panel').classList.add('hidden');

    const basicCount = this.state.inventory.prism_basic || 0;
    const hyperCount = this.state.inventory.prism_hyper || 0;
    document.getElementById('btn-prism').textContent = '\uD83D\uDD2E Prism (' + (basicCount + hyperCount) + ')';

    const aliveCount = this.state.party.filter(r => r.hp > 0).length;
    document.getElementById('btn-exchange').style.opacity = aliveCount <= 1 ? '0.4' : '1';
  }

  updateHPBar(barId, hp, maxHp) {
    const bar = document.getElementById(barId);
    const pct = Math.max(0, (hp / maxHp) * 100);
    bar.style.width = pct + '%';
    if (pct > 50) bar.style.background = 'linear-gradient(90deg, #4ade80, #22c55e)';
    else if (pct > 25) bar.style.background = 'linear-gradient(90deg, #fbbf24, #f59e0b)';
    else bar.style.background = 'linear-gradient(90deg, #ef4444, #dc2626)';
  }

  addLog(text) { this.battleState.log.push(text); if (this.battleState.log.length > 50) this.battleState.log.shift(); }

  openMoveSelect() {
    const player = this.getActivePlayer();
    const moves = getAvailableMoves(player);
    const panel = document.getElementById('move-select');
    panel.innerHTML = '';
    moves.forEach(m => {
      const btn = document.createElement('button');
      btn.className = 'move-btn';
      const typeClass = m.type === 'cosmos' ? '' : (m.type + '-text');
      btn.innerHTML = '<strong>' + m.name + '</strong><span class="move-type ' + typeClass + '">' + (ELEMENTS[m.type] ? ELEMENTS[m.type].name : 'Cosmos') + ' | Pwr:' + m.power + (m.effect ? ' | Effect' : '') + '</span>';
      const mk = m.key;
      btn.onclick = () => this.executePlayerMove(mk);
      panel.appendChild(btn);
    });
    document.getElementById('battle-actions').classList.add('hidden');
    panel.classList.remove('hidden');
  }

  openPrismSelect() {
    const panel = document.getElementById('prism-select');
    panel.innerHTML = '';
    const basicCount = this.state.inventory.prism_basic || 0;
    const hyperCount = this.state.inventory.prism_hyper || 0;

    if (basicCount > 0) {
      const btn = document.createElement('button');
      btn.className = 'prism-btn';
      btn.innerHTML = '<strong>Harmonizer Prism</strong><span class="move-type">Basic tier | ' + basicCount + ' remaining</span>';
      btn.onclick = () => this.attemptCapture('basic');
      panel.appendChild(btn);
    }
    if (hyperCount > 0) {
      const btn = document.createElement('button');
      btn.className = 'prism-btn';
      btn.innerHTML = '<strong>Hyper-Conductive Prism</strong><span class="move-type">Advanced tier | ' + hyperCount + ' remaining</span>';
      btn.onclick = () => this.attemptCapture('hyper');
      panel.appendChild(btn);
    }
    if (basicCount === 0 && hyperCount === 0) {
      const p = document.createElement('p');
      p.style.cssText = 'grid-column:1/-1;text-align:center;color:#94a3b8;padding:20px;';
      p.textContent = 'No prisms remaining! Visit the shop.';
      panel.appendChild(p);
    }

    const backBtn = document.createElement('button');
    backBtn.className = 'prism-btn';
    backBtn.style.gridColumn = '1 / -1';
    backBtn.textContent = '\u2190 Back';
    backBtn.onclick = () => this.renderBattle();
    panel.appendChild(backBtn);

    document.getElementById('battle-actions').classList.add('hidden');
    panel.classList.remove('hidden');
  }

  openExchangePanel() {
    const panel = document.getElementById('exchange-panel');
    panel.innerHTML = '';
    this.state.party.forEach((r, i) => {
      if (i === this.battleState.playerActiveIdx) return;
      const slot = document.createElement('div');
      slot.className = 'exchange-slot' + (r.hp <= 0 ? ' fainted' : '');
      slot.innerHTML = '<div style="width:40px;height:40px;margin:0 auto 6px">' + generateSpriteSVG(r.templateId, 40) + '</div><strong>' + r.name + '</strong> Lv' + r.level + '<br><span class="' + r.type + '-text" style="font-size:0.7rem">' + (ELEMENTS[r.type] ? ELEMENTS[r.type].name : '') + '</span><br><span style="font-size:0.65rem;color:#94a3b8">HP: ' + Math.max(0,r.hp) + '/' + r.maxHp + '</span>';
      if (r.hp > 0) {
        const ri = i;
        slot.onclick = () => this.exchangeResonant(ri);
      }
      panel.appendChild(slot);
    });

    const backBtn = document.createElement('button');
    backBtn.className = 'prism-btn';
    backBtn.style.gridColumn = '1 / -1';
    backBtn.textContent = '\u2190 Back';
    backBtn.onclick = () => this.renderBattle();
    panel.appendChild(backBtn);

    document.getElementById('battle-actions').classList.add('hidden');
    panel.classList.remove('hidden');
  }

  openInventoryPanel() {
    const panel = document.getElementById('inventory-panel');
    panel.innerHTML = '';
    const inv = this.state.inventory;

    if ((inv.elixir_hp || 0) > 0) {
      const btn = document.createElement('button');
      btn.className = 'inv-item-btn';
      btn.innerHTML = '<strong>Core Restorer</strong><span class="move-type">Heals 50% HP | ' + inv.elixir_hp + ' remaining</span>';
      btn.onclick = () => this.useItem('elixir_hp');
      panel.appendChild(btn);
    }
    if ((inv.elixir_full || 0) > 0) {
      const btn = document.createElement('button');
      btn.className = 'inv-item-btn';
      btn.innerHTML = '<strong>Vital Elixir</strong><span class="move-type">Fully heals HP | ' + inv.elixir_full + ' remaining</span>';
      btn.onclick = () => this.useItem('elixir_full');
      panel.appendChild(btn);
    }
    if ((inv.status_cure || 0) > 0) {
      const btn = document.createElement('button');
      btn.className = 'inv-item-btn';
      btn.innerHTML = '<strong>Resonance Cleanser</strong><span class="move-type">Cures all status | ' + inv.status_cure + ' remaining</span>';
      btn.onclick = () => this.useItem('status_cure');
      panel.appendChild(btn);
    }

    const backBtn = document.createElement('button');
    backBtn.className = 'prism-btn';
    backBtn.style.gridColumn = '1 / -1';
    backBtn.textContent = '\u2190 Back';
    backBtn.onclick = () => this.renderBattle();
    panel.appendChild(backBtn);

    document.getElementById('battle-actions').classList.add('hidden');
    panel.classList.remove('hidden');
  }

  useItem(itemId) {
    const player = this.getActivePlayer();
    if (!player || player.hp <= 0) return;
    switch(itemId) {
      case 'elixir_hp': player.hp = Math.min(player.maxHp, player.hp + Math.floor(player.maxHp * 0.5)); this.addLog(player.name + ' used Core Restorer! HP restored.'); break;
      case 'elixir_full': player.hp = player.maxHp; this.addLog(player.name + ' used Vital Elixir! Fully healed!'); break;
      case 'status_cure': player.statusEffects = []; player.critBoost = false; this.addLog(player.name + "'s status conditions cleared!"); break;
    }
    this.state.inventory[itemId]--;
    this.saveGame();
    this.renderBattle();
    setTimeout(() => this.enemyTurn(), 800);
  }

  async executePlayerMove(moveKey) {
    if (this.isProcessing) return;
    this.isProcessing = true;
    const player = this.getActivePlayer();
    const enemy = this.getActiveEnemy();
    const move = MOVES_DB[moveKey];

    if (move.effect === 'heal30') {
      const healAmt = Math.floor(player.maxHp * 0.3);
      player.hp = Math.min(player.maxHp, player.hp + healAmt);
      this.addLog(player.name + ' used ' + move.name + '! Recovered ' + healAmt + ' HP.');
      this.sound.play('levelup');
      this.renderBattle();
      await this.delay(800);
      this.isProcessing = false;
      this.enemyTurn();
      return;
    }

    const result = calcDamage(player, moveKey, enemy);
    if (!result) {
      this.addLog(player.name + ' used ' + move.name + '!');
      await this.delay(500);
      this.isProcessing = false;
      this.enemyTurn();
      return;
    }

    const playerSprite = document.getElementById('player-sprite');
    playerSprite.classList.add('attack-anim');
    setTimeout(() => playerSprite.classList.remove('attack-anim'), 400);
    await this.delay(300);

    enemy.hp -= result.damage;
    if (result.typeMod >= 2) this.sound.play('supereff');
    else if (result.typeMod <= 0.5) this.sound.play('hit');
    else this.sound.play('hitelem');

    let msg = player.name + ' used ' + move.name + '! Dealt ' + result.damage + ' damage.';
    if (result.isCrit) { msg += ' Critical hit!'; document.getElementById('screen-battle').classList.add('shake'); setTimeout(() => document.getElementById('screen-battle').classList.remove('shake'), 300); }
    if (result.typeMod >= 2) msg += ' Super effective!';
    else if (result.typeMod <= 0.5) msg += ' Not very effective...';
    this.addLog(msg);

    const enemySprite = document.getElementById('enemy-sprite');
    enemySprite.classList.add('flash-hit');
    setTimeout(() => enemySprite.classList.remove('flash-hit'), 200);

    this.renderBattle();
    await this.delay(600);

    if (enemy.hp <= 0) {
      this.addLog(enemy.name + ' fainted!');
      await this.delay(500);
      this.handleEnemyFaint();
    } else {
      this.isProcessing = false;
      this.enemyTurn();
    }
  }

  async attemptCapture(tier) {
    if (this.isProcessing) return;
    this.isProcessing = true;

    const enemy = this.getActiveEnemy();
    const hpRatio = Math.max(0, enemy.hp / enemy.maxHp);
    let catchRate = tier === 'hyper' ? 0.7 : 0.45;
    catchRate *= (1 - hpRatio * 0.8);

    if (tier === 'basic') this.state.inventory.prism_basic--;
    else this.state.inventory.prism_hyper--;
    this.saveGame();

    const overlay = document.getElementById('capture-overlay');
    overlay.classList.remove('hidden');
    const prismEl = document.getElementById('capture-prism');
    prismEl.style.animation = 'none';
    await this.delay(100);
    prismEl.style.animation = '';

    for (let i = 0; i < 3; i++) {
      prismEl.classList.add('capture-shake');
      await this.delay(400);
      prismEl.classList.remove('capture-shake');
      await this.delay(200);
    }

    const caught = Math.random() < catchRate;
    overlay.classList.add('hidden');

    if (caught) {
      this.sound.play('capture');
      this.addLog('Gotcha! ' + enemy.name + ' was captured!');
      const newResonant = createPlayerResonant(enemy.templateId, enemy.level);
      this.state.party.push(newResonant);
      this.state.discoveredResonants.add(enemy.templateId);
      this.saveGame();

      await this.delay(1000);
      this.battleState = null;
      this.advanceNode();
      this.showScreen('map');
    } else {
      this.sound.play('hit');
      this.addLog(enemy.name + ' broke free!');
      await this.delay(800);
      this.renderBattle();
      this.isProcessing = false;
      this.enemyTurn();
    }
  }

  exchangeResonant(idx) {
    const oldActive = this.getActivePlayer();
    if (oldActive.critBoost) oldActive.critBoost = false;
    this.battleState.playerActiveIdx = idx;
    const newActive = this.getActivePlayer();
    this.addLog('Go, ' + newActive.name + '!');
    this.renderBattle();
    setTimeout(() => this.enemyTurn(), 800);
  }

  async enemyTurn() {
    if (this.isProcessing) return;
    this.isProcessing = true;
    const enemy = this.getActiveEnemy();
    const player = this.getActivePlayer();

    if (!enemy || enemy.hp <= 0) { this.isProcessing = false; return; }

    const moves = getAvailableMoves(enemy);
    let bestMove = moves[0];
    let bestDmg = -1;
    for (const mk of moves) {
      const m = MOVES_DB[mk.key];
      if (m.power > 0) {
        const d = calcDamage(enemy, mk.key, player);
        if (d && d.damage > bestDmg) { bestDmg = d.damage; bestMove = mk.key; }
      }
    }

    const move = MOVES_DB[bestMove];
    this.addLog(enemy.name + ' used ' + move.name + '!');

    const enemySprite = document.getElementById('enemy-sprite');
    enemySprite.style.transition = 'transform 0.3s';
    enemySprite.style.transform = 'translateX(-40px)';
    await this.delay(200);
    enemySprite.style.transform = '';

    if (move.power > 0) {
      const result = calcDamage(enemy, bestMove, player);
      if (result) {
        player.hp -= result.damage;
        if (result.typeMod >= 2) this.sound.play('supereff');
        else this.sound.play('hit');

        let msg = 'Dealt ' + result.damage + ' damage.';
        if (result.isCrit) msg += ' Critical hit!';
        if (result.typeMod >= 2) msg += ' Super effective!';
        this.addLog(msg);

        document.getElementById('screen-battle').classList.add('shake');
        setTimeout(() => document.getElementById('screen-battle').classList.remove('shake'), 300);

        const playerSprite = document.getElementById('player-sprite');
        playerSprite.classList.add('flash-hit');
        setTimeout(() => playerSprite.classList.remove('flash-hit'), 200);
      }
    }

    this.renderBattle();
    await this.delay(600);

    if (player.hp <= 0) {
      this.addLog(player.name + ' fainted!');
      await this.delay(500);
      this.handlePlayerFaint();
    } else {
      this.isProcessing = false;
      this.renderBattle();
    }
  }

  handleEnemyFaint() {
    const bs = this.battleState;
    if (bs.currentEnemyIdx < bs.enemies.length - 1) {
      bs.currentEnemyIdx++;
      const nextEnemy = bs.enemies[bs.currentEnemyIdx];
      this.addLog(bs.opponentName + ' sent out ' + nextEnemy.name + '!');
      this.renderBattle();
      setTimeout(() => { this.isProcessing = false; }, 500);
    } else {
      this.handleVictory();
    }
  }

  handlePlayerFaint() {
    const alive = this.state.party.filter(r => r.hp > 0);
    if (alive.length === 0) {
      document.getElementById('defeat-overlay').classList.remove('hidden');
      this.sound.play('defeat');
    } else {
      for (let i = 0; i < this.state.party.length; i++) {
        if (this.state.party[i].hp > 0 && i !== this.battleState.playerActiveIdx) {
          this.battleState.playerActiveIdx = i;
          this.addLog('Go, ' + this.state.party[i].name + '!');
          this.renderBattle();
          setTimeout(() => { this.isProcessing = false; }, 500);
          return;
        }
      }
    }
  }

  handleVictory() {
    const bs = this.battleState;
    const player = this.getActivePlayer();

    let totalXP = 0;
    let shardsReward = 0;
    bs.enemies.forEach(e => {
      const xpGain = Math.floor(15 + e.level * 3);
      totalXP += xpGain;
      shardsReward += Math.floor(8 + e.level * 2);
    });

    if (bs.type === 'boss') {
      shardsReward *= 2;
      this.state.bossesDefeated.push(bs.zoneIdx);
    } else if (bs.type === 'npc') {
      shardsReward = Math.floor(shardsReward * 1.5);
    }

    player.xp += totalXP;
    this.state.shards += shardsReward;

    const msg = 'Victory! Gained ' + totalXP + ' XP and ' + shardsReward + ' Aether Shards!';
    document.getElementById('victory-text').textContent = msg;
    document.getElementById('victory-overlay').classList.remove('hidden');
    this.sound.play('victory');

    while (player.xp >= player.xpToNext) {
      player.xp -= player.xpToNext;
      player.level++;
      player.xpToNext = xpForLevel(player.level);
      const newStats = calcStats(RESONANT_MAP[player.templateId], player.level);
      player.maxHp = newStats.hp;
      player.atk = newStats.atk;
      player.def = newStats.def;
      player.spd = newStats.spd;
      player.hp = Math.min(player.hp + 10, player.maxHp);

      const template = RESONANT_MAP[player.templateId];
      template.moves.forEach(m => { if (m.level <= player.level && !player.moves.includes(m.move)) player.moves.push(m.move); });

      this.addLog(player.name + ' reached Level ' + player.level + '!');
    }

    const template = RESONANT_MAP[player.templateId];
    if (template.evoLevel && player.level >= template.evoLevel) {
      setTimeout(() => this.evolveResonant(bs.playerActiveIdx), 1500);
    } else {
      this.saveGame();
    }

    if (bs.zoneIdx === 3 && bs.type === 'boss') {
      this.state.gameWon = true;
      this.saveGame();
    }
  }

  afterVictory() {
    document.getElementById('victory-overlay').classList.add('hidden');
    if (this.state.gameWon) {
      document.getElementById('gameover-title').textContent = '\uD83C\uDFC6 Victory!';
      document.getElementById('gameover-title').className = 'win';
      document.getElementById('gameover-text').textContent = 'You have defeated the Conductor Supreme and become the greatest Conductor in Aethelgard! Your Resonants sing with joy!';
      this.showScreen('gameover');
    } else {
      this.battleState = null;
      this.advanceNode();
      this.showScreen('map');
    }
  }

  afterDefeat() {
    document.getElementById('defeat-overlay').classList.add('hidden');
    this.state.party.forEach(r => { r.hp = Math.floor(r.maxHp * 0.5); r.statusEffects = []; });
    this.saveGame();
    this.battleState = null;
    this.showScreen('map');
  }

  async evolveResonant(idx) {
    const resonant = this.state.party[idx];
    const template = RESONANT_MAP[resonant.templateId];
    if (!template.evoLevel || resonant.level < template.evoLevel) return;

    const evoTemplate = RESONANT_MAP[template.evoId];
    document.getElementById('evo-text').textContent = resonant.name + ' is evolving!';
    document.getElementById('evo-sprite').innerHTML = generateSpriteSVG(template.evoId, 100);
    document.getElementById('evolution-overlay').classList.remove('hidden');
    this.sound.play('evolve');

    await this.delay(2000);
    document.getElementById('evo-text').textContent = resonant.name + ' evolved into ' + evoTemplate.name + '!';
    document.getElementById('btn-evo-ok').classList.remove('hidden');

    resonant.templateId = template.evoId;
    resonant.name = evoTemplate.name;
    resonant.type = evoTemplate.type;
    const newStats = calcStats(evoTemplate, resonant.level);
    resonant.maxHp = newStats.hp;
    resonant.atk = newStats.atk;
    resonant.def = newStats.def;
    resonant.spd = newStats.spd;
    resonant.hp = Math.min(resonant.hp + 20, resonant.maxHp);
    resonant.moves = evoTemplate.moves.filter(m => m.level <= resonant.level).map(m => m.move);

    this.state.discoveredResonants.add(evoTemplate.id);
    this.saveGame();
  }

  renderShop() {
    document.getElementById('shop-shards').textContent = this.state.shards;
    const grid = document.getElementById('shop-items');
    grid.innerHTML = '';
    SHOP_ITEMS.forEach(item => {
      const el = document.createElement('div');
      el.className = 'shop-item';
      const canBuy = this.state.shards >= item.price;
      el.innerHTML = '<h4>' + item.name + '</h4><p>' + item.desc + '</p><button class="buy-btn" ' + (canBuy ? '' : 'disabled style="opacity:0.4"') + '>💎 ' + item.price + '</button>';
      const btn = el.querySelector('.buy-btn');
      if (!btn.disabled) {
        const iid = item.id;
        const iprice = item.price;
        btn.onclick = () => {
          if (this.state.shards >= iprice) {
            this.state.shards -= iprice;
            this.state.inventory[iid] = (this.state.inventory[iid] || 0) + 1;
            this.saveGame();
            this.sound.play('select');
            this.renderShop();
          }
        };
      }
      grid.appendChild(el);
    });
  }

  renderDex() {
    document.getElementById('dex-count').textContent = this.state.discoveredResonants.size;
    const grid = document.getElementById('dex-grid');
    grid.innerHTML = '';
    RESONANTS_DB.forEach(r => {
      const discovered = this.state.discoveredResonants.has(r.id);
      const el = document.createElement('div');
      el.className = 'dex-entry' + (discovered ? '' : ' dex-undiscovered');
      el.innerHTML = '<div class="dex-icon">' + generateSpriteSVG(r.id, 60) + '</div><h4>' + (discovered ? r.name : '???') + '</h4><p class="' + r.type + '-text">' + (ELEMENTS[r.type] ? ELEMENTS[r.type].name : '') + ' | Base HP:' + r.baseHp + ' ATK:' + r.baseAtk + '</p>';
      grid.appendChild(el);
    });
  }

  renderParty() {
    const grid = document.getElementById('party-grid');
    grid.innerHTML = '';
    this.state.party.forEach((r, i) => {
      const el = document.createElement('div');
      el.className = 'party-slot';
      el.innerHTML = '<div class="party-icon">' + generateSpriteSVG(r.templateId, 50) + '</div><h4>' + r.name + '</h4><p>Lv ' + r.level + ' | HP: ' + Math.max(0,r.hp) + '/' + r.maxHp + '</p><p class="' + r.type + '-text">' + (ELEMENTS[r.type] ? ELEMENTS[r.type].name : '') + '</p>';
      grid.appendChild(el);
    });
  }

  delay(ms) { return new Promise(r => setTimeout(r, ms)); }
}

// ── const: TYPE_CHART ──
const TYPE_CHART = {
  pyro:{pyro:1,hydro:0.5,flora:2,zephyr:1,earth:1,volt:1,cosmos:1},
  hydro:{pyro:2,hydro:1,flora:0.5,zephyr:1,earth:1,volt:1,cosmos:1},
  flora:{pyro:0.5,hydro:2,flora:1,zephyr:1,earth:1,volt:1,cosmos:1},
  zephyr:{pyro:1,hydro:1,flora:1,zephyr:1,earth:2,volt:0.5,cosmos:1},
  earth:{pyro:1,hydro:1,flora:1,zephyr:0.5,earth:1,volt:2,cosmos:1},
  volt:{pyro:1,hydro:1,flora:1,zephyr:2,earth:0.5,volt:1,cosmos:1},
  cosmos:{pyro:1,hydro:1,flora:1,zephyr:1,earth:1,volt:1,cosmos:1}
};

// ── main ──
const game = new GameEngine();
game.init();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"composer","sessionId":"a484a69d-fb7d-4b04-8a6f-2006a32a3742","tokensIn":2327823,"tokensOut":67631,"tokensTotal":2395454,"turns":45,"toolCalls":44,"failedToolCalls":3,"timestamp":"2026-07-16T03:10:32.229Z"} -->