← Aethelgard results

Aethelgard

Qwen 3.6 27b MTP · 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 130,876 bytes · SHA-256 bb056616e3ed
<!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 ── */
* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  width: 100%; height: 100%;
  overflow: hidden;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background: linear-gradient(135deg, #0f0a1e 0%, #1a0a2e 30%, #0d1b2a 60%, #0a0a14 100%);
  color: #e2e8f0;
}

body::before {
  content: '';
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: 
    radial-gradient(ellipse at 20% 50%, rgba(120, 40, 200, 0.08) 0%, transparent 50%),
    radial-gradient(ellipse at 80% 20%, rgba(60, 100, 200, 0.06) 0%, transparent 50%),
    radial-gradient(ellipse at 50% 80%, rgba(40, 180, 120, 0.05) 0%, transparent 50%);
  pointer-events: none;
  z-index: 0;
}

#app {
  position: relative;
  width: 100%; height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}

.game-screen {
  display: none;
  width: 100%; height: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 20px;
  animation: fadeIn 0.4s ease-out;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

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

button {
  font-family: 'Inter', sans-serif;
  cursor: pointer;
  border: none;
  outline: none;
  transition: all 0.2s ease;
}

button:hover:not(:disabled) {
  transform: translateY(-1px);
  filter: brightness(1.1);
}

button:active:not(:disabled) {
  transform: translateY(0);
}

button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
/* ── css: menu ── */
.menu-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 30px;
}

.title-section {
  text-align: center;
  margin-bottom: 20px;
}

.game-title {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(24px, 5vw, 48px);
  background: linear-gradient(135deg, #C77DFF, #4CC9F0, #52B788);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  text-shadow: none;
  letter-spacing: 4px;
  line-height: 1.3;
}

.subtitle {
  font-size: clamp(12px, 2vw, 18px);
  color: rgba(255, 255, 255, 0.5);
  margin-top: 8px;
  letter-spacing: 6px;
  text-transform: uppercase;
}

.menu-buttons {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(300px, 90vw);
}

.menu-btn {
  padding: 14px 28px;
  border-radius: 12px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 1px;
}

.menu-btn.primary {
  background: linear-gradient(135deg, rgba(199, 125, 255, 0.3), rgba(76, 201, 240, 0.3));
  border: 1px solid rgba(199, 125, 255, 0.4);
  color: #fff;
}

.menu-btn.primary:hover {
  background: linear-gradient(135deg, rgba(199, 125, 255, 0.5), rgba(76, 201, 240, 0.5));
  border-color: rgba(199, 125, 255, 0.7);
}

.menu-btn.secondary {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.7);
}

.menu-btn.secondary:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.2);
}

.menu-btn.danger {
  background: rgba(239, 68, 68, 0.15);
  border: 1px solid rgba(239, 68, 68, 0.3);
  color: #fca5a5;
}

.menu-btn.danger:hover {
  background: rgba(239, 68, 68, 0.25);
}

.version-text {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.25);
  margin-top: 30px;
}
/* ── css: map ── */
.map-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: min(700px, 95vw);
  height: min(80vh, 700px);
}

.map-header {
  text-align: center;
  padding: 16px 24px;
}

.map-header h2 {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(10px, 2vw, 16px);
  color: #C77DFF;
  margin-bottom: 8px;
}

.zone-desc {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.5);
  line-height: 1.4;
}

.zone-info {
  display: flex;
  gap: 16px;
  margin-top: 10px;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.4);
}

.map-path {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  padding: 16px;
  max-width: 100%;
  overflow-y: auto;
  flex: 1;
}

.map-node {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 8px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.06);
  min-width: 70px;
  transition: all 0.2s ease;
}

.map-node:not(.locked):hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(199, 125, 255, 0.3);
  transform: translateY(-2px);
}

.map-node.current {
  background: rgba(199, 125, 255, 0.15);
  border-color: rgba(199, 125, 255, 0.4);
  cursor: pointer;
  animation: pulseNode 2s ease-in-out infinite;
}

@keyframes pulseNode {
  0%, 100% { box-shadow: 0 0 0 0 rgba(199, 125, 255, 0.3); }
  50% { box-shadow: 0 0 12px 4px rgba(199, 125, 255, 0.15); }
}

.map-node.completed {
  opacity: 0.6;
}

.map-node.locked {
  opacity: 0.3;
  cursor: not-allowed;
}

.node-icon {
  font-size: 24px;
}

.node-label {
  font-size: 9px;
  color: rgba(255, 255, 255, 0.6);
  text-align: center;
  line-height: 1.2;
}

.map-actions {
  display: flex;
  gap: 8px;
  padding: 12px;
  flex-wrap: wrap;
  justify-content: center;
}

.action-btn {
  padding: 8px 16px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.7);
  font-size: 13px;
}

.action-btn:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.2);
}

.back-btn {
  margin-top: 16px;
}
/* ── css: battle ── */
.battle-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: min(700px, 95vw);
  height: min(85vh, 800px);
}

.enemy-side, .player-side {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0 10px;
  gap: 12px;
}

.enemy-side { flex-direction: row-reverse; }

.resonant-card {
  min-width: 180px;
  max-width: 240px;
  padding: 14px;
}

.resonant-info {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.resonant-info h3 {
  font-size: 15px;
  font-weight: 600;
}

.level-badge {
  font-size: 11px;
  background: rgba(255, 255, 255, 0.1);
  padding: 2px 8px;
  border-radius: 10px;
  color: rgba(255, 255, 255, 0.6);
}

.status-badge {
  font-size: 10px;
  background: rgba(239, 68, 68, 0.2);
  border: 1px solid rgba(239, 68, 68, 0.4);
  padding: 2px 6px;
  border-radius: 6px;
  color: #fca5a5;
}

.hp-bar-container {
  width: 100%;
  height: 8px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  overflow: hidden;
}

.hp-bar {
  height: 100%;
  background: var(--hp-color, #22c55e);
  border-radius: 4px;
  transition: width 0.5s ease, background 0.3s ease;
}

.xp-bar-container {
  width: 100%;
  height: 4px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 2px;
  overflow: hidden;
  margin-top: 4px;
}

.xp-bar {
  height: 100%;
  background: linear-gradient(90deg, #C77DFF, #4CC9F0);
  border-radius: 2px;
  transition: width 0.5s ease;
}

.hp-text {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.4);
  margin-top: 4px;
  text-align: right;
}

.stats-row {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.3);
  margin-top: 4px;
}

.resonant-sprite {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 100px;
}

.enemy-sprite svg, .player-sprite svg {
  filter: drop-shadow(0 0 20px rgba(199, 125, 255, 0.3));
  animation: floatSprite 3s ease-in-out infinite;
}

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

.battle-log {
  width: 100%;
  min-height: 80px;
  max-height: 120px;
  overflow-y: auto;
  padding: 14px;
}

.battle-msg {
  font-size: 13px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.7);
  animation: msgFadeIn 0.3s ease-out;
}

@keyframes msgFadeIn {
  from { opacity: 0; transform: translateX(-10px); }
  to { opacity: 1; transform: translateX(0); }
}

.battle-actions {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.action-tabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.tab-btn {
  padding: 8px 14px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.7);
  font-size: 13px;
}

.tab-btn.active {
  background: rgba(199, 125, 255, 0.2);
  border-color: rgba(199, 125, 255, 0.4);
  color: #fff;
}

.action-panel {
  display: none;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px;
}

.action-panel.active {
  display: flex;
}

.move-btn {
  flex: 1 1 calc(50% - 6px);
  min-width: 140px;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(var(--type-rgb, 199, 125, 255), 0.1);
  border: 1px solid var(--type-color, rgba(199, 125, 255, 0.3));
  color: #fff;
  text-align: left;
}

.move-btn:hover {
  background: rgba(var(--type-rgb, 199, 125, 255), 0.2);
}

.move-name {
  display: block;
  font-size: 13px;
  font-weight: 600;
}

.move-info {
  display: block;
  font-size: 10px;
  color: rgba(255, 255, 255, 0.4);
  margin-top: 2px;
}

.swap-btn, .inv-btn, .prism-btn {
  padding: 8px 14px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.7);
  font-size: 12px;
  text-align: left;
}

.swap-btn:hover:not(:disabled), .inv-btn:hover, .prism-btn:hover {
  background: rgba(255, 255, 255, 0.12);
}

.mini-hp-bar {
  width: 100%;
  height: 3px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 2px;
  overflow: hidden;
  margin-top: 4px;
}

.mini-hp-bar div {
  height: 100%;
  background: #22c55e;
  border-radius: 2px;
}

.waiting-text {
  text-align: center;
  color: rgba(255, 255, 255, 0.3);
  font-style: italic;
  padding: 16px;
}

/* Shake animation for damage */
.shake {
  animation: shakeAnim 0.3s ease-in-out;
}

@keyframes shakeAnim {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-8px) rotate(-1deg); }
  40% { transform: translateX(6px) rotate(1deg); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(2px); }
}

/* Flash effect for hits */
@keyframes flashHit {
  0% { filter: brightness(1); }
  50% { filter: brightness(3) saturate(0.5); }
  100% { filter: brightness(1); }
}

.hit-flash {
  animation: flashHit 0.2s ease-out;
}
/* ── css: screens ── */
/* Party Screen */
.party-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: min(700px, 95vw);
  height: min(85vh, 800px);
  overflow-y: auto;
  padding: 20px;
}

.party-container h2 {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(10px, 2vw, 14px);
  color: #C77DFF;
}

.party-grid {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}

.party-card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px;
}

.party-sprite svg {
  filter: drop-shadow(0 0 12px rgba(199, 125, 255, 0.3));
}

.party-info {
  flex: 1;
}

.party-info h3 {
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.type-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 6px;
  font-size: 10px;
  background: var(--badge-color, rgba(255,255,255,0.1));
  color: #fff;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.4);
  margin-top: 6px;
}

.moves-list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 8px;
}

.move-tag {
  font-size: 10px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.5);
}

.move-tag.unlocked {
  background: rgba(199, 125, 255, 0.15);
  color: rgba(255, 255, 255, 0.8);
}

.inventory-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  width: 100%;
}

.inv-item {
  padding: 6px 12px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.06);
  font-size: 12px;
  color: rgba(255, 255, 255, 0.6);
}

/* Shop Screen */
.shop-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: min(700px, 95vw);
  height: min(85vh, 800px);
  overflow-y: auto;
  padding: 20px;
}

.shop-container h2 {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(10px, 2vw, 14px);
  color: #FFD700;
}

.shards-display {
  font-size: 16px;
  color: #FFD700;
  font-weight: 600;
}

.shop-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.shop-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
}

.shop-info h4 {
  font-size: 14px;
  margin-bottom: 4px;
}

.shop-info p {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.4);
}

.buy-btn {
  padding: 8px 16px;
  border-radius: 8px;
  background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 165, 0, 0.2));
  border: 1px solid rgba(255, 215, 0, 0.3);
  color: #FFD700;
  font-size: 12px;
  white-space: nowrap;
}

.buy-btn:hover:not(:disabled) {
  background: linear-gradient(135deg, rgba(255, 215, 0, 0.3), rgba(255, 165, 0, 0.3));
}

/* Dex Screen */
.dex-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: min(700px, 95vw);
  height: min(85vh, 800px);
  overflow-y: auto;
  padding: 20px;
}

.dex-container h2 {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(10px, 2vw, 14px);
  color: #52B788;
}

.dex-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 10px;
  width: 100%;
}

.dex-card {
  text-align: center;
  padding: 14px;
}

.dex-card.hidden {
  opacity: 0.3;
}

.dex-sprite svg {
  filter: drop-shadow(0 0 8px rgba(82, 183, 136, 0.3));
}

.dex-hidden {
  font-family: 'Press Start 2P', monospace;
  font-size: 24px;
  color: rgba(255, 255, 255, 0.1);
  padding: 20px;
}

.dex-card h4 {
  font-size: 13px;
  margin-top: 8px;
}

.dex-stats {
  font-size: 10px;
  color: rgba(255, 255, 255, 0.3);
  margin-top: 6px;
}

/* Victory Screen */
.victory-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  text-align: center;
}

.victory-container h1 {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(16px, 3vw, 28px);
  background: linear-gradient(135deg, #FFD700, #C77DFF, #4CC9F0);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.victory-container p {
  font-size: 15px;
  color: rgba(255, 255, 255, 0.6);
  max-width: 400px;
  line-height: 1.5;
}

.victory-stats {
  text-align: left;
  padding: 20px;
}

.victory-stats h3 {
  font-size: 14px;
  color: #C77DFF;
  margin-bottom: 12px;
}

.victory-stats p {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.5);
  margin-bottom: 6px;
}

/* Notification */
.notification {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  padding: 12px 24px;
  border-radius: 12px;
  background: rgba(32, 32, 64, 0.9);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(199, 125, 255, 0.3);
  color: #fff;
  font-size: 14px;
  z-index: 1000;
  animation: notifSlideIn 0.3s ease-out, notifFadeOut 0.3s ease-in 2s forwards;
}

@keyframes notifSlideIn {
  from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

@keyframes notifFadeOut {
  from { opacity: 1; }
  to { opacity: 0; pointer-events: none; }
}

/* Responsive adjustments */
@media (max-width: 600px) {
  .enemy-side, .player-side {
    flex-direction: column;
    align-items: center;
  }
  
  .resonant-card {
    min-width: unset;
    width: 100%;
  }
  
  .move-btn {
    flex: 1 1 100%;
  }
}
/* ── css: particles ── */
/* Particle effects container */
.particles-container {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  pointer-events: none;
  z-index: 999;
  overflow: hidden;
}

.status-particle {
  position: absolute;
  border-radius: 50%;
  animation: particleFloat 2s ease-out forwards;
  opacity: 0.7;
}

@keyframes particleFloat {
  0% { transform: translateY(0) scale(1); opacity: 0.7; }
  100% { transform: translateY(-60px) scale(0.3); opacity: 0; }
}

/* Burn particles - orange/red */
.particle-burn { background: radial-gradient(circle, #FF6B35, transparent); width: 8px; height: 8px; }

/* Paralyze particles - yellow */
.particle-paralyze { background: radial-gradient(circle, #FFD700, transparent); width: 6px; height: 6px; }

/* Poison particles - green */
.particle-poison { background: radial-gradient(circle, #22c55e, transparent); width: 7px; height: 7px; }

/* Sleep particles - purple */
.particle-sleep { background: radial-gradient(circle, #C77DFF, transparent); width: 5px; height: 5px; }

/* Ambient floating particles in background */
.ambient-particle {
  position: fixed;
  border-radius: 50%;
  pointer-events: none;
  animation: ambientFloat linear infinite;
}

@keyframes ambientFloat {
  0% { transform: translateY(100vh) rotate(0deg); opacity: 0; }
  10% { opacity: 0.3; }
  90% { opacity: 0.3; }
  100% { transform: translateY(-20px) rotate(360deg); opacity: 0; }
}

/* Evolution animation */
@keyframes evolveFlash {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(2.5) saturate(1.5); }
}

.evolve-flash {
  animation: evolveFlash 0.8s ease-in-out 3;
}

/* Capture success glow */
@keyframes captureGlow {
  0%, 100% { box-shadow: 0 0 20px rgba(199, 125, 255, 0.3); }
  50% { box-shadow: 0 0 40px rgba(199, 125, 255, 0.6), 0 0 60px rgba(76, 201, 240, 0.3); }
}

.capture-glow {
  animation: captureGlow 1s ease-in-out;
}

/* Level up text animation */
@keyframes levelUpText {
  0% { transform: scale(0.5) translateY(10px); opacity: 0; }
  50% { transform: scale(1.2) translateY(-5px); opacity: 1; }
  100% { transform: scale(1) translateY(0); opacity: 0; }
}

.level-up-text {
  position: absolute;
  font-family: 'Press Start 2P', monospace;
  font-size: 14px;
  color: #FFD700;
  text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
  animation: levelUpText 1.5s ease-out forwards;
  pointer-events: none;
}

/* Zone transition overlay */
.zone-transition {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  animation: fadeIn 0.5s ease-out;
}

.zone-transition h2 {
  font-family: 'Press Start 2P', monospace;
  font-size: clamp(12px, 3vw, 24px);
  color: #C77DFF;
  text-align: center;
}

/* Improved HP bar glow */
.hp-bar-container {
  position: relative;
}

.hp-bar::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
  animation: hpShimmer 2s ease-in-out infinite;
}

@keyframes hpShimmer {
  0%, 100% { transform: translateX(-100%); }
  50% { transform: translateX(100%); }
}

/* Boss node special styling */
.map-node[data-index]:has(.node-icon) .node-icon {
  font-size: 28px;
}

.map-node.current .node-icon {
  animation: bossPulse 1.5s ease-in-out infinite;
}

@keyframes bossPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.2); }
}

/* Improved glass card hover */
.glass-card:hover {
  border-color: rgba(255, 255, 255, 0.12);
}
</style>
</head>
<body>
<div id="app"></div>
<div class="notification" style="display:none"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./engine/game":"src/engine/game.ts","./ui/renderer":"src/ui/renderer.ts"},"src/data/resonants.ts":{"../types":"src/types.ts"},"src/data/zones.ts":{"../types":"src/types.ts"},"src/state/player.ts":{"../types":"src/types.ts","../data/resonants":"src/data/resonants.ts"},"src/engine/combat.ts":{"../types":"src/types.ts","../data/resonants":"src/data/resonants.ts","../state/player":"src/state/player.ts"},"src/ui/renderer.ts":{"../types":"src/types.ts","../data/resonants":"src/data/resonants.ts"},"src/engine/game.ts":{"../types":"src/types.ts","../data/zones":"src/data/zones.ts","../data/resonants":"src/data/resonants.ts","../state/player":"src/state/player.ts","./sound":"src/engine/sound.ts","./combat":"src/engine/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 game_1 = require("./engine/game");
// Expose renderer functions for game engine to use
const Renderer = __importStar(require("./ui/renderer"));
window.__generateResonantSVG = Renderer.generateResonantSVG;
// Start the game when DOM is ready
(0, game_1.initGame)();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
};

// ── module: src/data/resonants.ts ──
__mods["src/data/resonants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ALL_RESONANT_IDS = exports.RESONANT_DB = void 0;
exports.getResonantById = getResonantById;
const pyroMoves = [
    { name: 'Ember Toss', power: 40, accuracy: 100, element: 'Pyro', unlockLevel: 1 },
    { name: 'Flame Burst', power: 65, accuracy: 95, element: 'Pyro', unlockLevel: 3 },
    { name: 'Inferno Wave', power: 90, accuracy: 85, element: 'Pyro', unlockLevel: 7 },
    { name: 'Solar Flare', power: 120, accuracy: 70, element: 'Pyro', unlockLevel: 13 },
];
const hydroMoves = [
    { name: 'Splash Strike', power: 40, accuracy: 100, element: 'Hydro', unlockLevel: 1 },
    { name: 'Tidal Surge', power: 65, accuracy: 95, element: 'Hydro', unlockLevel: 3 },
    { name: 'Aqua Cannon', power: 90, accuracy: 85, element: 'Hydro', unlockLevel: 7 },
    { name: 'Tsunami Blast', power: 120, accuracy: 70, element: 'Hydro', unlockLevel: 13 },
];
const floraMoves = [
    { name: 'Vine Whip', power: 40, accuracy: 100, element: 'Flora', unlockLevel: 1 },
    { name: 'Thorn Barrage', power: 65, accuracy: 95, element: 'Flora', unlockLevel: 3 },
    { name: 'Root Snare', power: 80, accuracy: 90, element: 'Flora', unlockLevel: 7 },
    { name: 'Verdant Storm', power: 120, accuracy: 70, element: 'Flora', unlockLevel: 13 },
];
const voltMoves = [
    { name: 'Spark Zap', power: 40, accuracy: 100, element: 'Volt', unlockLevel: 1 },
    { name: 'Thunder Arc', power: 65, accuracy: 95, element: 'Volt', unlockLevel: 3 },
    { name: 'Lightning Bolt', power: 90, accuracy: 85, element: 'Volt', unlockLevel: 7 },
    { name: 'Thunderstorm', power: 120, accuracy: 70, element: 'Volt', unlockLevel: 13 },
];
const zephyrMoves = [
    { name: 'Gust Breeze', power: 40, accuracy: 100, element: 'Zephyr', unlockLevel: 1 },
    { name: 'Wind Slash', power: 65, accuracy: 95, element: 'Zephyr', unlockLevel: 3 },
    { name: 'Cyclone Spin', power: 85, accuracy: 90, element: 'Zephyr', unlockLevel: 7 },
    { name: 'Tempest Fury', power: 120, accuracy: 70, element: 'Zephyr', unlockLevel: 13 },
];
const earthMoves = [
    { name: 'Rock Throw', power: 40, accuracy: 100, element: 'Earth', unlockLevel: 1 },
    { name: 'Stone Slide', power: 65, accuracy: 95, element: 'Earth', unlockLevel: 3 },
    { name: 'Quake Slam', power: 85, accuracy: 90, element: 'Earth', unlockLevel: 7 },
    { name: 'Mountain Crush', power: 120, accuracy: 70, element: 'Earth', unlockLevel: 13 },
];
const cosmosMoves = [
    { name: 'Void Pulse', power: 45, accuracy: 100, element: 'Cosmos', unlockLevel: 1 },
    { name: 'Star Beam', power: 70, accuracy: 95, element: 'Cosmos', unlockLevel: 3 },
    { name: 'Nebula Burst', power: 85, accuracy: 90, element: 'Cosmos', unlockLevel: 7 },
    { name: 'Supernova', power: 120, accuracy: 70, element: 'Cosmos', unlockLevel: 13 },
];
const physicalMoves = [
    { name: 'Tackle', power: 40, accuracy: 100, element: 'Cosmos', unlockLevel: 1 },
    { name: 'Quick Strike', power: 55, accuracy: 95, element: 'Cosmos', unlockLevel: 3 },
    { name: 'Power Slam', power: 75, accuracy: 90, element: 'Cosmos', unlockLevel: 6 },
];
exports.RESONANT_DB = {
    // === PYRO LINE ===
    emberkit: {
        id: 'emberkit', name: 'Emberkit', type: 'Pyro',
        baseHp: 45, baseAtk: 52, baseDef: 38, baseSpd: 48,
        evolutionLevel: 8, evolutionId: 'pyrowolf',
        moves: [pyroMoves[0], pyroMoves[1], physicalMoves[1]],
        spriteParams: { bodyShape: 'round', primaryColor: '#FF6B35', secondaryColor: '#FF9F1C', accentColor: '#FFE082', glowColor: '#FF4500', features: ['small-tail-flame', 'pointy-ears'] },
    },
    pyrowolf: {
        id: 'pyrowolf', name: 'Pyrowolf', type: 'Pyro',
        baseHp: 72, baseAtk: 78, baseDef: 56, baseSpd: 68,
        evolutionLevel: 15, evolutionId: 'ignisaur',
        moves: [pyroMoves[0], pyroMoves[1], pyroMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#E63946', secondaryColor: '#FF6B35', accentColor: '#FFE082', glowColor: '#FF4500', features: ['wolf-snout', 'flame-manes'] },
    },
    ignisaur: {
        id: 'ignisaur', name: 'Ignisaur', type: 'Pyro',
        baseHp: 105, baseAtk: 110, baseDef: 82, baseSpd: 95,
        moves: [pyroMoves[0], pyroMoves[1], pyroMoves[2], pyroMoves[3]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#D62828', secondaryColor: '#E63946', accentColor: '#FFB703', glowColor: '#FF4500', features: ['dragon-horns', 'flame-crown'] },
    },
    // === HYDRO LINE ===
    droplet: {
        id: 'droplet', name: 'Droplet', type: 'Hydro',
        baseHp: 50, baseAtk: 42, baseDef: 48, baseSpd: 55,
        evolutionLevel: 8, evolutionId: 'geoxus',
        moves: [hydroMoves[0], hydroMoves[1], physicalMoves[0]],
        spriteParams: { bodyShape: 'round', primaryColor: '#4CC9F0', secondaryColor: '#4361EE', accentColor: '#A8DADC', glowColor: '#00BFFF', features: ['water-droplet-body', 'big-eyes'] },
    },
    geoxus: {
        id: 'geoxus', name: 'Geoxus', type: 'Hydro',
        baseHp: 78, baseAtk: 65, baseDef: 72, baseSpd: 58,
        evolutionLevel: 15, evolutionId: 'neptunian',
        moves: [hydroMoves[0], hydroMoves[1], hydroMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'elongated', primaryColor: '#3A86FF', secondaryColor: '#4CC9F0', accentColor: '#A8DADC', glowColor: '#00BFFF', features: ['serpent-body', 'water-crest'] },
    },
    neptunian: {
        id: 'neptunian', name: 'Neptunian', type: 'Hydro',
        baseHp: 115, baseAtk: 98, baseDef: 100, baseSpd: 72,
        moves: [hydroMoves[0], hydroMoves[1], hydroMoves[2], hydroMoves[3]],
        spriteParams: { bodyShape: 'elongated', primaryColor: '#1D3557', secondaryColor: '#457B9D', accentColor: '#A8DADC', glowColor: '#00BFFF', features: ['sea-king-crown', 'trident-tail'] },
    },
    // === FLORA LINE ===
    seedling: {
        id: 'seedling', name: 'Seedling', type: 'Flora',
        baseHp: 48, baseAtk: 40, baseDef: 52, baseSpd: 35,
        evolutionLevel: 8, evolutionId: 'thornback',
        moves: [floraMoves[0], floraMoves[1], physicalMoves[0]],
        spriteParams: { bodyShape: 'round', primaryColor: '#2D6A4F', secondaryColor: '#52B788', accentColor: '#95D5B2', glowColor: '#00FF7F', features: ['leaf-sprout', 'bulb-body'] },
    },
    thornback: {
        id: 'thornback', name: 'Thornback', type: 'Flora',
        baseHp: 80, baseAtk: 68, baseDef: 78, baseSpd: 45,
        evolutionLevel: 15, evolutionId: 'bloomgoliath',
        moves: [floraMoves[0], floraMoves[1], floraMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#1B4332', secondaryColor: '#2D6A4F', accentColor: '#74C69D', glowColor: '#00FF7F', features: ['thorn-armor', 'flower-back'] },
    },
    bloomgoliath: {
        id: 'bloomgoliath', name: 'Bloomgoliath', type: 'Flora',
        baseHp: 125, baseAtk: 95, baseDef: 110, baseSpd: 55,
        moves: [floraMoves[0], floraMoves[1], floraMoves[2], floraMoves[3]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#081C15', secondaryColor: '#1B4332', accentColor: '#B7E4C7', glowColor: '#00FF7F', features: ['giant-blossom', 'root-legs'] },
    },
    // === VOLT LINE ===
    sparky: {
        id: 'sparky', name: 'Sparky', type: 'Volt',
        baseHp: 42, baseAtk: 50, baseDef: 35, baseSpd: 62,
        evolutionLevel: 8, evolutionId: 'boltsaber',
        moves: [voltMoves[0], voltMoves[1], physicalMoves[1]],
        spriteParams: { bodyShape: 'round', primaryColor: '#FFD700', secondaryColor: '#FFA500', accentColor: '#FFFACD', glowColor: '#FFFF00', features: ['lightning-tail', 'electric-cheeks'] },
    },
    boltsaber: {
        id: 'boltsaber', name: 'Boltsaber', type: 'Volt',
        baseHp: 70, baseAtk: 82, baseDef: 55, baseSpd: 80,
        evolutionLevel: 15, evolutionId: 'stormlord',
        moves: [voltMoves[0], voltMoves[1], voltMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#FFC300', secondaryColor: '#FFD700', accentColor: '#FFFACD', glowColor: '#FFFF00', features: ['sabertooth', 'bolt-armor'] },
    },
    stormlord: {
        id: 'stormlord', name: 'Stormlord', type: 'Volt',
        baseHp: 108, baseAtk: 115, baseDef: 78, baseSpd: 105,
        moves: [voltMoves[0], voltMoves[1], voltMoves[2], voltMoves[3]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#E6B800', secondaryColor: '#FFC300', accentColor: '#FFFACD', glowColor: '#FFFF00', features: ['storm-crown', 'lightning-wings'] },
    },
    // === ZEPHYR LINE ===
    gusty: {
        id: 'gusty', name: 'Gusty', type: 'Zephyr',
        baseHp: 44, baseAtk: 45, baseDef: 38, baseSpd: 60,
        evolutionLevel: 8, evolutionId: 'slipstream',
        moves: [zephyrMoves[0], zephyrMoves[1], physicalMoves[0]],
        spriteParams: { bodyShape: 'elongated', primaryColor: '#A2D2FF', secondaryColor: '#BDE0FE', accentColor: '#FFFFFF', glowColor: '#87CEEB', features: ['wind-wings', 'feather-body'] },
    },
    slipstream: {
        id: 'slipstream', name: 'Slipstream', type: 'Zephyr',
        baseHp: 74, baseAtk: 72, baseDef: 58, baseSpd: 82,
        evolutionLevel: 15, evolutionId: 'zephyrus',
        moves: [zephyrMoves[0], zephyrMoves[1], zephyrMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'elongated', primaryColor: '#74C0FC', secondaryColor: '#A2D2FF', accentColor: '#FFFFFF', glowColor: '#87CEEB', features: ['streamlined-body', 'wind-trails'] },
    },
    zephyrus: {
        id: 'zephyrus', name: 'Zephyrus', type: 'Zephyr',
        baseHp: 102, baseAtk: 105, baseDef: 75, baseSpd: 118,
        moves: [zephyrMoves[0], zephyrMoves[1], zephyrMoves[2], zephyrMoves[3]],
        spriteParams: { bodyShape: 'elongated', primaryColor: '#4A90D9', secondaryColor: '#74C0FC', accentColor: '#FFFFFF', glowColor: '#87CEEB', features: ['sky-king-halo', 'tempest-wings'] },
    },
    // === EARTH LINE ===
    pebble: {
        id: 'pebble', name: 'Pebble', type: 'Earth',
        baseHp: 52, baseAtk: 38, baseDef: 55, baseSpd: 28,
        evolutionLevel: 8, evolutionId: 'obsidian',
        moves: [earthMoves[0], earthMoves[1], physicalMoves[0]],
        spriteParams: { bodyShape: 'round', primaryColor: '#8B7355', secondaryColor: '#A0926B', accentColor: '#D4C5A9', glowColor: '#CD853F', features: ['rock-body', 'crystal-eyes'] },
    },
    obsidian: {
        id: 'obsidian', name: 'Obsidian', type: 'Earth',
        baseHp: 82, baseAtk: 70, baseDef: 85, baseSpd: 38,
        evolutionLevel: 15, evolutionId: 'terrasunder',
        moves: [earthMoves[0], earthMoves[1], earthMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#4A4E69', secondaryColor: '#8B7355', accentColor: '#C9ADA7', glowColor: '#CD853F', features: ['crystal-armor', 'stone-claws'] },
    },
    terrasunder: {
        id: 'terrasunder', name: 'Terrasunder', type: 'Earth',
        baseHp: 120, baseAtk: 98, baseDef: 125, baseSpd: 48,
        moves: [earthMoves[0], earthMoves[1], earthMoves[2], earthMoves[3]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#2B2D42', secondaryColor: '#4A4E69', accentColor: '#D4A574', glowColor: '#CD853F', features: ['mountain-horns', 'earth-shield'] },
    },
    // === BOSS RESONANTS (Cosmos type) ===
    canopy_guardian: {
        id: 'canopy_guardian', name: 'Canopy Guardian', type: 'Flora',
        baseHp: 130, baseAtk: 85, baseDef: 90, baseSpd: 60,
        moves: [floraMoves[2], floraMoves[3], cosmosMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#1B4332', secondaryColor: '#52B788', accentColor: '#FFD700', glowColor: '#00FF7F', features: ['ancient-tree-form', 'golden-eyes'] },
    },
    grotto_sentinel: {
        id: 'grotto_sentinel', name: 'Grotto Sentinel', type: 'Hydro',
        baseHp: 145, baseAtk: 90, baseDef: 100, baseSpd: 55,
        moves: [hydroMoves[2], hydroMoves[3], cosmosMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'elongated', primaryColor: '#1D3557', secondaryColor: '#4CC9F0', accentColor: '#FFD700', glowColor: '#00BFFF', features: ['crystal-armor', 'water-crown'] },
    },
    obsidian_lord: {
        id: 'obsidian_lord', name: 'Obsidian Lord', type: 'Pyro',
        baseHp: 155, baseAtk: 105, baseDef: 95, baseSpd: 70,
        moves: [pyroMoves[2], pyroMoves[3], cosmosMoves[2], physicalMoves[2]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#D62828', secondaryColor: '#4A4E69', accentColor: '#FFD700', glowColor: '#FF4500', features: ['lava-veins', 'obsidian-crown'] },
    },
    apex_conductor: {
        id: 'apex_conductor', name: 'Apex Conductor', type: 'Cosmos',
        baseHp: 180, baseAtk: 125, baseDef: 110, baseSpd: 95,
        moves: [cosmosMoves[2], cosmosMoves[3], pyroMoves[3], hydroMoves[3]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#7B2D8E', secondaryColor: '#C77DFF', accentColor: '#FFFFFF', glowColor: '#BF00FF', features: ['void-crown', 'cosmic-aura'] },
    },
    // === WANDERING CONDUCTOR RESONANTS (special variants) ===
    wild_pyrowolf: {
        id: 'wild_pyrowolf', name: 'Pyrowolf', type: 'Pyro',
        baseHp: 72, baseAtk: 78, baseDef: 56, baseSpd: 68,
        moves: [pyroMoves[0], pyroMoves[1], pyroMoves[2]],
        spriteParams: { bodyShape: 'angular', primaryColor: '#E63946', secondaryColor: '#FF6B35', accentColor: '#FFE082', glowColor: '#FF4500', features: ['wolf-snout', 'flame-manes'] },
    },
};
function getResonantById(id) {
    return exports.RESONANT_DB[id];
}
exports.ALL_RESONANT_IDS = Object.keys(exports.RESONANT_DB);
};

// ── module: src/data/zones.ts ──
__mods["src/data/zones.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SHOP_ITEMS = exports.CACHE_REWARDS = exports.WILD_ENCOUNTER_TABLES = exports.BOSS_IDS = exports.CONDUCTOR_ROSTERS = exports.ZONES = void 0;
exports.ZONES = [
    {
        id: 'verdant_canopy',
        name: 'The Verdant Canopy',
        description: 'A lush forest canopy where Flora and Zephyr Resonants thrive among ancient trees.',
        levelRange: [1, 5],
        bgColor: '#0a2e1f',
        accentColor: '#52B788',
        nodes: [
            { type: 'wild', label: 'Tall Grass' },
            { type: 'cache', label: 'Mystery Cache' },
            { type: 'spring', label: 'Healing Spring' },
            { type: 'wild', label: 'Dense Thicket' },
            { type: 'conductor', label: 'Conductor Elara' },
            { type: 'wild', label: 'Flower Meadow' },
            { type: 'cache', label: 'Hidden Cache' },
            { type: 'spring', label: 'Crystal Spring' },
            { type: 'wild', label: 'Ancient Grove' },
            { type: 'conductor', label: 'Conductor Finn' },
            { type: 'wild', label: 'Canopy Edge' },
            { type: 'boss', label: '🌿 Canopy Guardian' },
        ],
    },
    {
        id: 'sunken_grotto',
        name: 'The Sunken Grotto',
        description: 'Underground caverns filled with underground rivers and crystalline formations.',
        levelRange: [5, 10],
        bgColor: '#0a1628',
        accentColor: '#4CC9F0',
        nodes: [
            { type: 'wild', label: 'Crystal Cavern' },
            { type: 'spring', label: 'Underground Spring' },
            { type: 'cache', label: 'Grotto Cache' },
            { type: 'conductor', label: 'Conductor Mira' },
            { type: 'wild', label: 'Deep Pool' },
            { type: 'wild', label: 'Stalactite Hall' },
            { type: 'cache', label: 'Crystal Cache' },
            { type: 'spring', label: 'Healing Waters' },
            { type: 'conductor', label: 'Conductor Rex' },
            { type: 'wild', label: 'Abyssal Chamber' },
            { type: 'wild', label: 'Echoing Depths' },
            { type: 'boss', label: '💧 Grotto Sentinel' },
        ],
    },
    {
        id: 'obsidian_crags',
        name: 'The Obsidian Crags',
        description: 'Volcanic peaks where Pyro and Volt Resonants dance among lightning storms.',
        levelRange: [10, 15],
        bgColor: '#1a0a0a',
        accentColor: '#FF6B35',
        nodes: [
            { type: 'wild', label: 'Lava Fields' },
            { type: 'cache', label: 'Volcanic Cache' },
            { type: 'conductor', label: 'Conductor Zara' },
            { type: 'spring', label: 'Hot Spring' },
            { type: 'wild', label: 'Thunder Ridge' },
            { type: 'wild', label: 'Obsidian Path' },
            { type: 'cache', label: 'Magma Cache' },
            { type: 'conductor', label: 'Conductor Kai' },
            { type: 'spring', label: 'Steam Spring' },
            { type: 'wild', label: 'Storm Peak' },
            { type: 'wild', label: 'Crater Edge' },
            { type: 'boss', label: '🔥 Obsidian Lord' },
        ],
    },
    {
        id: 'apex_pillar',
        name: 'The Apex Pillar',
        description: 'A cosmic tower reaching into the void. Only the strongest Conductors dare enter.',
        levelRange: [15, 20],
        bgColor: '#0d0a1f',
        accentColor: '#C77DFF',
        nodes: [
            { type: 'wild', label: 'Void Chamber I' },
            { type: 'spring', label: 'Cosmic Spring' },
            { type: 'conductor', label: 'Conductor Nova' },
            { type: 'wild', label: 'Star Hall' },
            { type: 'cache', label: 'Astral Cache' },
            { type: 'conductor', label: 'Conductor Orion' },
            { type: 'wild', label: 'Nebula Room' },
            { type: 'spring', label: 'Void Spring' },
            { type: 'conductor', label: 'Conductor Lyra' },
            { type: 'wild', label: 'Event Horizon' },
            { type: 'boss', label: '🌌 Apex Conductor' },
        ],
    },
];
exports.CONDUCTOR_ROSTERS = [
    // Zone 0 conductors (Elara, Finn)
    [{ templateId: 'seedling', level: 3 }, { templateId: 'gusty', level: 4 }],
    [{ templateId: 'thornback', level: 5 }, { templateId: 'slipstream', level: 5 }],
    // Zone 1 conductors (Mira, Rex)
    [{ templateId: 'droplet', level: 7 }, { templateId: 'pebble', level: 6 }],
    [{ templateId: 'geoxus', level: 9 }, { templateId: 'obsidian', level: 8 }],
    // Zone 2 conductors (Zara, Kai)
    [{ templateId: 'emberkit', level: 11 }, { templateId: 'sparky', level: 10 }],
    [{ templateId: 'pyrowolf', level: 13 }, { templateId: 'boltsaber', level: 12 }],
    // Zone 3 conductors (Nova, Orion, Lyra)
    [{ templateId: 'ignisaur', level: 15 }, { templateId: 'neptunian', level: 14 }],
    [{ templateId: 'stormlord', level: 16 }, { templateId: 'zephyrus', level: 15 }],
    [{ templateId: 'bloomgoliath', level: 17 }, { templateId: 'terrasunder', level: 16 }],
];
exports.BOSS_IDS = ['canopy_guardian', 'grotto_sentinel', 'obsidian_lord', 'apex_conductor'];
exports.WILD_ENCOUNTER_TABLES = {
    0: ['seedling', 'gusty', 'emberkit', 'droplet', 'pebble'],
    1: ['geoxus', 'thornback', 'slipstream', 'obsidian', 'sparky'],
    2: ['pyrowolf', 'boltsaber', 'ignisaur', 'stormlord', 'zephyrus'],
    3: ['neptunian', 'bloomgoliath', 'terrasunder', 'zephyrus', 'stormlord'],
};
exports.CACHE_REWARDS = [
    { type: 'shards', amount: 25 },
    { type: 'item', itemId: 'vitals_elixir', quantity: 1 },
    { type: 'item', itemId: 'status_cleanser', quantity: 1 },
    { type: 'shards', amount: 40 },
    { type: 'item', itemId: 'prism_boost', quantity: 2 },
];
exports.SHOP_ITEMS = [
    { id: 'harmonizer_prism', name: 'Harmonizer Prism', description: 'Basic capture prism. Moderate catch rate.', price: 30, itemTypeId: 'prism', catchRate: 0.45 },
    { id: 'hyper_conductive_prism', name: 'Hyper-Conductive Prism', description: 'Advanced prism with high catch rate.', price: 80, itemTypeId: 'prism', catchRate: 0.75 },
    { id: 'core_restorer', name: 'Core Restorer', description: 'Restores 50% of a Resonant\'s HP.', price: 20, itemTypeId: 'heal' },
    { id: 'resonance_elixir', name: 'Resonance Elixir', description: 'Heals all status conditions on one Resonant.', price: 35, itemTypeId: 'status' },
    { id: 'vitals_elixir', name: 'Vitals Elixir', description: 'Restores a small amount of HP in battle.', price: 15, itemTypeId: 'heal' },
    { id: 'prism_boost', name: 'Prism Booster', description: 'Doubles the next prism\'s catch rate.', price: 25, itemTypeId: 'boost' },
];
};

// ── module: src/engine/sound.ts ──
__mods["src/engine/sound.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.soundManager = exports.SoundManager = void 0;
class SoundManager {
    constructor() {
        this.ctx = null;
        this.enabled = true;
    }
    init() {
        if (!this.ctx) {
            this.ctx = new (window.AudioContext || window.webkitAudioContext)();
        }
        if (this.ctx.state === 'suspended') {
            this.ctx.resume();
        }
    }
    playTone(freq, duration, type = 'square', volume = 0.15) {
        if (!this.ctx || !this.enabled)
            return;
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = type;
        osc.frequency.setValueAtTime(freq, 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.ctx.destination);
        osc.start();
        osc.stop(this.ctx.currentTime + duration);
    }
    playNoise(duration, volume = 0.1) {
        if (!this.ctx || !this.enabled)
            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 source = this.ctx.createBufferSource();
        source.buffer = buffer;
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(volume, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + duration);
        source.connect(gain);
        gain.connect(this.ctx.destination);
        source.start();
    }
    hover() {
        this.playTone(800, 0.05, 'sine', 0.06);
    }
    select() {
        this.playTone(523, 0.08, 'square', 0.1);
        setTimeout(() => this.playTone(659, 0.08, 'square', 0.1), 60);
    }
    hitPhysical() {
        this.playNoise(0.12, 0.15);
        this.playTone(150, 0.1, 'sawtooth', 0.12);
    }
    hitElemental() {
        this.playTone(300, 0.08, 'square', 0.12);
        setTimeout(() => this.playTone(450, 0.06, 'square', 0.1), 50);
        setTimeout(() => this.playNoise(0.08, 0.08), 30);
    }
    hitSuperEffective() {
        this.playTone(400, 0.06, 'square', 0.15);
        setTimeout(() => this.playTone(600, 0.06, 'square', 0.15), 40);
        setTimeout(() => this.playTone(800, 0.12, 'square', 0.15), 80);
        setTimeout(() => this.playNoise(0.1, 0.12), 60);
    }
    levelUp() {
        const notes = [523, 587, 659, 784, 880, 1047];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.15, 'square', 0.12), i * 80);
        });
    }
    captureSuccess() {
        const notes = [392, 440, 523, 659, 784];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.12, 'triangle', 0.1), i * 70);
        });
    }
    captureFail() {
        this.playTone(300, 0.15, 'sawtooth', 0.1);
        setTimeout(() => this.playTone(200, 0.2, 'sawtooth', 0.1), 150);
    }
    battleStart() {
        const notes = [262, 330, 392, 523];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.15, 'square', 0.1), i * 100);
        });
    }
    victory() {
        const melody = [523, 659, 784, 1047, 784, 1047];
        melody.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.2, 'square', 0.1), i * 120);
        });
    }
    defeat() {
        const notes = [392, 349, 330, 262];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.3, 'sawtooth', 0.1), i * 200);
        });
    }
    evolution() {
        const notes = [440, 554, 659, 880, 1108];
        notes.forEach((freq, i) => {
            setTimeout(() => this.playTone(freq, 0.2, 'triangle', 0.12), i * 100);
        });
    }
    toggle() {
        this.enabled = !this.enabled;
    }
}
exports.SoundManager = SoundManager;
exports.soundManager = new SoundManager();
};

// ── module: src/state/player.ts ──
__mods["src/state/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createResonant = createResonant;
exports.xpForLevel = xpForLevel;
exports.gainXp = gainXp;
exports.checkEvolution = checkEvolution;
exports.evolveResonant = evolveResonant;
exports.getTypeAdvantage = getTypeAdvantage;
exports.calculateDamage = calculateDamage;
exports.createDefaultPlayer = createDefaultPlayer;
exports.saveGame = saveGame;
exports.loadGame = loadGame;
exports.resetGame = resetGame;
const resonants_1 = require("../data/resonants");
const SAVE_KEY = 'aethelgard_save_v1';
function createResonant(templateId, level, name) {
    const tmpl = (0, resonants_1.getResonantById)(templateId);
    if (!tmpl)
        throw new Error(`Unknown resonant template: ${templateId}`);
    const xpBase = Math.floor(level * level * 1.5 + level * 2);
    return {
        templateId,
        name: name || tmpl.name,
        level,
        currentHp: calcStat(tmpl.baseHp, level),
        maxHp: calcStat(tmpl.baseHp, level),
        atk: calcStat(tmpl.baseAtk, level),
        def: calcStat(tmpl.baseDef, level),
        spd: calcStat(tmpl.baseSpd, level),
        xp: 0,
        xpToNext: xpBase + 20,
        statusEffect: null,
    };
}
function calcStat(base, level) {
    return Math.floor((base * 2 + 31) * level / 100 + (level >= 5 ? level : 5));
}
function xpForLevel(level) {
    return Math.floor(level * level * 1.5 + level * 2);
}
function gainXp(r, amount) {
    r.xp += amount;
    let leveledUp = false;
    while (r.xp >= r.xpToNext) {
        r.xp -= r.xpToNext;
        r.level++;
        const tmpl = (0, resonants_1.getResonantById)(r.templateId);
        if (!tmpl)
            break;
        const oldMaxHp = r.maxHp;
        r.maxHp = calcStat(tmpl.baseHp, r.level);
        r.atk = calcStat(tmpl.baseAtk, r.level);
        r.def = calcStat(tmpl.baseDef, r.level);
        r.spd = calcStat(tmpl.baseSpd, r.level);
        const hpGain = r.maxHp - oldMaxHp;
        r.currentHp += Math.max(hpGain, 10);
        if (r.currentHp > r.maxHp)
            r.currentHp = r.maxHp;
        r.xpToNext = xpForLevel(r.level) + 20;
        leveledUp = true;
    }
    return { leveledUp };
}
function checkEvolution(r) {
    const tmpl = (0, resonants_1.getResonantById)(r.templateId);
    if (!tmpl || !tmpl.evolutionLevel)
        return null;
    if (r.level >= tmpl.evolutionLevel && tmpl.evolutionId) {
        return tmpl.evolutionId;
    }
    return null;
}
function evolveResonant(r, newTemplateId) {
    const tmpl = (0, resonants_1.getResonantById)(newTemplateId);
    if (!tmpl)
        return;
    r.templateId = newTemplateId;
    r.name = tmpl.name;
    r.maxHp = calcStat(tmpl.baseHp, r.level);
    r.atk = calcStat(tmpl.baseAtk, r.level);
    r.def = calcStat(tmpl.baseDef, r.level);
    r.spd = calcStat(tmpl.baseSpd, r.level);
    r.currentHp = r.maxHp; // Full heal on evolution
}
function getTypeAdvantage(attackerType, defenderType) {
    const chart = {
        Pyro: { Flora: 1.5, Hydro: 0.67 },
        Hydro: { Pyro: 1.5, Flora: 0.67 },
        Flora: { Hydro: 1.5, Pyro: 0.67 },
        Zephyr: { Earth: 1.5, Volt: 0.67 },
        Earth: { Volt: 1.5, Zephyr: 0.67 },
        Volt: { Zephyr: 1.5, Earth: 0.67 },
        Cosmos: {},
    };
    return chart[attackerType]?.[defenderType] ?? 1.0;
}
function calculateDamage(attacker, defender, movePower, moveElement) {
    const typeMod = getTypeAdvantage(moveElement, (0, resonants_1.getResonantById)(defender.templateId)?.type ?? 'Cosmos');
    const randomFactor = 0.85 + Math.random() * 0.15;
    let damage = (((2 * attacker.level / 5 + 2) * movePower * (attacker.atk / defender.def)) / 50 + 2) * typeMod * randomFactor;
    const isCrit = Math.random() < 0.1;
    if (isCrit)
        damage *= 1.5;
    return { damage: Math.max(1, Math.floor(damage)), isCrit, typeMod };
}
function createDefaultPlayer() {
    return {
        playerName: 'Conductor',
        party: [createResonant('emberkit', 3)],
        inventory: [
            { id: 'harmonizer_prism', name: 'Harmonizer Prism', description: 'Basic capture prism.', quantity: 5, type: 'prism', catchRate: 0.45 },
            { id: 'vitals_elixir', name: 'Vitals Elixir', description: 'Restores small HP.', quantity: 3, type: 'heal' },
        ],
        shards: 50,
        unlockedZones: 1,
        currentZone: 0,
        currentNodeInZone: 0,
        completedBosses: [],
        dexSeen: new Set(),
    };
}
function saveGame(state) {
    try {
        const serializable = JSON.stringify({
            ...state,
            dexSeen: Array.from(state.dexSeen),
        });
        localStorage.setItem(SAVE_KEY, serializable);
    }
    catch (e) {
        console.warn('Failed to save game:', e);
    }
}
function loadGame() {
    try {
        const data = localStorage.getItem(SAVE_KEY);
        if (!data)
            return null;
        const parsed = JSON.parse(data);
        return {
            ...parsed,
            dexSeen: new Set(parsed.dexSeen || []),
        };
    }
    catch (e) {
        console.warn('Failed to load game:', e);
        return null;
    }
}
function resetGame() {
    localStorage.removeItem(SAVE_KEY);
}
};

// ── module: src/engine/combat.ts ──
__mods["src/engine/combat.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CombatEngine = void 0;
const resonants_1 = require("../data/resonants");
const player_1 = require("../state/player");
class CombatEngine {
    constructor(battle) {
        this.messages = [];
        this.animating = false;
        this.battle = battle;
    }
    getBattle() {
        return this.battle;
    }
    isAnimating() {
        return this.animating;
    }
    addMessage(msg) {
        this.messages.push(msg);
    }
    clearMessages() {
        this.messages = [];
    }
    getMessages() {
        return [...this.messages];
    }
    getEnemyResonant() {
        return this.battle.enemy;
    }
    async executePlayerAttack(moveIndex, party) {
        const playerR = party[this.battle.activeIndex];
        if (!playerR || this.animating)
            return null;
        const tmpl = (0, resonants_1.getResonantById)(playerR.templateId);
        if (!tmpl)
            return null;
        const availableMoves = tmpl.moves.filter(m => m.unlockLevel <= playerR.level);
        if (moveIndex >= availableMoves.length)
            return null;
        const move = availableMoves[moveIndex];
        this.animating = true;
        this.battle.turnPhase = 'animating';
        // Accuracy check
        if (Math.random() * 100 > move.accuracy) {
            this.addMessage(`${playerR.name}'s ${move.name} missed!`);
            await this.delay(800);
            this.animating = false;
            this.battle.turnPhase = 'enemy';
            return null;
        }
        const result = (0, player_1.calculateDamage)(playerR, this.battle.enemy, move.power, move.element);
        const typeModText = result.typeMod > 1 ? "It's super effective!" :
            result.typeMod < 1 ? "It's not very effective..." : '';
        let hitMsg = `${playerR.name} used ${move.name}!`;
        if (result.isCrit)
            hitMsg += ' Critical hit!';
        if (typeModText)
            hitMsg += ` ${typeModText}`;
        this.addMessage(hitMsg);
        await this.delay(600);
        // Apply damage
        this.battle.enemy.currentHp = Math.max(0, this.battle.enemy.currentHp - result.damage);
        // Status effect chance for elemental moves
        if (move.element !== 'Cosmos' && Math.random() < 0.15) {
            const statusTypes = ['burn', 'paralyze', 'poison'];
            this.battle.enemy.statusEffect = { type: statusTypes[Math.floor(Math.random() * statusTypes.length)], turnsLeft: 3 };
            this.addMessage(`${this.battle.enemy.name} got a ${this.battle.enemy.statusEffect.type}!`);
        }
        await this.delay(500);
        // Check if enemy fainted
        if (this.battle.enemy.currentHp <= 0) {
            this.animating = false;
            const xpGained = Math.floor(this.battle.enemy.level * 15 + 20);
            const shardsGained = Math.floor(this.battle.enemy.level * 8 + 10);
            return { type: 'enemyDefeated', xpGained, shardsGained };
        }
        // Enemy turn
        this.animating = false;
        this.battle.turnPhase = 'enemy';
        return null;
    }
    async executeEnemyTurn(party) {
        const playerR = party[this.battle.activeIndex];
        if (!playerR || this.animating)
            return null;
        // Status effect damage
        if (this.battle.enemy.statusEffect && Math.random() < 0.33) {
            const statusDmg = Math.floor(this.battle.enemy.maxHp * 0.1);
            this.battle.enemy.currentHp = Math.max(0, this.battle.enemy.currentHp - statusDmg);
            this.addMessage(`${this.battle.enemy.name} is hurt by its ${this.battle.enemy.statusEffect.type}!`);
            if (this.battle.enemy.currentHp <= 0) {
                const xpGained = Math.floor(this.battle.enemy.level * 15 + 20);
                const shardsGained = Math.floor(this.battle.enemy.level * 8 + 10);
                return { type: 'enemyDefeated', xpGained, shardsGained };
            }
        }
        this.animating = true;
        this.battle.turnPhase = 'animating';
        const enemyTmpl = (0, resonants_1.getResonantById)(this.battle.enemy.templateId);
        if (!enemyTmpl)
            return null;
        const availableMoves = enemyTmpl.moves.filter(m => m.unlockLevel <= this.battle.enemy.level);
        // AI: prefer super-effective moves, otherwise random
        let chosenMove = availableMoves[Math.floor(Math.random() * availableMoves.length)];
        for (const move of availableMoves) {
            if ((0, player_1.getTypeAdvantage)(move.element, (0, resonants_1.getResonantById)(playerR.templateId)?.type ?? 'Cosmos') > 1) {
                chosenMove = move;
                break;
            }
        }
        const result = (0, player_1.calculateDamage)(this.battle.enemy, playerR, chosenMove.power, chosenMove.element);
        const typeModText = result.typeMod > 1 ? "It's super effective!" :
            result.typeMod < 1 ? "It's not very effective..." : '';
        let hitMsg = `${this.battle.enemy.name} used ${chosenMove.name}!`;
        if (result.isCrit)
            hitMsg += ' Critical hit!';
        if (typeModText)
            hitMsg += ` ${typeModText}`;
        this.addMessage(hitMsg);
        await this.delay(600);
        playerR.currentHp = Math.max(0, playerR.currentHp - result.damage);
        // Status effect chance
        if (chosenMove.element !== 'Cosmos' && Math.random() < 0.12) {
            const statusTypes = ['burn', 'paralyze', 'poison'];
            playerR.statusEffect = { type: statusTypes[Math.floor(Math.random() * statusTypes.length)], turnsLeft: 3 };
            this.addMessage(`${playerR.name} got a ${playerR.statusEffect.type}!`);
        }
        await this.delay(500);
        if (playerR.currentHp <= 0) {
            // Check if player has other conscious resonants
            const aliveParty = party.filter(r => r.currentHp > 0 && r !== playerR);
            if (aliveParty.length === 0 || !this.battle.isWild) {
                this.animating = false;
                return { type: 'playerFainted' };
            }
            else {
                // Auto-switch to next alive resonant
                for (let i = 0; i < party.length; i++) {
                    if (party[i].currentHp > 0 && i !== this.battle.activeIndex) {
                        this.battle.activeIndex = i;
                        this.addMessage(`Go, ${party[i].name}!`);
                        await this.delay(500);
                        break;
                    }
                }
            }
        }
        // Player status effect damage on their turn start (handled at beginning of player turn)
        if (playerR.statusEffect && Math.random() < 0.33) {
            const statusDmg = Math.floor(playerR.maxHp * 0.1);
            playerR.currentHp = Math.max(0, playerR.currentHp - statusDmg);
            this.addMessage(`${playerR.name} is hurt by its ${playerR.statusEffect.type}!`);
        }
        this.animating = false;
        this.battle.turnPhase = 'player';
        return null;
    }
    async attemptCapture(party, prismCatchRate) {
        if (this.animating)
            return null;
        this.animating = true;
        this.battle.turnPhase = 'animating';
        const playerR = party[this.battle.activeIndex];
        if (!playerR)
            return null;
        this.addMessage(`${playerR.name} threw an Aether Prism!`);
        await this.delay(800);
        // Capture formula: based on HP ratio, status effects, and prism tier
        const hpRatio = this.battle.enemy.currentHp / this.battle.enemy.maxHp;
        let catchRate = prismCatchRate * (1 - hpRatio * 0.7);
        if (this.battle.enemy.statusEffect)
            catchRate *= 1.5;
        // Multiple shakes for dramatic effect
        const maxShakes = Math.min(4, Math.floor(catchRate * 8));
        this.addMessage(`The Prism is shaking...`);
        await this.delay(600);
        let captured = false;
        if (Math.random() < catchRate) {
            captured = true;
        }
        else {
            // Show shakes then break free
            for (let i = 0; i < maxShakes && !captured; i++) {
                await this.delay(500);
                if (i === maxShakes - 1) {
                    captured = Math.random() < catchRate * 0.8;
                }
                else if (Math.random() > 0.6) {
                    // Continue shaking
                }
            }
        }
        await this.delay(400);
        if (captured) {
            const capturedR = { ...this.battle.enemy };
            capturedR.currentHp = Math.max(1, Math.floor(capturedR.maxHp * 0.3));
            capturedR.statusEffect = null;
            this.addMessage(`${this.battle.enemy.name} was captured!`);
            await this.delay(500);
            this.animating = false;
            return { type: 'captured', capturedResonant: capturedR };
        }
        else {
            this.addMessage(`${this.battle.enemy.name} broke free!`);
            await this.delay(600);
            this.animating = false;
            this.battle.turnPhase = 'enemy';
            return null; // Enemy gets a turn after failed capture
        }
    }
    async useItem(itemType, party) {
        if (this.animating)
            return null;
        this.animating = true;
        const playerR = party[this.battle.activeIndex];
        if (!playerR)
            return null;
        switch (itemType) {
            case 'heal': {
                const healAmount = Math.floor(playerR.maxHp * 0.5);
                playerR.currentHp = Math.min(playerR.maxHp, playerR.currentHp + healAmount);
                this.addMessage(`${playerR.name} recovered ${healAmount} HP!`);
                await this.delay(600);
                break;
            }
            case 'status': {
                if (playerR.statusEffect) {
                    this.addMessage(`${playerR.name}'s status was cured!`);
                    playerR.statusEffect = null;
                }
                else {
                    this.addMessage(`${playerR.name} had no status to cure.`);
                }
                await this.delay(600);
                break;
            }
            case 'boost': {
                // Boost is handled in the capture flow, just consume it
                this.addMessage(`Prism power boosted!`);
                await this.delay(400);
                break;
            }
        }
        this.animating = false;
        this.battle.turnPhase = 'enemy';
        return null; // Enemy gets a turn after using item
    }
    async attemptFlee() {
        if (!this.battle.isWild) {
            this.addMessage("Can't flee from a Conductor battle!");
            await this.delay(500);
            return null;
        }
        const playerR = /* will be passed */ null;
        // Flee chance based on speed difference
        if (Math.random() < 0.7) {
            this.addMessage('Got away safely!');
            await this.delay(400);
            return { type: 'fled' };
        }
        else {
            this.addMessage("Couldn't escape!");
            await this.delay(500);
            return null; // Enemy gets a turn
        }
    }
    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}
exports.CombatEngine = CombatEngine;
};

// ── module: src/ui/renderer.ts ──
__mods["src/ui/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTypeColor = getTypeColor;
exports.generateResonantSVG = generateResonantSVG;
exports.generateZoneIcon = generateZoneIcon;
exports.generateTypeIcon = generateTypeIcon;
const resonants_1 = require("../data/resonants");
const TYPE_COLORS = {
    Pyro: '#FF6B35', Hydro: '#4CC9F0', Flora: '#2D6A4F',
    Zephyr: '#A2D2FF', Earth: '#8B7355', Volt: '#FFD700', Cosmos: '#C77DFF',
};
function getTypeColor(type) {
    return TYPE_COLORS[type] || '#888';
}
function generateResonantSVG(r, size = 120) {
    const tmpl = (0, resonants_1.getResonantById)(r.templateId);
    if (!tmpl)
        return '';
    const sp = tmpl.spriteParams;
    const cx = size / 2;
    const cy = size / 2;
    let bodyPath = '';
    switch (sp.bodyShape) {
        case 'round':
            bodyPath = `<ellipse cx="${cx}" cy="${cy + 5}" rx="${size * 0.32}" ry="${size * 0.35}" fill="${sp.primaryColor}"/>`;
            break;
        case 'angular':
            bodyPath = `<polygon points="${cx},${cy - size * 0.38} ${cx + size * 0.35},${cy + size * 0.1} ${cx + size * 0.2},${cy + size * 0.4} ${cx - size * 0.2},${cy + size * 0.4} ${cx - size * 0.35},${cy + size * 0.1}" fill="${sp.primaryColor}"/>`;
            break;
        case 'elongated':
            bodyPath = `<ellipse cx="${cx}" cy="${cy + 5}" rx="${size * 0.25}" ry="${size * 0.4}" fill="${sp.primaryColor}"/>`;
            break;
        case 'spiky':
            const pts = [];
            for (let i = 0; i < 8; i++) {
                const angle = (i / 8) * Math.PI * 2 - Math.PI / 2;
                const r1 = size * 0.35;
                const r2 = size * 0.2;
                pts.push(`${cx + Math.cos(angle) * r1},${cy + Math.sin(angle) * r1}`);
                const midAngle = ((i + 0.5) / 8) * Math.PI * 2 - Math.PI / 2;
                pts.push(`${cx + Math.cos(midAngle) * r2},${cy + Math.sin(midAngle) * r2}`);
            }
            bodyPath = `<polygon points="${pts.join(' ')}" fill="${sp.primaryColor}"/>`;
            break;
    }
    // Eyes
    const eyeY = cy - size * 0.1;
    const eyes = `
    <circle cx="${cx - size * 0.1}" cy="${eyeY}" r="${size * 0.06}" fill="white"/>
    <circle cx="${cx + size * 0.1}" cy="${eyeY}" r="${size * 0.06}" fill="white"/>
    <circle cx="${cx - size * 0.08}" cy="${eyeY}" r="${size * 0.035}" fill="#222"/>
    <circle cx="${cx + size * 0.12}" cy="${eyeY}" r="${size * 0.035}" fill="#222"/>
  `;
    // Feature-specific decorations
    let features = '';
    if (sp.features.includes('small-tail-flame')) {
        features += `<path d="M${cx + size * 0.3},${cy} Q${cx + size * 0.45},${cy - size * 0.2} ${cx + size * 0.35},${cy - size * 0.35}" stroke="${sp.accentColor}" stroke-width="3" fill="none"/>`;
    }
    if (sp.features.includes('pointy-ears')) {
        features += `<polygon points="${cx - size * 0.2},${cy - size * 0.2} ${cx - size * 0.15},${cy - size * 0.45} ${cx - size * 0.05},${cy - size * 0.2}" fill="${sp.secondaryColor}"/>`;
        features += `<polygon points="${cx + size * 0.05},${cy - size * 0.2} ${cx + size * 0.15},${cy - size * 0.45} ${cx + size * 0.2},${cy - size * 0.2}" fill="${sp.secondaryColor}"/>`;
    }
    if (sp.features.includes('flame-manes')) {
        for (let i = -2; i <= 2; i++) {
            features += `<path d="M${cx + i * size * 0.1},${cy - size * 0.35} Q${cx + i * size * 0.12},${cy - size * 0.55} ${cx + i * size * 0.08},${cy - size * 0.6}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
        }
    }
    if (sp.features.includes('wolf-snout')) {
        features += `<ellipse cx="${cx}" cy="${eyeY + size * 0.15}" rx="${size * 0.12}" ry="${size * 0.08}" fill="${sp.secondaryColor}"/>`;
        features += `<circle cx="${cx}" cy="${eyeY + size * 0.13}" r="${size * 0.04}" fill="#333"/>`;
    }
    if (sp.features.includes('dragon-horns')) {
        features += `<polygon points="${cx - size * 0.25},${cy - size * 0.3} ${cx - size * 0.35},${cy - size * 0.6} ${cx - size * 0.15},${cy - size * 0.3}" fill="${sp.accentColor}"/>`;
        features += `<polygon points="${cx + size * 0.25},${cy - size * 0.3} ${cx + size * 0.35},${cy - size * 0.6} ${cx + size * 0.15},${cy - size * 0.3}" fill="${sp.accentColor}"/>`;
    }
    if (sp.features.includes('flame-crown')) {
        for (let i = -2; i <= 2; i++) {
            features += `<path d="M${cx + i * size * 0.1},${cy - size * 0.38} Q${cx + i * size * 0.12},${cy - size * 0.65} ${cx + i * size * 0.07},${cy - size * 0.7}" stroke="${sp.accentColor}" stroke-width="3" fill="none"/>`;
        }
    }
    if (sp.features.includes('water-droplet-body')) {
        features += `<ellipse cx="${cx}" cy="${cy - size * 0.15}" rx="${size * 0.12}" ry="${size * 0.18}" fill="${sp.secondaryColor}" opacity="0.6"/>`;
    }
    if (sp.features.includes('big-eyes')) {
        features += `<circle cx="${cx - size * 0.1}" cy="${eyeY}" r="${size * 0.09}" fill="white" stroke="#333" stroke-width="1"/>`;
        features += `<circle cx="${cx + size * 0.1}" cy="${eyeY}" r="${size * 0.09}" fill="white" stroke="#333" stroke-width="1"/>`;
    }
    if (sp.features.includes('serpent-body')) {
        features += `<path d="M${cx - size * 0.2},${cy + size * 0.3} Q${cx - size * 0.4},${cy + size * 0.5} ${cx - size * 0.15},${cy + size * 0.6}" stroke="${sp.secondaryColor}" stroke-width="4" fill="none"/>`;
    }
    if (sp.features.includes('water-crest')) {
        features += `<path d="M${cx - size * 0.2},${cy - size * 0.35} Q${cx},${cy - size * 0.6} ${cx + size * 0.2},${cy - size * 0.35}" stroke="${sp.accentColor}" stroke-width="3" fill="none"/>`;
    }
    if (sp.features.includes('sea-king-crown')) {
        features += `<path d="M${cx - size * 0.25},${cy - size * 0.4} L${cx - size * 0.15},${cy - size * 0.6} L${cx - size * 0.05},${cy - size * 0.45} L${cx + size * 0.05},${cy - size * 0.6} L${cx + size * 0.15},${cy - size * 0.45} L${cx + size * 0.25},${cy - size * 0.3}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('trident-tail')) {
        features += `<path d="M${cx + size * 0.15},${cy + size * 0.3} Q${cx + size * 0.4},${cy + size * 0.5} ${cx + size * 0.35},${cy + size * 0.6}" stroke="${sp.secondaryColor}" stroke-width="3" fill="none"/>`;
    }
    if (sp.features.includes('leaf-sprout')) {
        features += `<ellipse cx="${cx}" cy="${cy - size * 0.4}" rx="${size * 0.15}" ry="${size * 0.2}" fill="${sp.accentColor}"/>`;
    }
    if (sp.features.includes('bulb-body')) {
        features += `<circle cx="${cx}" cy="${cy + size * 0.1}" r="${size * 0.15}" fill="${sp.secondaryColor}" opacity="0.4"/>`;
    }
    if (sp.features.includes('thorn-armor')) {
        for (let i = -2; i <= 2; i++) {
            features += `<line x1="${cx + i * size * 0.1}" y1="${cy - size * 0.35}" x2="${cx + i * size * 0.12}" y2="${cy - size * 0.5}" stroke="${sp.accentColor}" stroke-width="2"/>`;
        }
    }
    if (sp.features.includes('flower-back')) {
        features += `<circle cx="${cx}" cy="${cy - size * 0.35}" r="${size * 0.12}" fill="${sp.accentColor}"/>`;
        features += `<circle cx="${cx}" cy="${cy - size * 0.35}" r="${size * 0.06}" fill="${sp.secondaryColor}"/>`;
    }
    if (sp.features.includes('giant-blossom')) {
        for (let i = 0; i < 5; i++) {
            const angle = (i / 5) * Math.PI * 2 - Math.PI / 2;
            features += `<ellipse cx="${cx + Math.cos(angle) * size * 0.18}" cy="${cy - size * 0.35 + Math.sin(angle) * size * 0.18}" rx="${size * 0.1}" ry="${size * 0.06}" fill="${sp.accentColor}" transform="rotate(${i * 72} ${cx + Math.cos(angle) * size * 0.18} ${cy - size * 0.35 + Math.sin(angle) * size * 0.18})"/>`;
        }
    }
    if (sp.features.includes('root-legs')) {
        features += `<path d="M${cx - size * 0.15},${cy + size * 0.4} Q${cx - size * 0.25},${cy + size * 0.6} ${cx - size * 0.3},${cy + size * 0.7}" stroke="${sp.secondaryColor}" stroke-width="3" fill="none"/>`;
        features += `<path d="M${cx + size * 0.15},${cy + size * 0.4} Q${cx + size * 0.25},${cy + size * 0.6} ${cx + size * 0.3},${cy + size * 0.7}" stroke="${sp.secondaryColor}" stroke-width="3" fill="none"/>`;
    }
    if (sp.features.includes('lightning-tail')) {
        features += `<polyline points="${cx + size * 0.25},${cy} ${cx + size * 0.4},${cy - size * 0.15} ${cx + size * 0.35},${cy - size * 0.05} ${cx + size * 0.5},${cy - size * 0.3}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('electric-cheeks')) {
        features += `<ellipse cx="${cx - size * 0.18}" cy="${eyeY + size * 0.1}" rx="${size * 0.06}" ry="${size * 0.04}" fill="${sp.accentColor}"/>`;
        features += `<ellipse cx="${cx + size * 0.18}" cy="${eyeY + size * 0.1}" rx="${size * 0.06}" ry="${size * 0.04}" fill="${sp.accentColor}"/>`;
    }
    if (sp.features.includes('sabertooth')) {
        features += `<path d="M${cx - size * 0.08},${eyeY + size * 0.12} L${cx - size * 0.05},${eyeY + size * 0.25}" stroke="white" stroke-width="2"/>`;
        features += `<path d="M${cx + size * 0.08},${eyeY + size * 0.12} L${cx + size * 0.05},${eyeY + size * 0.25}" stroke="white" stroke-width="2"/>`;
    }
    if (sp.features.includes('bolt-armor')) {
        features += `<path d="M${cx - size * 0.3},${cy} L${cx - size * 0.15},${cy - size * 0.1} L${cx - size * 0.2},${cy + size * 0.1}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
        features += `<path d="M${cx + size * 0.3},${cy} L${cx + size * 0.15},${cy - size * 0.1} L${cx + size * 0.2},${cy + size * 0.1}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('storm-crown')) {
        features += `<path d="M${cx - size * 0.3},${cy - size * 0.4} L${cx - size * 0.15},${cy - size * 0.65} L${cx - size * 0.02},${cy - size * 0.45} L${cx + size * 0.1},${cy - size * 0.7} L${cx + size * 0.2},${cy - size * 0.45}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('lightning-wings')) {
        features += `<path d="M${cx - size * 0.3},${cy} Q${cx - size * 0.6},${cy - size * 0.3} ${cx - size * 0.5},${cy + size * 0.1}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
        features += `<path d="M${cx + size * 0.3},${cy} Q${cx + size * 0.6},${cy - size * 0.3} ${cx + size * 0.5},${cy + size * 0.1}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('wind-wings')) {
        features += `<ellipse cx="${cx - size * 0.35}" cy="${cy - size * 0.1}" rx="${size * 0.15}" ry="${size * 0.25}" fill="${sp.secondaryColor}" opacity="0.6" transform="rotate(-20 ${cx - size * 0.35} ${cy})"/>`;
        features += `<ellipse cx="${cx + size * 0.35}" cy="${cy - size * 0.1}" rx="${size * 0.15}" ry="${size * 0.25}" fill="${sp.secondaryColor}" opacity="0.6" transform="rotate(20 ${cx + size * 0.35} ${cy})"/>`;
    }
    if (sp.features.includes('feather-body')) {
        features += `<path d="M${cx - size * 0.1},${cy + size * 0.1} Q${cx},${cy + size * 0.25} ${cx + size * 0.1},${cy + size * 0.1}" stroke="${sp.accentColor}" stroke-width="1" fill="none"/>`;
    }
    if (sp.features.includes('streamlined-body')) {
        features += `<path d="M${cx - size * 0.35},${cy} Q${cx - size * 0.45},${cy + size * 0.2} ${cx - size * 0.3},${cy + size * 0.3}" stroke="${sp.secondaryColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('wind-trails')) {
        for (let i = 0; i < 3; i++) {
            features += `<path d="M${cx - size * 0.4 - i * size * 0.1},${cy + i * size * 0.1} Q${cx - size * 0.5 - i * size * 0.1},${cy + size * 0.05 + i * size * 0.1} ${cx - size * 0.35 - i * size * 0.1},${cy + size * 0.1 + i * size * 0.1}" stroke="${sp.accentColor}" stroke-width="1" fill="none" opacity="0.5"/>`;
        }
    }
    if (sp.features.includes('sky-king-halo')) {
        features += `<ellipse cx="${cx}" cy="${cy - size * 0.45}" rx="${size * 0.2}" ry="${size * 0.06}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('tempest-wings')) {
        features += `<path d="M${cx - size * 0.3},${cy} Q${cx - size * 0.7},${cy - size * 0.4} ${cx - size * 0.55},${cy + size * 0.2}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
        features += `<path d="M${cx + size * 0.3},${cy} Q${cx + size * 0.7},${cy - size * 0.4} ${cx + size * 0.55},${cy + size * 0.2}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('rock-body')) {
        features += `<circle cx="${cx - size * 0.1}" cy="${cy + size * 0.15}" r="${size * 0.08}" fill="${sp.secondaryColor}" opacity="0.4"/>`;
    }
    if (sp.features.includes('crystal-eyes')) {
        features += `<polygon points="${cx - size * 0.1},${eyeY - size * 0.06} ${cx - size * 0.07},${eyeY + size * 0.03} ${cx - size * 0.13},${eyeY + size * 0.03}" fill="${sp.accentColor}"/>`;
        features += `<polygon points="${cx + size * 0.1},${eyeY - size * 0.06} ${cx + size * 0.13},${eyeY + size * 0.03} ${cx + size * 0.07},${eyeY + size * 0.03}" fill="${sp.accentColor}"/>`;
    }
    if (sp.features.includes('crystal-armor')) {
        features += `<polygon points="${cx - size * 0.25},${cy - size * 0.1} ${cx - size * 0.15},${cy + size * 0.1} ${cx - size * 0.3},${cy + size * 0.1}" fill="${sp.accentColor}" opacity="0.6"/>`;
        features += `<polygon points="${cx + size * 0.25},${cy - size * 0.1} ${cx + size * 0.15},${cy + size * 0.1} ${cx + size * 0.3},${cy + size * 0.1}" fill="${sp.accentColor}" opacity="0.6"/>`;
    }
    if (sp.features.includes('stone-claws')) {
        features += `<path d="M${cx - size * 0.25},${cy + size * 0.2} L${cx - size * 0.35},${cy + size * 0.3}" stroke="${sp.accentColor}" stroke-width="2"/>`;
        features += `<path d="M${cx + size * 0.25},${cy + size * 0.2} L${cx + size * 0.35},${cy + size * 0.3}" stroke="${sp.accentColor}" stroke-width="2"/>`;
    }
    if (sp.features.includes('mountain-horns')) {
        features += `<polygon points="${cx - size * 0.2},${cy - size * 0.3} ${cx - size * 0.35},${cy - size * 0.65} ${cx - size * 0.1},${cy - size * 0.3}" fill="${sp.accentColor}"/>`;
        features += `<polygon points="${cx + size * 0.2},${cy - size * 0.3} ${cx + size * 0.35},${cy - size * 0.65} ${cx + size * 0.1},${cy - size * 0.3}" fill="${sp.accentColor}"/>`;
    }
    if (sp.features.includes('earth-shield')) {
        features += `<rect x="${cx - size * 0.25}" y="${cy - size * 0.15}" width="${size * 0.5}" height="${size * 0.3}" rx="4" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('ancient-tree-form')) {
        features += `<path d="M${cx},${cy - size * 0.5} Q${cx - size * 0.3},${cy - size * 0.7} ${cx - size * 0.4},${cy - size * 0.6}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
        features += `<path d="M${cx},${cy - size * 0.5} Q${cx + size * 0.3},${cy - size * 0.7} ${cx + size * 0.4},${cy - size * 0.6}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('golden-eyes')) {
        features += `<circle cx="${cx - size * 0.1}" cy="${eyeY}" r="${size * 0.07}" fill="${sp.accentColor}"/>`;
        features += `<circle cx="${cx + size * 0.1}" cy="${eyeY}" r="${size * 0.07}" fill="${sp.accentColor}"/>`;
    }
    if (sp.features.includes('water-crown')) {
        features += `<path d="M${cx - size * 0.2},${cy - size * 0.45} Q${cx},${cy - size * 0.65} ${cx + size * 0.2},${cy - size * 0.45}" stroke="${sp.accentColor}" stroke-width="3" fill="none"/>`;
    }
    if (sp.features.includes('lava-veins')) {
        features += `<path d="M${cx - size * 0.15},${cy} Q${cx},${cy + size * 0.1} ${cx + size * 0.15},${cy}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('obsidian-crown')) {
        features += `<path d="M${cx - size * 0.25},${cy - size * 0.4} L${cx - size * 0.1},${cy - size * 0.6} L${cx + size * 0.1},${cy - size * 0.6} L${cx + size * 0.25},${cy - size * 0.4}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
    }
    if (sp.features.includes('void-crown')) {
        features += `<circle cx="${cx}" cy="${cy - size * 0.5}" r="${size * 0.15}" stroke="${sp.accentColor}" stroke-width="2" fill="none"/>`;
        features += `<circle cx="${cx}" cy="${cy - size * 0.5}" r="${size * 0.08}" fill="${sp.accentColor}" opacity="0.3"/>`;
    }
    if (sp.features.includes('cosmic-aura')) {
        for (let i = 0; i < 6; i++) {
            const angle = (i / 6) * Math.PI * 2;
            features += `<circle cx="${cx + Math.cos(angle) * size * 0.4}" cy="${cy + Math.sin(angle) * size * 0.4}" r="${size * 0.03}" fill="${sp.accentColor}" opacity="0.5"/>`;
        }
    }
    // Glow filter
    const glow = `<filter id="glow_${r.templateId}"><feGaussianBlur stdDeviation="3" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter>`;
    return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
    <defs>${glow}</defs>
    <g filter="url(#glow_${r.templateId})" opacity="0.3">${bodyPath}</g>
    ${bodyPath}
    ${eyes}
    ${features}
  </svg>`;
}
function generateZoneIcon(zoneIndex, size = 40) {
    const icons = [
        `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="#52B788"><path d="M12 2L6 8v10h3v-6h6v6h3V8z"/></svg>`,
        `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="#4CC9F0"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 3c1.7 0 3 1.3 3 3s-1.3 3-3 3-3-1.3-3-3 1.3-3 3-3z"/></svg>`,
        `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="#FF6B35"><path d="M12 2l3 7h7l-5.5 4.5L18 21l-6-4.5L6 21l1.5-7.5L2 9h7z"/></svg>`,
        `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="#C77DFF"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/></svg>`,
    ];
    return icons[zoneIndex] || icons[0];
}
function generateTypeIcon(type, size = 24) {
    const color = getTypeColor(type);
    const shapes = {
        Pyro: `<path d="M12 2 Q8 8 10 12 Q6 14 8 18 L12 22 L16 18 Q18 14 14 12 Q16 8 12 2z"/>`,
        Hydro: `<path d="M12 2 Q6 10 6 14 A6 6 0 0 0 18 14 Q18 10 12 2z"/>`,
        Flora: `<path d="M12 2 L8 8 L4 8 L8 12 L6 18 L12 14 L18 18 L16 12 L20 8 L16 8z"/>`,
        Zephyr: `<path d="M4 8 Q8 6 12 8 Q16 10 20 8 M4 12 Q8 10 12 12 Q16 14 20 12 M4 16 Q8 14 12 16"/>`,
        Earth: `<path d="M12 2 L4 8 L4 18 L12 22 L20 18 L20 8z"/>`,
        Volt: `<path d="M13 2 L7 12 L11 12 L10 22 L18 10 L14 10z"/>`,
        Cosmos: `<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/>`,
    };
    return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="${color}">${shapes[type] || shapes.Cosmos}</svg>`;
}
};

// ── module: src/engine/game.ts ──
__mods["src/engine/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initGame = initGame;
const zones_1 = require("../data/zones");
const resonants_1 = require("../data/resonants");
const player_1 = require("../state/player");
const sound_1 = require("./sound");
const combat_1 = require("./combat");
// DOM references
let screens;
let currentScreen = 'menu';
let playerState;
let battleEngine = null;
let battleParty = [];
let prismBoostActive = false;
// Animation state
let animatingBattle = false;
// Particle system
function spawnStatusParticles(statusType, targetElementId) {
    const target = document.getElementById(targetElementId);
    if (!target)
        return;
    const rect = target.getBoundingClientRect();
    const particleClass = `particle-${statusType}`;
    for (let i = 0; i < 6; i++) {
        const particle = document.createElement('div');
        particle.className = `status-particle ${particleClass}`;
        particle.style.left = `${rect.left + rect.width * 0.3 + Math.random() * rect.width * 0.4}px`;
        particle.style.top = `${rect.top + rect.height * 0.5 + Math.random() * 20}px`;
        document.body.appendChild(particle);
        setTimeout(() => particle.remove(), 2000);
    }
}
// Ambient background particles
function initAmbientParticles() {
    const colors = ['rgba(199,125,255,0.3)', 'rgba(76,201,240,0.2)', 'rgba(82,183,136,0.2)'];
    for (let i = 0; i < 15; i++) {
        const particle = document.createElement('div');
        particle.className = 'ambient-particle';
        const size = 2 + Math.random() * 4;
        particle.style.width = `${size}px`;
        particle.style.height = `${size}px`;
        particle.style.left = `${Math.random() * 100}%`;
        particle.style.background = colors[Math.floor(Math.random() * colors.length)];
        particle.style.animationDuration = `${8 + Math.random() * 12}s`;
        particle.style.animationDelay = `${Math.random() * 10}s`;
        document.body.appendChild(particle);
    }
}
function initGame() {
    screens = {};
    const app = document.getElementById('app');
    // Create screen containers
    const screenNames = ['menu', 'map', 'battle', 'party', 'shop', 'dex', 'victory'];
    for (const name of screenNames) {
        const div = document.createElement('div');
        div.id = `screen-${name}`;
        div.className = 'game-screen';
        app.appendChild(div);
        screens[name] = div;
    }
    // Try to load saved game
    const saved = (0, player_1.loadGame)();
    if (saved) {
        playerState = saved;
        showScreen('map');
        renderMap();
    }
    else {
        playerState = (0, player_1.createDefaultPlayer)();
        showScreen('menu');
        renderMenu();
    }
    // Init sound on first interaction
    document.addEventListener('click', () => sound_1.soundManager.init(), { once: true });
    // Ambient background particles
    initAmbientParticles();
}
function showScreen(name) {
    currentScreen = name;
    for (const key in screens) {
        screens[key].style.display = 'none';
    }
    screens[name].style.display = 'flex';
    sound_1.soundManager.select();
}
// ==================== MENU SCREEN ====================
function renderMenu() {
    const hasSave = !!(0, player_1.loadGame)();
    screens.menu.innerHTML = `
    <div class="menu-container">
      <div class="title-section">
        <h1 class="game-title">AETHELGARD</h1>
        <p class="subtitle">Resonant Chronicles</p>
      </div>
      <div class="menu-buttons">
        ${hasSave ? `<button id="btn-continue" class="menu-btn primary">Continue</button>` : ''}
        <button id="btn-newgame" class="menu-btn primary">New Game</button>
        <button id="btn-dex" class="menu-btn secondary">Conductors Dex</button>
        ${hasSave ? `<button id="btn-reset" class="menu-btn danger">Reset Progress</button>` : ''}
      </div>
      <p class="version-text">v1.0 — A world vibrating with elemental energy</p>
    </div>
  `;
    document.getElementById('btn-continue')?.addEventListener('click', () => {
        const saved = (0, player_1.loadGame)();
        if (saved) {
            playerState = saved;
            showScreen('map');
            renderMap();
        }
    });
    document.getElementById('btn-newgame').addEventListener('click', () => {
        (0, player_1.resetGame)();
        playerState = (0, player_1.createDefaultPlayer)();
        (0, player_1.saveGame)(playerState);
        showScreen('map');
        renderMap();
    });
    document.getElementById('btn-dex')?.addEventListener('click', () => {
        showScreen('dex');
        renderDex();
    });
    document.getElementById('btn-reset')?.addEventListener('click', () => {
        if (confirm('Are you sure? This will erase all progress!')) {
            (0, player_1.resetGame)();
            playerState = (0, player_1.createDefaultPlayer)();
            renderMenu();
        }
    });
}
// ==================== MAP SCREEN ====================
function renderMap() {
    const zone = zones_1.ZONES[playerState.currentZone];
    const nodes = zone.nodes;
    let nodesHTML = '';
    for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        const isUnlocked = i <= playerState.currentNodeInZone;
        const isCurrent = i === playerState.currentNodeInZone;
        const isCompleted = i < playerState.currentNodeInZone;
        let icon = '🌿';
        if (node.type === 'cache')
            icon = '📦';
        else if (node.type === 'conductor')
            icon = '⚔️';
        else if (node.type === 'spring')
            icon = '💧';
        else if (node.type === 'boss')
            icon = '👑';
        let nodeClass = 'map-node';
        if (!isUnlocked)
            nodeClass += ' locked';
        if (isCurrent)
            nodeClass += ' current';
        if (isCompleted)
            nodeClass += ' completed';
        nodesHTML += `
      <div class="${nodeClass}" data-index="${i}" title="${node.label}">
        <span class="node-icon">${icon}</span>
        <span class="node-label">${node.label}</span>
      </div>
    `;
    }
    screens.map.innerHTML = `
    <div class="map-container">
      <div class="map-header glass-card">
        <h2>${zone.name}</h2>
        <p class="zone-desc">${zone.description}</p>
        <div class="zone-info">
          <span>Levels ${zone.levelRange[0]}-${zone.levelRange[1]}</span>
          <span>Shards: ${playerState.shards} 💎</span>
          <span>Party: ${playerState.party.length}/6</span>
        </div>
      </div>
      <div class="map-path">
        ${nodesHTML}
      </div>
      <div class="map-actions glass-card">
        <button id="btn-party" class="action-btn">🎒 Party</button>
        <button id="btn-shop" class="action-btn">🏪 Shop</button>
        <button id="btn-dex-map" class="action-btn">📖 Dex</button>
        <button id="btn-menu" class="action-btn">☰ Menu</button>
      </div>
    </div>
  `;
    // Node click handlers
    document.querySelectorAll('.map-node.current').forEach(el => {
        el.addEventListener('click', () => {
            const idx = parseInt(el.dataset.index);
            handleNodeInteraction(idx);
        });
    });
    document.getElementById('btn-party').addEventListener('click', () => { showScreen('party'); renderParty(); });
    document.getElementById('btn-shop').addEventListener('click', () => { showScreen('shop'); renderShop(); });
    document.getElementById('btn-dex-map').addEventListener('click', () => { showScreen('dex'); renderDex(); });
    document.getElementById('btn-menu').addEventListener('click', () => { showScreen('menu'); renderMenu(); });
}
async function handleNodeInteraction(nodeIndex) {
    const zone = zones_1.ZONES[playerState.currentZone];
    const node = zone.nodes[nodeIndex];
    switch (node.type) {
        case 'wild':
            await startWildBattle(playerState.currentZone);
            break;
        case 'cache':
            await openCache();
            break;
        case 'conductor':
            await startConductorBattle(nodeIndex);
            break;
        case 'spring':
            healParty();
            advanceNode();
            renderMap();
            break;
        case 'boss':
            await startBossBattle(playerState.currentZone);
            break;
    }
}
function advanceNode() {
    playerState.currentNodeInZone++;
    (0, player_1.saveGame)(playerState);
}
// ==================== WILD BATTLES ====================
async function startWildBattle(zoneIndex) {
    const zone = zones_1.ZONES[zoneIndex];
    const encounterTable = zones_1.WILD_ENCOUNTER_TABLES[zoneIndex] || [];
    const templateId = encounterTable[Math.floor(Math.random() * encounterTable.length)];
    const levelRange = zone.levelRange;
    const level = levelRange[0] + Math.floor(Math.random() * (levelRange[1] - levelRange[0] + 1));
    const enemy = (0, player_1.createResonant)(templateId, level);
    playerState.dexSeen.add(templateId);
    battleParty = [...playerState.party];
    startBattle(enemy, true);
}
// ==================== CONDUCTOR BATTLES ====================
async function startConductorBattle(nodeIndex) {
    const zone = zones_1.ZONES[playerState.currentZone];
    const conductorIdx = nodeIndex - 1; // First conductor is at index 4 in zone 0, etc.
    let rosterIdx = 0;
    if (playerState.currentZone === 0)
        rosterIdx = nodeIndex >= 8 ? 1 : 0;
    else if (playerState.currentZone === 1)
        rosterIdx = nodeIndex >= 8 ? 3 : 2;
    else if (playerState.currentZone === 2)
        rosterIdx = nodeIndex >= 8 ? 5 : 4;
    else
        rosterIdx = Math.min(nodeIndex - 1, 8);
    const roster = zones_1.CONDUCTOR_ROSTERS[rosterIdx] || zones_1.CONDUCTOR_ROSTERS[0];
    const conductorNames = ['Elara', 'Finn', 'Mira', 'Rex', 'Zara', 'Kai', 'Nova', 'Orion', 'Lyra'];
    // Pick a random resonant from the roster
    const entry = roster[Math.floor(Math.random() * roster.length)];
    const enemy = (0, player_1.createResonant)(entry.templateId, entry.level);
    playerState.dexSeen.add(entry.templateId);
    battleParty = [...playerState.party];
    startBattle(enemy, false, conductorNames[rosterIdx] || 'Unknown');
}
// ==================== BOSS BATTLES ====================
async function startBossBattle(zoneIndex) {
    const bossId = zones_1.BOSS_IDS[zoneIndex];
    if (!bossId)
        return;
    let level = 8 + zoneIndex * 3;
    const enemy = (0, player_1.createResonant)(bossId, level);
    playerState.dexSeen.add(bossId);
    battleParty = [...playerState.party];
    startBattle(enemy, false, bossId.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()));
}
// ==================== BATTLE SYSTEM ====================
function startBattle(enemy, isWild, conductorName) {
    const battleState = {
        enemy,
        activeIndex: findAliveResonant(battleParty),
        turnPhase: 'player',
        messageLog: [`A wild ${enemy.name} appeared!`],
        isWild,
        conductorName,
    };
    battleEngine = new combat_1.CombatEngine(battleState);
    prismBoostActive = false;
    showScreen('battle');
    renderBattle();
    sound_1.soundManager.battleStart();
}
function findAliveResonant(party) {
    for (let i = 0; i < party.length; i++) {
        if (party[i].currentHp > 0)
            return i;
    }
    return 0;
}
// ==================== BATTLE RENDERING ====================
function renderBattle() {
    if (!battleEngine)
        return;
    const bs = battleEngine.getBattle();
    const enemy = bs.enemy;
    const playerR = battleParty[bs.activeIndex];
    if (!playerR)
        return;
    const tmpl = (0, resonants_1.getResonantById)(playerR.templateId);
    const availableMoves = tmpl?.moves.filter(m => m.unlockLevel <= playerR.level) || [];
    const enemyHpPct = Math.max(0, (enemy.currentHp / enemy.maxHp) * 100);
    const playerHpPct = Math.max(0, (playerR.currentHp / playerR.maxHp) * 100);
    const playerXpPct = Math.min(100, (playerR.xp / playerR.xpToNext) * 100);
    const enemySvg = generateResonantSVG(enemy, 140);
    const playerSvg = generateResonantSVG(playerR, 120);
    // Build move buttons
    let movesHTML = '';
    for (let i = 0; i < availableMoves.length; i++) {
        const m = availableMoves[i];
        const typeColor = getTypeColor(m.element);
        movesHTML += `<button class="move-btn" data-move="${i}" style="--type-color: ${typeColor}">
      <span class="move-name">${m.name}</span>
      <span class="move-info">${m.element} · Pwr ${m.power} · Acc ${m.accuracy}%</span>
    </button>`;
    }
    // Build party swap buttons
    let swapHTML = '';
    for (let i = 0; i < battleParty.length; i++) {
        if (i === bs.activeIndex)
            continue;
        const r = battleParty[i];
        const hpPct = Math.max(0, (r.currentHp / r.maxHp) * 100);
        swapHTML += `<button class="swap-btn" data-swap="${i}" ${r.currentHp <= 0 ? 'disabled' : ''}>
      <span>${r.name} Lv.${r.level}</span>
      <div class="mini-hp-bar"><div style="width:${hpPct}%"></div></div>
    </button>`;
    }
    // Build inventory buttons
    let invHTML = '';
    for (const item of playerState.inventory) {
        if (item.quantity <= 0)
            continue;
        const icon = item.type === 'prism' ? '💎' : item.type === 'heal' ? '❤️' : item.type === 'status' ? '✨' : '⚡';
        invHTML += `<button class="inv-btn" data-item="${item.id}">
      ${icon} ${item.name} (×${item.quantity})
    </button>`;
    }
    // Message log
    const messages = battleEngine.getMessages();
    let msgHTML = '';
    for (const m of messages.slice(-3)) {
        msgHTML += `<div class="battle-msg">${m}</div>`;
    }
    screens.battle.innerHTML = `
    <div class="battle-container">
      <!-- Enemy side -->
      <div class="enemy-side">
        <div class="resonant-card glass-card enemy-card" id="enemy-card">
          <div class="resonant-info">
            <h3>${enemy.name}</h3>
            <span class="level-badge">Lv.${enemy.level}</span>
            ${enemy.statusEffect ? `<span class="status-badge">${enemy.statusEffect.type}</span>` : ''}
          </div>
          <div class="hp-bar-container">
            <div class="hp-bar" style="width:${enemyHpPct}%; --hp-color: ${getHpColor(enemyHpPct)}"></div>
          </div>
          <div class="hp-text">${enemy.currentHp}/${enemy.maxHp}</div>
        </div>
        <div class="resonant-sprite enemy-sprite" id="enemy-sprite">${enemySvg}</div>
      </div>

      <!-- Player side -->
      <div class="player-side">
        <div class="resonant-sprite player-sprite" id="player-sprite">${playerSvg}</div>
        <div class="resonant-card glass-card player-card" id="player-card">
          <div class="resonant-info">
            <h3>${playerR.name}</h3>
            <span class="level-badge">Lv.${playerR.level}</span>
            ${playerR.statusEffect ? `<span class="status-badge">${playerR.statusEffect.type}</span>` : ''}
          </div>
          <div class="hp-bar-container">
            <div class="hp-bar" style="width:${playerHpPct}%; --hp-color: ${getHpColor(playerHpPct)}"></div>
          </div>
          <div class="xp-bar-container">
            <div class="xp-bar" style="width:${playerXpPct}%"></div>
          </div>
          <div class="stats-row">
            <span>${playerR.currentHp}/${playerR.maxHp} HP</span>
            <span>XP ${playerR.xp}/${playerR.xpToNext}</span>
          </div>
        </div>
      </div>

      <!-- Message log -->
      <div class="battle-log glass-card" id="battle-log">${msgHTML || '<div class="battle-msg">What will you do?</div>'}</div>

      <!-- Action buttons -->
      <div class="battle-actions" id="battle-actions">
        ${bs.turnPhase === 'player' ? `
          <div class="action-tabs">
            <button class="tab-btn active" data-tab="attack">⚔️ Attack</button>
            <button class="tab-btn" data-tab="prism">💎 Prism</button>
            <button class="tab-btn" data-tab="swap">🔄 Swap</button>
            <button class="tab-btn" data-tab="items">🎒 Items</button>
            ${bs.isWild ? '<button class="tab-btn" data-tab="flee">🏃 Flee</button>' : ''}
          </div>
          <div class="action-panel active" id="panel-attack">${movesHTML}</div>
          <div class="action-panel" id="panel-prism">
            ${playerState.inventory.filter(i => i.type === 'prism' && i.quantity > 0).map(item => `<button class="inv-btn prism-btn" data-prism="${item.id}">${item.name} (×${item.quantity})</button>`).join('') || '<p>No prisms available. Visit the shop!</p>'}
          </div>
          <div class="action-panel" id="panel-swap">${swapHTML || '<p>No other Resonants to swap.</p>'}</div>
          <div class="action-panel" id="panel-items">${invHTML || '<p>No items available.</p>'}</div>
        ` : '<div class="waiting-text">Battle in progress...</div>'}
      </div>
    </div>
  `;
    // Bind event listeners
    bindBattleEvents();
}
function getHpColor(pct) {
    if (pct > 50)
        return '#22c55e';
    if (pct > 25)
        return '#eab308';
    return '#ef4444';
}
function bindBattleEvents() {
    // Tab switching
    document.querySelectorAll('.tab-btn').forEach(btn => {
        btn.addEventListener('click', () => {
            sound_1.soundManager.hover();
            const tab = btn.dataset.tab;
            if (!tab)
                return;
            document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
            btn.classList.add('active');
            document.querySelectorAll('.action-panel').forEach(p => p.classList.remove('active'));
            const panel = document.getElementById(`panel-${tab}`);
            if (panel)
                panel.classList.add('active');
        });
    });
    // Move buttons
    document.querySelectorAll('.move-btn').forEach(btn => {
        btn.addEventListener('click', async () => {
            sound_1.soundManager.select();
            const moveIdx = parseInt(btn.dataset.move);
            await executePlayerMove(moveIdx);
        });
    });
    // Prism buttons
    document.querySelectorAll('.prism-btn').forEach(btn => {
        btn.addEventListener('click', async () => {
            sound_1.soundManager.select();
            const itemId = btn.dataset.prism;
            await executePrism(itemId);
        });
    });
    // Swap buttons
    document.querySelectorAll('.swap-btn').forEach(btn => {
        btn.addEventListener('click', async () => {
            sound_1.soundManager.select();
            const swapIdx = parseInt(btn.dataset.swap);
            await executeSwap(swapIdx);
        });
    });
    // Item buttons
    document.querySelectorAll('.inv-btn').forEach(btn => {
        btn.addEventListener('click', async () => {
            sound_1.soundManager.select();
            const itemId = btn.dataset.item;
            await executeItem(itemId);
        });
    });
    // Flee button
    document.querySelector('[data-tab="flee"]')?.addEventListener('click', async () => {
        sound_1.soundManager.select();
        await executeFlee();
    });
}
// ==================== BATTLE ACTIONS ====================
async function executePlayerMove(moveIdx) {
    if (animatingBattle)
        return;
    animatingBattle = true;
    const bs = battleEngine.getBattle();
    const playerR = battleParty[bs.activeIndex];
    const tmpl = (0, resonants_1.getResonantById)(playerR.templateId);
    const availableMoves = tmpl?.moves.filter(m => m.unlockLevel <= playerR.level) || [];
    const move = availableMoves[moveIdx];
    if (!playerR || !move) {
        animatingBattle = false;
        return;
    }
    // Status effect damage at start of turn
    if (playerR.statusEffect) {
        const statusDmg = Math.floor(playerR.maxHp * 0.1);
        playerR.currentHp = Math.max(0, playerR.currentHp - statusDmg);
        battleEngine.addMessage(`${playerR.name} is hurt by its ${playerR.statusEffect.type}!`);
        if (playerR.statusEffect.turnsLeft !== undefined) {
            // @ts-ignore
            playerR.statusEffect.turnsLeft--;
            if (playerR.statusEffect.turnsLeft <= 0)
                playerR.statusEffect = null;
        }
        renderBattle();
        await delay(500);
        if (playerR.currentHp <= 0) {
            animatingBattle = false;
            await handlePlayerFaint();
            return;
        }
    }
    // Accuracy check
    if (Math.random() * 100 > move.accuracy) {
        battleEngine.addMessage(`${playerR.name}'s ${move.name} missed!`);
        renderBattle();
        await delay(800);
        animatingBattle = false;
        bs.turnPhase = 'enemy';
        await executeEnemyTurn();
        return;
    }
    // Calculate damage
    const result = (0, player_1.calculateDamage)(playerR, bs.enemy, move.power, move.element);
    let hitMsg = `${playerR.name} used ${move.name}!`;
    if (result.isCrit)
        hitMsg += ' Critical hit!';
    if (result.typeMod > 1)
        hitMsg += " It's super effective!";
    else if (result.typeMod < 1)
        hitMsg += " It's not very effective...";
    battleEngine.addMessage(hitMsg);
    // Play sound based on effectiveness
    if (result.typeMod > 1)
        sound_1.soundManager.hitSuperEffective();
    else if (move.element !== 'Cosmos')
        sound_1.soundManager.hitElemental();
    else
        sound_1.soundManager.hitPhysical();
    renderBattle();
    await delay(600);
    // Animate hit on enemy
    const enemyCard = document.getElementById('enemy-card');
    if (enemyCard) {
        enemyCard.classList.add('shake');
        setTimeout(() => enemyCard.classList.remove('shake'), 300);
    }
    bs.enemy.currentHp = Math.max(0, bs.enemy.currentHp - result.damage);
    // Status effect chance
    if (move.element !== 'Cosmos' && !bs.enemy.statusEffect && Math.random() < 0.15) {
        const statusTypes = ['burn', 'paralyze', 'poison'];
        bs.enemy.statusEffect = { type: statusTypes[Math.floor(Math.random() * statusTypes.length)], turnsLeft: 3 };
        battleEngine.addMessage(`${bs.enemy.name} got a ${bs.enemy.statusEffect.type}!`);
        spawnStatusParticles(bs.enemy.statusEffect.type, 'enemy-card');
    }
    renderBattle();
    await delay(500);
    // Check enemy faint
    if (bs.enemy.currentHp <= 0) {
        animatingBattle = false;
        await handleEnemyDefeated();
        return;
    }
    // Enemy turn
    bs.turnPhase = 'enemy';
    renderBattle();
    await delay(500);
    await executeEnemyTurn();
}
async function executePrism(itemId) {
    if (animatingBattle)
        return;
    animatingBattle = true;
    const bs = battleEngine.getBattle();
    const playerR = battleParty[bs.activeIndex];
    if (!playerR) {
        animatingBattle = false;
        return;
    }
    // Find and consume prism
    const prismItem = playerState.inventory.find(i => i.id === itemId);
    if (!prismItem || prismItem.quantity <= 0) {
        animatingBattle = false;
        return;
    }
    prismItem.quantity--;
    if (prismItem.quantity <= 0) {
        const idx = playerState.inventory.indexOf(prismItem);
        if (idx >= 0)
            playerState.inventory.splice(idx, 1);
    }
    battleEngine.addMessage(`${playerR.name} threw an Aether Prism!`);
    renderBattle();
    await delay(800);
    // Capture formula
    const hpRatio = bs.enemy.currentHp / bs.enemy.maxHp;
    let catchRate = (prismItem.catchRate || 0.45) * (1 - hpRatio * 0.7);
    if (bs.enemy.statusEffect)
        catchRate *= 1.5;
    if (prismBoostActive) {
        catchRate *= 2;
        prismBoostActive = false;
    }
    // Dramatic shake sequence
    const maxShakes = Math.min(4, Math.floor(catchRate * 6));
    battleEngine.addMessage('The Prism is shaking...');
    renderBattle();
    for (let i = 0; i < maxShakes; i++) {
        await delay(700);
        const enemySprite = document.getElementById('enemy-sprite');
        if (enemySprite) {
            enemySprite.classList.add('shake');
            setTimeout(() => enemySprite.classList.remove('shake'), 300);
        }
    }
    const captured = Math.random() < catchRate;
    await delay(400);
    if (captured) {
        sound_1.soundManager.captureSuccess();
        battleEngine.addMessage(`${bs.enemy.name} was captured!`);
        renderBattle();
        await delay(800);
        const capturedR = (0, player_1.createResonant)(bs.enemy.templateId, bs.enemy.level);
        capturedR.currentHp = Math.max(1, Math.floor(capturedR.maxHp * 0.3));
        capturedR.statusEffect = null;
        if (playerState.party.length < 6) {
            playerState.party.push(capturedR);
            battleEngine.addMessage(`${capturedR.name} joined your party!`);
        }
        else {
            battleEngine.addMessage(`Party is full. ${capturedR.name} was released.`);
        }
        (0, player_1.saveGame)(playerState);
        animatingBattle = false;
        await delay(500);
        returnToMap();
    }
    else {
        sound_1.soundManager.captureFail();
        battleEngine.addMessage(`${bs.enemy.name} broke free!`);
        renderBattle();
        await delay(600);
        animatingBattle = false;
        bs.turnPhase = 'enemy';
        await executeEnemyTurn();
    }
}
async function executeSwap(swapIdx) {
    if (animatingBattle)
        return;
    const bs = battleEngine.getBattle();
    const target = battleParty[swapIdx];
    if (!target || target.currentHp <= 0)
        return;
    sound_1.soundManager.select();
    bs.activeIndex = swapIdx;
    battleEngine.addMessage(`Go, ${target.name}!`);
    renderBattle();
    await delay(500);
    // Enemy still gets a turn after swap
    bs.turnPhase = 'enemy';
    await executeEnemyTurn();
}
async function executeItem(itemId) {
    if (animatingBattle)
        return;
    animatingBattle = true;
    const bs = battleEngine.getBattle();
    const playerR = battleParty[bs.activeIndex];
    if (!playerR) {
        animatingBattle = false;
        return;
    }
    const item = playerState.inventory.find(i => i.id === itemId);
    if (!item || item.quantity <= 0) {
        animatingBattle = false;
        return;
    }
    item.quantity--;
    if (item.quantity <= 0) {
        const idx = playerState.inventory.indexOf(item);
        if (idx >= 0)
            playerState.inventory.splice(idx, 1);
    }
    switch (item.type) {
        case 'heal': {
            const healAmount = Math.floor(playerR.maxHp * 0.5);
            playerR.currentHp = Math.min(playerR.maxHp, playerR.currentHp + healAmount);
            battleEngine.addMessage(`${playerR.name} recovered ${healAmount} HP!`);
            break;
        }
        case 'status': {
            if (playerR.statusEffect) {
                battleEngine.addMessage(`${playerR.name}'s status was cured!`);
                playerR.statusEffect = null;
            }
            else {
                battleEngine.addMessage(`${playerR.name} had no status to cure.`);
            }
            break;
        }
        case 'boost': {
            prismBoostActive = true;
            battleEngine.addMessage('Prism power boosted! Next capture attempt will be stronger.');
            animatingBattle = false;
            renderBattle();
            return;
        }
    }
    sound_1.soundManager.select();
    renderBattle();
    await delay(600);
    animatingBattle = false;
    bs.turnPhase = 'enemy';
    await executeEnemyTurn();
}
async function executeFlee() {
    if (animatingBattle)
        return;
    const bs = battleEngine.getBattle();
    if (!bs.isWild) {
        battleEngine.addMessage("Can't flee from a Conductor battle!");
        renderBattle();
        await delay(500);
        return;
    }
    if (Math.random() < 0.7) {
        sound_1.soundManager.select();
        battleEngine.addMessage('Got away safely!');
        renderBattle();
        await delay(400);
        animatingBattle = false;
        returnToMap();
    }
    else {
        battleEngine.addMessage("Couldn't escape!");
        renderBattle();
        await delay(500);
        bs.turnPhase = 'enemy';
        await executeEnemyTurn();
    }
}
// ==================== ENEMY TURN ====================
async function executeEnemyTurn() {
    const bs = battleEngine.getBattle();
    const playerR = battleParty[bs.activeIndex];
    if (!playerR)
        return;
    // Enemy status effect damage
    if (bs.enemy.statusEffect && Math.random() < 0.33) {
        const statusDmg = Math.floor(bs.enemy.maxHp * 0.1);
        bs.enemy.currentHp = Math.max(0, bs.enemy.currentHp - statusDmg);
        battleEngine.addMessage(`${bs.enemy.name} is hurt by its ${bs.enemy.statusEffect.type}!`);
        if (bs.enemy.statusEffect.turnsLeft !== undefined) {
            // @ts-ignore
            bs.enemy.statusEffect.turnsLeft--;
            if (bs.enemy.statusEffect.turnsLeft <= 0)
                bs.enemy.statusEffect = null;
        }
        renderBattle();
        await delay(500);
        if (bs.enemy.currentHp <= 0) {
            animatingBattle = false;
            await handleEnemyDefeated();
            return;
        }
    }
    // Enemy AI: choose move
    const enemyTmpl = (0, resonants_1.getResonantById)(bs.enemy.templateId);
    if (!enemyTmpl)
        return;
    const availableMoves = enemyTmpl.moves.filter(m => m.unlockLevel <= bs.enemy.level);
    let chosenMove = availableMoves[Math.floor(Math.random() * availableMoves.length)];
    // Prefer super-effective moves
    for (const move of availableMoves) {
        const playerType = (0, resonants_1.getResonantById)(playerR.templateId)?.type ?? 'Cosmos';
        if (getTypeAdvantage(move.element, playerType) > 1) {
            chosenMove = move;
            break;
        }
    }
    // Paralyzed enemy might be unable to move
    if (bs.enemy.statusEffect?.type === 'paralyze' && Math.random() < 0.25) {
        battleEngine.addMessage(`${bs.enemy.name} is paralyzed and can't move!`);
        renderBattle();
        await delay(600);
        bs.turnPhase = 'player';
        animatingBattle = false;
        renderBattle();
        return;
    }
    const result = (0, player_1.calculateDamage)(bs.enemy, playerR, chosenMove.power, chosenMove.element);
    let hitMsg = `${bs.enemy.name} used ${chosenMove.name}!`;
    if (result.isCrit)
        hitMsg += ' Critical hit!';
    if (result.typeMod > 1)
        hitMsg += " It's super effective!";
    else if (result.typeMod < 1)
        hitMsg += " It's not very effective...";
    battleEngine.addMessage(hitMsg);
    if (result.typeMod > 1)
        sound_1.soundManager.hitSuperEffective();
    else if (chosenMove.element !== 'Cosmos')
        sound_1.soundManager.hitElemental();
    else
        sound_1.soundManager.hitPhysical();
    renderBattle();
    await delay(600);
    // Animate hit on player
    const playerCard = document.getElementById('player-card');
    if (playerCard) {
        playerCard.classList.add('shake');
        setTimeout(() => playerCard.classList.remove('shake'), 300);
    }
    playerR.currentHp = Math.max(0, playerR.currentHp - result.damage);
    // Status effect chance on player
    if (chosenMove.element !== 'Cosmos' && !playerR.statusEffect && Math.random() < 0.12) {
        const statusTypes = ['burn', 'paralyze', 'poison'];
        playerR.statusEffect = { type: statusTypes[Math.floor(Math.random() * statusTypes.length)], turnsLeft: 3 };
        battleEngine.addMessage(`${playerR.name} got a ${playerR.statusEffect.type}!`);
        spawnStatusParticles(playerR.statusEffect.type, 'player-card');
    }
    renderBattle();
    await delay(500);
    // Check player faint
    if (playerR.currentHp <= 0) {
        animatingBattle = false;
        await handlePlayerFaint();
        return;
    }
    bs.turnPhase = 'player';
    animatingBattle = false;
    renderBattle();
}
// ==================== BATTLE RESOLUTIONS ====================
async function handleEnemyDefeated() {
    const bs = battleEngine.getBattle();
    sound_1.soundManager.victory();
    const xpGained = Math.floor(bs.enemy.level * 15 + 20);
    const shardsGained = Math.floor(bs.enemy.level * 8 + 10);
    // Apply XP to active resonant
    const playerR = battleParty[bs.activeIndex];
    if (playerR) {
        const result = (0, player_1.gainXp)(playerR, xpGained);
        battleEngine.addMessage(`${playerR.name} gained ${xpGained} XP!`);
        // Check for level up
        if (result.leveledUp) {
            sound_1.soundManager.levelUp();
            battleEngine.addMessage(`${playerR.name} grew to Lv.${playerR.level}!`);
            renderBattle();
            await delay(800);
            // Check evolution
            const evoId = (0, player_1.checkEvolution)(playerR);
            if (evoId) {
                sound_1.soundManager.evolution();
                battleEngine.addMessage(`${playerR.name} is evolving!`);
                renderBattle();
                await delay(1000);
                (0, player_1.evolveResonant)(playerR, evoId);
                battleEngine.addMessage(`It evolved into ${playerR.name}!`);
                renderBattle();
                await delay(800);
            }
        }
    }
    // Add shards
    playerState.shards += shardsGained;
    battleEngine.addMessage(`You earned ${shardsGained} Aether Shards! 💎`);
    renderBattle();
    await delay(1000);
    // Check if this was a boss battle
    const zone = zones_1.ZONES[playerState.currentZone];
    const currentNode = zone.nodes[playerState.currentNodeInZone];
    if (currentNode.type === 'boss') {
        const bossId = zones_1.BOSS_IDS[playerState.currentZone];
        if (bossId && !playerState.completedBosses.includes(bossId)) {
            playerState.completedBosses.push(bossId);
            // Unlock next zone
            if (playerState.currentZone < zones_1.ZONES.length - 1) {
                playerState.unlockedZones = Math.max(playerState.unlockedZones, playerState.currentZone + 2);
                battleEngine.addMessage(`🎉 ${zone.name} cleared! The path to the next zone is open!`);
            }
            else {
                // Final boss defeated - victory!
                (0, player_1.saveGame)(playerState);
                animatingBattle = false;
                showScreen('victory');
                renderVictory();
                return;
            }
        }
    }
    // Sync battle party back to player state
    syncPartyToPlayer();
    advanceNode();
    (0, player_1.saveGame)(playerState);
    animatingBattle = false;
    await delay(500);
    returnToMap();
}
async function handlePlayerFaint() {
    sound_1.soundManager.defeat();
    // Check if any resonants are still alive
    const aliveResonants = battleParty.filter(r => r.currentHp > 0);
    if (aliveResonants.length === 0) {
        battleEngine.addMessage('All your Resonants have fainted!');
        renderBattle();
        await delay(1500);
        // Heal party and return to map
        for (const r of playerState.party) {
            r.currentHp = Math.max(1, Math.floor(r.maxHp * 0.3));
            r.statusEffect = null;
        }
        (0, player_1.saveGame)(playerState);
        animatingBattle = false;
        await delay(500);
        returnToMap();
        return;
    }
    // Auto-switch to next alive resonant
    for (let i = 0; i < battleParty.length; i++) {
        if (battleParty[i].currentHp > 0 && i !== battleEngine.getBattle().activeIndex) {
            battleEngine.getBattle().activeIndex = i;
            battleEngine.addMessage(`Go, ${battleParty[i].name}!`);
            renderBattle();
            await delay(500);
            // Enemy gets a turn after switch
            animatingBattle = false;
            battleEngine.getBattle().turnPhase = 'enemy';
            await executeEnemyTurn();
            return;
        }
    }
}
function syncPartyToPlayer() {
    for (let i = 0; i < Math.min(battleParty.length, playerState.party.length); i++) {
        playerState.party[i] = battleParty[i];
    }
}
function returnToMap() {
    showScreen('map');
    renderMap();
}
// ==================== CACHE & HEALING ====================
async function openCache() {
    const reward = zones_1.CACHE_REWARDS[Math.floor(Math.random() * zones_1.CACHE_REWARDS.length)];
    let msg = '';
    if (reward.type === 'shards') {
        const amt = reward.amount ?? 0;
        playerState.shards += amt;
        msg = `You found ${amt} Aether Shards! 💎`;
    }
    else {
        const shopItem = zones_1.SHOP_ITEMS.find(s => s.id === reward.itemId);
        if (shopItem) {
            const qty = reward.quantity ?? 1;
            const existing = playerState.inventory.find(i => i.id === shopItem.id);
            if (existing) {
                existing.quantity += qty;
            }
            else {
                playerState.inventory.push({
                    id: shopItem.id,
                    name: shopItem.name,
                    description: shopItem.description,
                    quantity: qty,
                    type: shopItem.itemTypeId,
                    catchRate: shopItem.catchRate,
                });
            }
            msg = `You found ${qty}× ${shopItem.name}!`;
        }
    }
    showNotification(msg);
    advanceNode();
    (0, player_1.saveGame)(playerState);
    renderMap();
}
function healParty() {
    for (const r of playerState.party) {
        r.currentHp = r.maxHp;
        r.statusEffect = null;
    }
    showNotification('Your Resonants have been fully restored! 💚');
}
// ==================== PARTY SCREEN ====================
function renderParty() {
    let partyHTML = '';
    for (let i = 0; i < playerState.party.length; i++) {
        const r = playerState.party[i];
        const tmpl = (0, resonants_1.getResonantById)(r.templateId);
        const hpPct = Math.max(0, (r.currentHp / r.maxHp) * 100);
        const xpPct = Math.min(100, (r.xp / r.xpToNext) * 100);
        const svg = generateResonantSVG(r, 80);
        let movesList = '';
        if (tmpl) {
            for (const m of tmpl.moves) {
                const unlocked = m.unlockLevel <= r.level;
                movesList += `<span class="move-tag ${unlocked ? 'unlocked' : 'locked'}">${m.name} Lv.${m.unlockLevel}</span>`;
            }
        }
        partyHTML += `
      <div class="party-card glass-card">
        <div class="party-sprite">${svg}</div>
        <div class="party-info">
          <h3>${r.name} <span class="level-badge">Lv.${r.level}</span></h3>
          ${tmpl ? `<span class="type-badge" style="--badge-color: ${getTypeColor(tmpl.type)}">${tmpl.type}</span>` : ''}
          <div class="hp-bar-container"><div class="hp-bar" style="width:${hpPct}%; --hp-color: ${getHpColor(hpPct)}"></div></div>
          <div class="xp-bar-container"><div class="xp-bar" style="width:${xpPct}%"></div></div>
          <div class="stats-grid">
            <span>HP: ${r.maxHp}</span><span>ATK: ${r.atk}</span>
            <span>DEF: ${r.def}</span><span>SPD: ${r.spd}</span>
          </div>
          <div class="moves-list">${movesList}</div>
        </div>
      </div>
    `;
    }
    // Inventory section
    let invHTML = '';
    for (const item of playerState.inventory) {
        if (item.quantity <= 0)
            continue;
        const icon = item.type === 'prism' ? '💎' : item.type === 'heal' ? '❤️' : item.type === 'status' ? '✨' : '⚡';
        invHTML += `<div class="inv-item">${icon} ${item.name} ×${item.quantity}</div>`;
    }
    screens.party.innerHTML = `
    <div class="party-container">
      <h2>Party & Inventory</h2>
      <div class="party-grid">${partyHTML || '<p>No Resonants yet!</p>'}</div>
      ${playerState.party.length < 6 ? `<button id="btn-catch-info" class="action-btn">Capture more Resonants in the wild!</button>` : ''}
      <h3 style="margin-top:20px;">Inventory</h3>
      <div class="inventory-grid">${invHTML || '<p>No items.</p>'}</div>
      <button id="btn-back-party" class="action-btn back-btn">← Back to Map</button>
    </div>
  `;
    document.getElementById('btn-back-party').addEventListener('click', () => { showScreen('map'); renderMap(); });
}
// ==================== SHOP SCREEN ====================
function renderShop() {
    let shopHTML = '';
    for (const item of zones_1.SHOP_ITEMS) {
        const canAfford = playerState.shards >= item.price;
        shopHTML += `
      <div class="shop-item glass-card">
        <div class="shop-info">
          <h4>${item.name}</h4>
          <p>${item.description}</p>
        </div>
        <button class="buy-btn ${canAfford ? '' : 'disabled'}" data-shop-id="${item.id}" ${!canAfford ? 'disabled' : ''}>
          Buy — ${item.price} 💎
        </button>
      </div>
    `;
    }
    screens.shop.innerHTML = `
    <div class="shop-container">
      <h2>Camp Shop</h2>
      <p class="shards-display">Aether Shards: ${playerState.shards} 💎</p>
      <div class="shop-grid">${shopHTML}</div>
      <button id="btn-back-shop" class="action-btn back-btn">← Back to Map</button>
    </div>
  `;
    document.querySelectorAll('.buy-btn:not(.disabled)').forEach(btn => {
        btn.addEventListener('click', () => {
            const shopId = btn.dataset.shopId;
            buyItem(shopId);
        });
    });
    document.getElementById('btn-back-shop').addEventListener('click', () => { showScreen('map'); renderMap(); });
}
function buyItem(shopId) {
    const shopItem = zones_1.SHOP_ITEMS.find(s => s.id === shopId);
    if (!shopItem || playerState.shards < shopItem.price)
        return;
    playerState.shards -= shopItem.price;
    sound_1.soundManager.select();
    const existing = playerState.inventory.find(i => i.id === shopItem.id);
    if (existing) {
        existing.quantity++;
    }
    else {
        playerState.inventory.push({
            id: shopItem.id,
            name: shopItem.name,
            description: shopItem.description,
            quantity: 1,
            type: shopItem.itemTypeId,
            catchRate: shopItem.catchRate,
        });
    }
    (0, player_1.saveGame)(playerState);
    showNotification(`Bought ${shopItem.name}!`);
    renderShop();
}
// ==================== DEX SCREEN ====================
function renderDex() {
    const allResonants = Object.values(resonants_1.RESONANT_DB).filter(r => !zones_1.BOSS_IDS.includes(r.id));
    let dexHTML = '';
    for (const r of allResonants) {
        const seen = playerState.dexSeen.has(r.id);
        const svg = seen ? generateResonantSVG((0, player_1.createResonant)(r.id, 1), 60) : '<div class="dex-hidden">???</div>';
        dexHTML += `
      <div class="dex-card glass-card ${seen ? '' : 'hidden'}">
        <div class="dex-sprite">${svg}</div>
        ${seen ? `
          <h4>${r.name}</h4>
          <span class="type-badge" style="--badge-color: ${getTypeColor(r.type)}">${r.type}</span>
          <div class="dex-stats">
            HP:${r.baseHp} ATK:${r.baseAtk} DEF:${r.baseDef} SPD:${r.baseSpd}
          </div>
        ` : `<h4>Unknown</h4><p>Not yet encountered</p>`}
      </div>
    `;
    }
    screens.dex.innerHTML = `
    <div class="dex-container">
      <h2>Conductors Dex</h2>
      <p>${playerState.dexSeen.size}/${allResonants.length} Resonants discovered</p>
      <div class="dex-grid">${dexHTML}</div>
      <button id="btn-back-dex" class="action-btn back-btn">← Back</button>
    </div>
  `;
    document.getElementById('btn-back-dex').addEventListener('click', () => {
        const prevScreen = currentScreen === 'menu' ? 'menu' : 'map';
        showScreen(prevScreen);
        if (prevScreen === 'menu')
            renderMenu();
        else
            renderMap();
    });
}
// ==================== VICTORY SCREEN ====================
function renderVictory() {
    screens.victory.innerHTML = `
    <div class="victory-container">
      <h1>🌟 Victory! 🌟</h1>
      <p>You have defeated the Apex Conductor and restored harmony to Aethelgard!</p>
      <div class="victory-stats glass-card">
        <h3>Journey Complete</h3>
        <p>Resonants Discovered: ${playerState.dexSeen.size}/18</p>
        <p>Bosses Defeated: ${playerState.completedBosses.length}/4</p>
        <p>Total Shards Earned: ${playerState.shards} 💎</p>
      </div>
      <button id="btn-newgame-victory" class="menu-btn primary">Start New Journey</button>
    </div>
  `;
    document.getElementById('btn-newgame-victory').addEventListener('click', () => {
        (0, player_1.resetGame)();
        playerState = (0, player_1.createDefaultPlayer)();
        showScreen('menu');
        renderMenu();
    });
}
// ==================== UTILITIES ====================
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
function getTypeAdvantage(attackerType, defenderType) {
    const chart = {
        Pyro: { Flora: 1.5, Hydro: 0.67 },
        Hydro: { Pyro: 1.5, Flora: 0.67 },
        Flora: { Hydro: 1.5, Pyro: 0.67 },
        Zephyr: { Earth: 1.5, Volt: 0.67 },
        Earth: { Volt: 1.5, Zephyr: 0.67 },
        Volt: { Zephyr: 1.5, Earth: 0.67 },
    };
    return chart[attackerType]?.[defenderType] ?? 1.0;
}
function getTypeColor(type) {
    const colors = {
        Pyro: '#FF6B35', Hydro: '#4CC9F0', Flora: '#2D6A4F',
        Zephyr: '#A2D2FF', Earth: '#8B7355', Volt: '#FFD700', Cosmos: '#C77DFF',
    };
    return colors[type] || '#888';
}
function generateResonantSVG(r, size = 120) {
    // Delegate to the renderer module via window reference
    const fn = window.__generateResonantSVG;
    if (fn)
        return fn(r, size);
    // Fallback simple SVG
    const tmpl = (0, resonants_1.getResonantById)(r.templateId);
    if (!tmpl)
        return '';
    const color = getTypeColor(tmpl.type);
    return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}"><circle cx="${size / 2}" cy="${size / 2}" r="${size * 0.35}" fill="${color}"/><text x="${size / 2}" y="${size / 2 + 8}" text-anchor="middle" fill="white" font-size="14">${r.name.charAt(0)}</text></svg>`;
}
function showNotification(msg) {
    const existing = document.querySelector('.notification');
    if (existing)
        existing.remove();
    const notif = document.createElement('div');
    notif.className = 'notification';
    notif.textContent = msg;
    document.body.appendChild(notif);
    setTimeout(() => notif.remove(), 2500);
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"typescript","sessionId":"255d2649-41c0-40ea-9323-9744fe02e2dc","tokensIn":4390755,"tokensOut":63164,"tokensTotal":4453919,"turns":83,"toolCalls":82,"failedToolCalls":9,"timestamp":"2026-07-30T15:37:23.368Z"} -->