Aethelgard
thinkingcap qwen3.6 27b · typescript

Browser check: Artifact runtime error. This is a technical check, not a quality review.
Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 95,407 bytes · SHA-256 a0dfec65635b
<!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&family=Press+Start+2P&display=swap">
<style>
/* ── css: base ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; }
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #0f0a1e 0%, #1a1040 30%, #0d1b2a 70%, #0a0618 100%);
color: #e2e8f0;
}
#app { width: 100%; height: 100%; position: relative; overflow: hidden; }
.game-screen {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center;
opacity: 0; pointer-events: none; transition: opacity 0.4s ease, transform 0.4s ease;
transform: scale(0.97); padding: 16px;
}
.game-screen.active { opacity: 1; pointer-events: auto; transform: scale(1); }
.hidden { display: none !important; }
.retro-title { font-family: 'Press Start 2P', monospace; letter-spacing: 1px; }
.glass-card {
background: rgba(30, 27, 56, 0.65);
backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
border: 1px solid rgba(139, 92, 246, 0.25);
border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06);
}
.glass-btn {
background: rgba(139, 92, 246, 0.2); border: 1px solid rgba(139, 92, 246, 0.4);
color: #e2e8f0; padding: 10px 24px; border-radius: 10px; cursor: pointer;
font-family: 'Inter', sans-serif; font-weight: 600; font-size: 14px;
transition: all 0.2s ease; backdrop-filter: blur(8px);
}
.glass-btn:hover { background: rgba(139, 92, 246, 0.4); border-color: rgba(139, 92, 246, 0.7); transform: translateY(-1px); box-shadow: 0 4px 16px rgba(139,92,246,0.3); }
.glass-btn:active { transform: translateY(0); }
.glass-btn.danger { background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.4); }
.glass-btn.danger:hover { background: rgba(239, 68, 68, 0.4); box-shadow: 0 4px 16px rgba(239,68,68,0.3); }
.glass-btn.success { background: rgba(34, 197, 94, 0.2); border-color: rgba(34, 197, 94, 0.4); }
.glass-btn.success:hover { background: rgba(34, 197, 94, 0.4); box-shadow: 0 4px 16px rgba(34,197,94,0.3); }
/* ── css: animations ── */
@keyframes shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-6px) rotate(-1deg)} 75%{transform:translateX(6px) rotate(1deg)} }
@keyframes flashHit { 0%{filter:brightness(1)} 30%{filter:brightness(2.5)} 100%{filter:brightness(1)} }
@keyframes bounceIn { 0%{transform:scale(0);opacity:0} 60%{transform:scale(1.1)} 100%{transform:scale(1);opacity:1} }
@keyframes floatUp { 0%{transform:translateY(0) scale(1);opacity:1} 100%{transform:translateY(-40px) scale(0.5);opacity:0} }
@keyframes pulseGlow { 0%,100%{box-shadow:0 0 8px var(--glow-color,rgba(139,92,246,0.4))} 50%{box-shadow:0 0 24px var(--glow-color,rgba(139,92,246,0.7))} }
@keyframes particleDrift { 0%{transform:translateY(0) translateX(0);opacity:1} 50%{opacity:0.8} 100%{transform:translateY(-30px) translateX(var(--drift,10px));opacity:0} }
@keyframes slideInRight { from{transform:translateX(60px);opacity:0} to{transform:translateX(0);opacity:1} }
@keyframes slideInLeft { from{transform:translateX(-60px);opacity:0} to{transform:translateX(0);opacity:1} }
@keyframes evolveFlash { 0%{filter:brightness(1)} 25%{filter:brightness(3) hue-rotate(90deg)} 50%{filter:brightness(4) hue-rotate(180deg)} 75%{filter:brightness(3) hue-rotate(270deg)} 100%{filter:brightness(1)} }
.shake { animation: shake 0.4s ease; }
.flash-hit { animation: flashHit 0.3s ease; }
.bounce-in { animation: bounceIn 0.5s ease forwards; }
.evolve-flash { animation: evolveFlash 1.2s ease; }
/* ── css: elements ── */
.el-pyro { --el-color: #f97316; --glow-color: rgba(249,115,22,0.5); }
.el-hydro { --el-color: #3b82f6; --glow-color: rgba(59,130,246,0.5); }
.el-flora { --el-color: #22c55e; --glow-color: rgba(34,197,94,0.5); }
.el-volt { --el-color: #facc15; --glow-color: rgba(250,204,21,0.5); }
.el-zephyr{ --el-color: #a78bfa; --glow-color: rgba(167,139,250,0.5); }
.el-earth { --el-color: #d97706; --glow-color: rgba(217,119,6,0.5); }
.el-cosmos{ --el-color: #c084fc; --glow-color: rgba(192,132,252,0.5); }
.type-badge { display:inline-block; padding:2px 10px; border-radius:6px; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.5px; color:#fff; background:var(--el-color,rgba(139,92,246,0.6)); box-shadow:0 0 8px var(--glow-color); }
.hp-bar-bg { width:100%; height:14px; background:rgba(0,0,0,0.5); border-radius:7px; overflow:hidden; position:relative; }
.hp-bar-fill { height:100%; border-radius:7px; transition:width 0.6s ease, background 0.3s ease; }
.xp-bar-bg { width:100%; height:6px; background:rgba(0,0,0,0.4); border-radius:3px; overflow:hidden; margin-top:4px; }
.xp-bar-fill { height:100%; background:linear-gradient(90deg,#8b5cf6,#c084fc); border-radius:3px; transition:width 0.4s ease; }
.resonant-sprite { width:120px; height:120px; filter:drop-shadow(0 0 12px var(--glow-color,rgba(139,92,246,0.5))); transition:filter 0.3s ease; }
.resonant-sprite.enemy { width:100px; height:100px; }
.toast-overlay { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; z-index:999; pointer-events:none; }
.toast-msg { background:rgba(30,27,56,0.9); backdrop-filter:blur(12px); border:1px solid rgba(139,92,246,0.4); padding:16px 32px; border-radius:12px; font-weight:600; animation:bounceIn 0.3s ease forwards; }
/* ── css: battle-particles ── */
.particle-container { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 5; }
.battle-particle {
position: absolute; width: 6px; height: 6px; border-radius: 50%;
animation: particleDrift 2s ease-out forwards;
}
.status-burn { color: #f97316; }
.status-paralyze { color: #facc15; }
.status-poison { color: #a855f7; }
.status-sleep { color: #60a5fa; }
/* Scrollbar styling */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 3px; }
::-webkit-scrollbar-thumb { background: rgba(139,92,246,0.4); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(139,92,246,0.6); }
/* Victory screen */
.victory-overlay {
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
background: rgba(0,0,0,0.7); z-index: 100; animation: bounceIn 0.5s ease;
}
.victory-card {
text-align: center; padding: 48px; max-width: 500px;
background: rgba(30,27,56,0.9); backdrop-filter: blur(20px);
border: 1px solid rgba(192,132,252,0.4); border-radius: 20px;
box-shadow: 0 0 60px rgba(192,132,252,0.3);
}
/* Map node pulse animation */
@keyframes nodePulse {
0%, 100% { box-shadow: 0 0 8px var(--node-color, #8b5cf6)40; }
50% { box-shadow: 0 0 20px var(--node-color, #8b5cf6)70; }
}
</style>
</head>
<body>
<div id="screen-menu" class="game-screen active"></div>
<div id="screen-map" class="game-screen hidden"></div>
<div id="screen-battle" class="game-screen hidden"></div>
<div id="screen-shop" class="game-screen hidden"></div>
<div id="screen-dex" class="game-screen hidden"></div>
<div id="screen-dialog" class="game-screen hidden"></div>
<div id="overlay-toast" class="toast-overlay hidden"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./ui/game":"src/ui/game.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/systems/combat.ts":{"../types":"src/types.ts","../data/resonants":"src/data/resonants.ts","../state/player":"src/state/player.ts"},"src/ui/sprites.ts":{"../data/resonants":"src/data/resonants.ts","../types":"src/types.ts"},"src/ui/game.ts":{"../types":"src/types.ts","../data/resonants":"src/data/resonants.ts","../data/zones":"src/data/zones.ts","../state/player":"src/state/player.ts","../systems/combat":"src/systems/combat.ts","../systems/synth":"src/systems/synth.ts","./sprites":"src/ui/sprites.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";
Object.defineProperty(exports, "__esModule", { value: true });
const game_1 = require("./ui/game");
// Initialize game on load
(0, game_1.init)();
};
// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TYPE_ADVANTAGES = exports.ELEMENT_COLORS = void 0;
exports.ELEMENT_COLORS = {
Pyro: '#f97316', Hydro: '#3b82f6', Flora: '#22c55e',
Volt: '#facc15', Zephyr: '#a78bfa', Earth: '#d97706', Cosmos: '#c084fc'
};
exports.TYPE_ADVANTAGES = {
Pyro: { strong: ['Flora'], weak: ['Hydro'] },
Hydro: { strong: ['Pyro'], weak: ['Flora'] },
Flora: { strong: ['Hydro'], weak: ['Pyro'] },
Zephyr: { strong: ['Earth'], weak: ['Volt'] },
Earth: { strong: ['Volt'], weak: ['Zephyr'] },
Volt: { strong: ['Zephyr'], weak: ['Earth'] },
Cosmos: { strong: [], weak: [] }
};
};
// ── module: src/data/resonants.ts ──
__mods["src/data/resonants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ALL_IDS = exports.RESONANT_DB = void 0;
exports.getResonantDef = getResonantDef;
exports.RESONANT_DB = {
// === PYRO LINE ===
emberkit: {
id: 'emberkit', name: 'Emberkit', type: 'Pyro', baseHp: 45, baseAtk: 38, baseDef: 28, baseSpd: 35,
evolutionLevel: 8, evolvesInto: 'pyrowolf',
moves: [
{ name: 'Ember Toss', power: 40, accuracy: 95, element: 'Pyro', unlockLevel: 1, pp: 20 },
{ name: 'Scratch', power: 30, accuracy: 100, element: 'Cosmos', unlockLevel: 1, pp: 25 },
{ name: 'Flame Burst', power: 65, accuracy: 90, element: 'Pyro', unlockLevel: 4, pp: 15 },
{ name: 'Inferno Wave', power: 85, accuracy: 85, element: 'Pyro', unlockLevel: 7, pp: 10 }
],
spriteParams: { shape: 'circle', primaryColor: '#f97316', secondaryColor: '#dc2626', accentColor: '#fef3c7', features: ['eyes', 'tail'] }
},
pyrowolf: {
id: 'pyrowolf', name: 'Pyrowolf', type: 'Pyro', baseHp: 70, baseAtk: 58, baseDef: 42, baseSpd: 50,
evolutionLevel: 15, evolvesInto: 'ignisaur',
moves: [
{ name: 'Flame Burst', power: 65, accuracy: 90, element: 'Pyro', unlockLevel: 1, pp: 15 },
{ name: 'Bite', power: 45, accuracy: 95, element: 'Cosmos', unlockLevel: 1, pp: 20 },
{ name: 'Fire Fang', power: 75, accuracy: 90, element: 'Pyro', unlockLevel: 8, pp: 12 },
{ name: 'Blaze Rush', power: 95, accuracy: 85, element: 'Pyro', unlockLevel: 12, pp: 8 }
],
spriteParams: { shape: 'hexagon', primaryColor: '#f97316', secondaryColor: '#dc2626', accentColor: '#fef3c7', features: ['eyes', 'horns', 'tail'] }
},
ignisaur: {
id: 'ignisaur', name: 'Ignisaur', type: 'Pyro', baseHp: 100, baseAtk: 85, baseDef: 65, baseSpd: 70,
moves: [
{ name: 'Fire Fang', power: 75, accuracy: 90, element: 'Pyro', unlockLevel: 1, pp: 12 },
{ name: 'Inferno Wave', power: 85, accuracy: 85, element: 'Pyro', unlockLevel: 1, pp: 10 },
{ name: 'Magma Storm', power: 110, accuracy: 80, element: 'Pyro', unlockLevel: 15, pp: 6 },
{ name: 'Solar Flare', power: 130, accuracy: 75, element: 'Pyro', unlockLevel: 20, pp: 4 }
],
spriteParams: { shape: 'hexagon', primaryColor: '#ef4444', secondaryColor: '#f97316', accentColor: '#fef3c7', features: ['eyes', 'horns', 'tail', 'aura'] }
},
// === HYDRO LINE ===
droplet: {
id: 'droplet', name: 'Droplet', type: 'Hydro', baseHp: 50, baseAtk: 32, baseDef: 35, baseSpd: 40,
evolutionLevel: 8, evolvesInto: 'geoxus',
moves: [
{ name: 'Water Splash', power: 35, accuracy: 100, element: 'Hydro', unlockLevel: 1, pp: 25 },
{ name: 'Tackle', power: 30, accuracy: 95, element: 'Cosmos', unlockLevel: 1, pp: 25 },
{ name: 'Aqua Jet', power: 60, accuracy: 95, element: 'Hydro', unlockLevel: 4, pp: 18 },
{ name: 'Tidal Surge', power: 75, accuracy: 90, element: 'Hydro', unlockLevel: 7, pp: 12 }
],
spriteParams: { shape: 'circle', primaryColor: '#3b82f6', secondaryColor: '#1d4ed8', accentColor: '#dbeafe', features: ['eyes', 'aura'] }
},
geoxus: {
id: 'geoxus', name: 'Geoxus', type: 'Hydro', baseHp: 75, baseAtk: 52, baseDef: 50, baseSpd: 45,
evolutionLevel: 15, evolvesInto: 'neptunian',
moves: [
{ name: 'Aqua Jet', power: 60, accuracy: 95, element: 'Hydro', unlockLevel: 1, pp: 18 },
{ name: 'Water Pulse', power: 70, accuracy: 90, element: 'Hydro', unlockLevel: 1, pp: 14 },
{ name: 'Tidal Surge', power: 75, accuracy: 90, element: 'Hydro', unlockLevel: 8, pp: 12 },
{ name: 'Deep Current', power: 90, accuracy: 85, element: 'Hydro', unlockLevel: 12, pp: 8 }
],
spriteParams: { shape: 'diamond', primaryColor: '#3b82f6', secondaryColor: '#1e40af', accentColor: '#bfdbfe', features: ['eyes', 'horns', 'aura'] }
},
neptunian: {
id: 'neptunian', name: 'Neptunian', type: 'Hydro', baseHp: 105, baseAtk: 80, baseDef: 72, baseSpd: 60,
moves: [
{ name: 'Tidal Surge', power: 75, accuracy: 90, element: 'Hydro', unlockLevel: 1, pp: 12 },
{ name: 'Deep Current', power: 90, accuracy: 85, element: 'Hydro', unlockLevel: 1, pp: 8 },
{ name: 'Ocean Fury', power: 115, accuracy: 80, element: 'Hydro', unlockLevel: 15, pp: 6 },
{ name: 'Tsunami Blast', power: 135, accuracy: 75, element: 'Hydro', unlockLevel: 20, pp: 4 }
],
spriteParams: { shape: 'diamond', primaryColor: '#2563eb', secondaryColor: '#1e3a8a', accentColor: '#93c5fd', features: ['eyes', 'horns', 'aura', 'crown'] }
},
// === FLORA LINE ===
seedling: {
id: 'seedling', name: 'Seedling', type: 'Flora', baseHp: 48, baseAtk: 30, baseDef: 32, baseSpd: 30,
evolutionLevel: 8, evolvesInto: 'thornback',
moves: [
{ name: 'Vine Whip', power: 35, accuracy: 100, element: 'Flora', unlockLevel: 1, pp: 25 },
{ name: 'Tackle', power: 30, accuracy: 95, element: 'Cosmos', unlockLevel: 1, pp: 25 },
{ name: 'Leaf Blade', power: 60, accuracy: 95, element: 'Flora', unlockLevel: 4, pp: 18 },
{ name: 'Thorn Barrage', power: 70, accuracy: 90, element: 'Flora', unlockLevel: 7, pp: 12 }
],
spriteParams: { shape: 'circle', primaryColor: '#22c55e', secondaryColor: '#16a34a', accentColor: '#bbf7d0', features: ['eyes', 'petals'] }
},
thornback: {
id: 'thornback', name: 'Thornback', type: 'Flora', baseHp: 72, baseAtk: 55, baseDef: 48, baseSpd: 38,
evolutionLevel: 15, evolvesInto: 'bloomgoliath',
moves: [
{ name: 'Leaf Blade', power: 60, accuracy: 95, element: 'Flora', unlockLevel: 1, pp: 18 },
{ name: 'Razor Leaf', power: 70, accuracy: 95, element: 'Flora', unlockLevel: 1, pp: 14 },
{ name: 'Thorn Barrage', power: 70, accuracy: 90, element: 'Flora', unlockLevel: 8, pp: 12 },
{ name: 'Root Crush', power: 90, accuracy: 85, element: 'Flora', unlockLevel: 12, pp: 8 }
],
spriteParams: { shape: 'hexagon', primaryColor: '#22c55e', secondaryColor: '#15803d', accentColor: '#bbf7d0', features: ['eyes', 'spikes', 'petals'] }
},
bloomgoliath: {
id: 'bloomgoliath', name: 'Bloomgoliath', type: 'Flora', baseHp: 108, baseAtk: 82, baseDef: 75, baseSpd: 45,
moves: [
{ name: 'Thorn Barrage', power: 70, accuracy: 90, element: 'Flora', unlockLevel: 1, pp: 12 },
{ name: 'Root Crush', power: 90, accuracy: 85, element: 'Flora', unlockLevel: 1, pp: 8 },
{ name: 'Verdant Storm', power: 115, accuracy: 80, element: 'Flora', unlockLevel: 15, pp: 6 },
{ name: 'World Bloom', power: 130, accuracy: 75, element: 'Flora', unlockLevel: 20, pp: 4 }
],
spriteParams: { shape: 'hexagon', primaryColor: '#16a34a', secondaryColor: '#14532d', accentColor: '#86efac', features: ['eyes', 'spikes', 'petals', 'crown'] }
},
// === VOLT LINE ===
sparky: {
id: 'sparky', name: 'Sparky', type: 'Volt', baseHp: 40, baseAtk: 36, baseDef: 25, baseSpd: 48,
evolutionLevel: 8, evolvesInto: 'boltsaber',
moves: [
{ name: 'Zap', power: 35, accuracy: 100, element: 'Volt', unlockLevel: 1, pp: 25 },
{ name: 'Quick Attack', power: 30, accuracy: 95, element: 'Cosmos', unlockLevel: 1, pp: 25 },
{ name: 'Thunder Shock', power: 60, accuracy: 95, element: 'Volt', unlockLevel: 4, pp: 18 },
{ name: 'Lightning Arc', power: 75, accuracy: 90, element: 'Volt', unlockLevel: 7, pp: 12 }
],
spriteParams: { shape: 'star', primaryColor: '#facc15', secondaryColor: '#eab308', accentColor: '#fef9c3', features: ['eyes', 'horns'] }
},
boltsaber: {
id: 'boltsaber', name: 'Boltsaber', type: 'Volt', baseHp: 68, baseAtk: 56, baseDef: 40, baseSpd: 62,
evolutionLevel: 15, evolvesInto: 'stormlord',
moves: [
{ name: 'Thunder Shock', power: 60, accuracy: 95, element: 'Volt', unlockLevel: 1, pp: 18 },
{ name: 'Spark Slash', power: 70, accuracy: 95, element: 'Volt', unlockLevel: 1, pp: 14 },
{ name: 'Lightning Arc', power: 75, accuracy: 90, element: 'Volt', unlockLevel: 8, pp: 12 },
{ name: 'Thunder Crash', power: 95, accuracy: 85, element: 'Volt', unlockLevel: 12, pp: 8 }
],
spriteParams: { shape: 'star', primaryColor: '#eab308', secondaryColor: '#a16207', accentColor: '#fde047', features: ['eyes', 'horns', 'tail'] }
},
stormlord: {
id: 'stormlord', name: 'Stormlord', type: 'Volt', baseHp: 95, baseAtk: 88, baseDef: 60, baseSpd: 80,
moves: [
{ name: 'Lightning Arc', power: 75, accuracy: 90, element: 'Volt', unlockLevel: 1, pp: 12 },
{ name: 'Thunder Crash', power: 95, accuracy: 85, element: 'Volt', unlockLevel: 1, pp: 8 },
{ name: 'Tempest Strike', power: 120, accuracy: 80, element: 'Volt', unlockLevel: 15, pp: 6 },
{ name: 'Omega Bolt', power: 140, accuracy: 70, element: 'Volt', unlockLevel: 20, pp: 4 }
],
spriteParams: { shape: 'star', primaryColor: '#ca8a04', secondaryColor: '#713f12', accentColor: '#fde047', features: ['eyes', 'horns', 'tail', 'aura'] }
},
// === ZEPHYR LINE ===
gusty: {
id: 'gusty', name: 'Gusty', type: 'Zephyr', baseHp: 42, baseAtk: 34, baseDef: 26, baseSpd: 50,
evolutionLevel: 8, evolvesInto: 'slipstream',
moves: [
{ name: 'Air Slash', power: 35, accuracy: 95, element: 'Zephyr', unlockLevel: 1, pp: 25 },
{ name: 'Tackle', power: 30, accuracy: 95, element: 'Cosmos', unlockLevel: 1, pp: 25 },
{ name: 'Gust Wave', power: 60, accuracy: 95, element: 'Zephyr', unlockLevel: 4, pp: 18 },
{ name: 'Wind Cutter', power: 70, accuracy: 90, element: 'Zephyr', unlockLevel: 7, pp: 12 }
],
spriteParams: { shape: 'diamond', primaryColor: '#a78bfa', secondaryColor: '#7c3aed', accentColor: '#ede9fe', features: ['eyes', 'wings'] }
},
slipstream: {
id: 'slipstream', name: 'Slipstream', type: 'Zephyr', baseHp: 65, baseAtk: 54, baseDef: 38, baseSpd: 65,
evolutionLevel: 15, evolvesInto: 'zephyrus',
moves: [
{ name: 'Gust Wave', power: 60, accuracy: 95, element: 'Zephyr', unlockLevel: 1, pp: 18 },
{ name: 'Air Cutter', power: 70, accuracy: 95, element: 'Zephyr', unlockLevel: 1, pp: 14 },
{ name: 'Wind Cutter', power: 70, accuracy: 90, element: 'Zephyr', unlockLevel: 8, pp: 12 },
{ name: 'Cyclone Spin', power: 90, accuracy: 85, element: 'Zephyr', unlockLevel: 12, pp: 8 }
],
spriteParams: { shape: 'diamond', primaryColor: '#7c3aed', secondaryColor: '#4c1d95', accentColor: '#ddd6fe', features: ['eyes', 'wings', 'tail'] }
},
zephyrus: {
id: 'zephyrus', name: 'Zephyrus', type: 'Zephyr', baseHp: 92, baseAtk: 84, baseDef: 55, baseSpd: 85,
moves: [
{ name: 'Wind Cutter', power: 70, accuracy: 90, element: 'Zephyr', unlockLevel: 1, pp: 12 },
{ name: 'Cyclone Spin', power: 90, accuracy: 85, element: 'Zephyr', unlockLevel: 1, pp: 8 },
{ name: 'Hurricane Fury', power: 115, accuracy: 80, element: 'Zephyr', unlockLevel: 15, pp: 6 },
{ name: 'Sky Rend', power: 130, accuracy: 75, element: 'Zephyr', unlockLevel: 20, pp: 4 }
],
spriteParams: { shape: 'diamond', primaryColor: '#6d28d9', secondaryColor: '#3b0764', accentColor: '#c4b5fd', features: ['eyes', 'wings', 'tail', 'aura'] }
},
// === EARTH LINE ===
pebble: {
id: 'pebble', name: 'Pebble', type: 'Earth', baseHp: 52, baseAtk: 30, baseDef: 40, baseSpd: 22,
evolutionLevel: 8, evolvesInto: 'obsidian',
moves: [
{ name: 'Rock Throw', power: 35, accuracy: 95, element: 'Earth', unlockLevel: 1, pp: 25 },
{ name: 'Tackle', power: 30, accuracy: 95, element: 'Cosmos', unlockLevel: 1, pp: 25 },
{ name: 'Stone Edge', power: 60, accuracy: 90, element: 'Earth', unlockLevel: 4, pp: 18 },
{ name: 'Boulder Slam', power: 75, accuracy: 85, element: 'Earth', unlockLevel: 7, pp: 12 }
],
spriteParams: { shape: 'triangle', primaryColor: '#d97706', secondaryColor: '#92400e', accentColor: '#fef3c7', features: ['eyes'] }
},
obsidian: {
id: 'obsidian', name: 'Obsidian', type: 'Earth', baseHp: 78, baseAtk: 52, baseDef: 60, baseSpd: 30,
evolutionLevel: 15, evolvesInto: 'terrasunder',
moves: [
{ name: 'Stone Edge', power: 60, accuracy: 90, element: 'Earth', unlockLevel: 1, pp: 18 },
{ name: 'Rock Blast', power: 70, accuracy: 90, element: 'Earth', unlockLevel: 1, pp: 14 },
{ name: 'Boulder Slam', power: 75, accuracy: 85, element: 'Earth', unlockLevel: 8, pp: 12 },
{ name: 'Quake Strike', power: 95, accuracy: 80, element: 'Earth', unlockLevel: 12, pp: 8 }
],
spriteParams: { shape: 'triangle', primaryColor: '#78350f', secondaryColor: '#451a03', accentColor: '#d6d3d1', features: ['eyes', 'spikes'] }
},
terrasunder: {
id: 'terrasunder', name: 'Terrasunder', type: 'Earth', baseHp: 112, baseAtk: 78, baseDef: 90, baseSpd: 35,
moves: [
{ name: 'Boulder Slam', power: 75, accuracy: 85, element: 'Earth', unlockLevel: 1, pp: 12 },
{ name: 'Quake Strike', power: 95, accuracy: 80, element: 'Earth', unlockLevel: 1, pp: 8 },
{ name: 'Mountain Crush', power: 120, accuracy: 75, element: 'Earth', unlockLevel: 15, pp: 6 },
{ name: 'Continental Shift', power: 135, accuracy: 70, element: 'Earth', unlockLevel: 20, pp: 4 }
],
spriteParams: { shape: 'triangle', primaryColor: '#451a03', secondaryColor: '#1c0f06', accentColor: '#d6d3d1', features: ['eyes', 'spikes', 'aura'] }
}
};
function getResonantDef(id) {
return exports.RESONANT_DB[id];
}
exports.ALL_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.CONDUCTOR_DATA = exports.BOSS_DATA = exports.ZONES = void 0;
exports.getZone = getZone;
exports.ZONES = {
verdant_canopy: {
id: 'verdant_canopy', name: 'The Verdant Canopy', description: 'A lush forest canopy alive with Flora and Zephyr Resonants.',
levelRange: [1, 5], elementBias: ['Flora', 'Zephyr'], color: '#22c55e',
nodes: [
{ id: 'vc_1', type: 'wild', label: 'Tall Grass', x: 15, y: 70, unlocked: true, completed: false },
{ id: 'vc_2', type: 'cache', label: 'Mystery Cache', x: 30, y: 60, unlocked: true, completed: false },
{ id: 'vc_3', type: 'wild', label: 'Tall Grass', x: 45, y: 72, unlocked: true, completed: false },
{ id: 'vc_4', type: 'spring', label: 'Healing Spring', x: 60, y: 58, unlocked: true, completed: false },
{ id: 'vc_5', type: 'conductor', label: 'Conductor Mira', x: 75, y: 68, unlocked: true, completed: false },
{ id: 'vc_6', type: 'wild', label: 'Tall Grass', x: 25, y: 40, unlocked: true, completed: false },
{ id: 'vc_7', type: 'cache', label: 'Mystery Cache', x: 50, y: 35, unlocked: true, completed: false },
{ id: 'vc_8', type: 'wild', label: 'Tall Grass', x: 70, y: 42, unlocked: true, completed: false },
{ id: 'vc_boss', type: 'boss', label: 'Gatekeeper: Bloomgoliath Alpha', x: 50, y: 15, unlocked: true, completed: false }
]
},
sunken_grotto: {
id: 'sunken_grotto', name: 'The Sunken Grotto', description: 'Underground caverns echoing with Hydro and Earth Resonants.',
levelRange: [5, 10], elementBias: ['Hydro', 'Earth'], color: '#3b82f6',
nodes: [
{ id: 'sg_1', type: 'wild', label: 'Crystal Pool', x: 15, y: 70, unlocked: false, completed: false },
{ id: 'sg_2', type: 'cache', label: 'Mystery Cache', x: 30, y: 60, unlocked: false, completed: false },
{ id: 'sg_3', type: 'wild', label: 'Crystal Pool', x: 45, y: 72, unlocked: false, completed: false },
{ id: 'sg_4', type: 'spring', label: 'Healing Spring', x: 60, y: 58, unlocked: false, completed: false },
{ id: 'sg_5', type: 'conductor', label: 'Conductor Kael', x: 75, y: 68, unlocked: false, completed: false },
{ id: 'sg_6', type: 'wild', label: 'Crystal Pool', x: 25, y: 40, unlocked: false, completed: false },
{ id: 'sg_7', type: 'cache', label: 'Mystery Cache', x: 50, y: 35, unlocked: false, completed: false },
{ id: 'sg_8', type: 'wild', label: 'Crystal Pool', x: 70, y: 42, unlocked: false, completed: false },
{ id: 'sg_boss', type: 'boss', label: 'Gatekeeper: Neptunian Prime', x: 50, y: 15, unlocked: false, completed: false }
]
},
obsidian_crags: {
id: 'obsidian_crags', name: 'The Obsidian Crags', description: 'Volcanic peaks crackling with Pyro and Volt energy.',
levelRange: [10, 15], elementBias: ['Pyro', 'Volt'], color: '#f97316',
nodes: [
{ id: 'oc_1', type: 'wild', label: 'Lava Fields', x: 15, y: 70, unlocked: false, completed: false },
{ id: 'oc_2', type: 'cache', label: 'Mystery Cache', x: 30, y: 60, unlocked: false, completed: false },
{ id: 'oc_3', type: 'wild', label: 'Lava Fields', x: 45, y: 72, unlocked: false, completed: false },
{ id: 'oc_4', type: 'spring', label: 'Healing Spring', x: 60, y: 58, unlocked: false, completed: false },
{ id: 'oc_5', type: 'conductor', label: 'Conductor Vex', x: 75, y: 68, unlocked: false, completed: false },
{ id: 'oc_6', type: 'wild', label: 'Lava Fields', x: 25, y: 40, unlocked: false, completed: false },
{ id: 'oc_7', type: 'cache', label: 'Mystery Cache', x: 50, y: 35, unlocked: false, completed: false },
{ id: 'oc_8', type: 'wild', label: 'Lava Fields', x: 70, y: 42, unlocked: false, completed: false },
{ id: 'oc_boss', type: 'boss', label: 'Gatekeeper: Ignisaur Omega', x: 50, y: 15, unlocked: false, completed: false }
]
},
apex_pillar: {
id: 'apex_pillar', name: 'The Apex Pillar', description: 'The final ascent — a gauntlet of the strongest Resonants.',
levelRange: [15, 20], elementBias: ['Cosmos'], color: '#c084fc',
nodes: [
{ id: 'ap_1', type: 'wild', label: 'Aether Storm', x: 15, y: 70, unlocked: false, completed: false },
{ id: 'ap_2', type: 'cache', label: 'Mystery Cache', x: 30, y: 60, unlocked: false, completed: false },
{ id: 'ap_3', type: 'wild', label: 'Aether Storm', x: 45, y: 72, unlocked: false, completed: false },
{ id: 'ap_4', type: 'spring', label: 'Healing Spring', x: 60, y: 58, unlocked: false, completed: false },
{ id: 'ap_5', type: 'conductor', label: 'Conductor Zephyra', x: 75, y: 68, unlocked: false, completed: false },
{ id: 'ap_6', type: 'wild', label: 'Aether Storm', x: 25, y: 40, unlocked: false, completed: false },
{ id: 'ap_7', type: 'cache', label: 'Mystery Cache', x: 50, y: 35, unlocked: false, completed: false },
{ id: 'ap_boss1', type: 'boss', label: 'Boss: Stormlord Zenith', x: 25, y: 20, unlocked: false, completed: false },
{ id: 'ap_boss2', type: 'boss', label: 'Boss: Terrasunder Apex', x: 75, y: 20, unlocked: false, completed: false },
{ id: 'ap_final', type: 'boss', label: 'Final Boss: Cosmos Sovereign', x: 50, y: 8, unlocked: false, completed: false }
]
}
};
exports.BOSS_DATA = {
vc_boss: { defId: 'bloomgoliath', level: 5, dialog: 'The canopy trembles as a massive Bloomgoliath emerges from the undergrowth. "You dare challenge the guardian of this forest?"' },
sg_boss: { defId: 'neptunian', level: 8, dialog: 'Deep beneath the grotto, an ancient Neptunian rises from the crystal waters. "The depths do not welcome intruders."' },
oc_boss: { defId: 'ignisaur', level: 12, dialog: 'Magma erupts as Ignisaur Omega descends upon the crags. "Feel the fury of a thousand suns!"' },
ap_boss1: { defId: 'stormlord', level: 15, dialog: 'Lightning splits the sky — Stormlord Zenith commands the tempest itself.' },
ap_boss2: { defId: 'terrasunder', level: 16, dialog: 'The ground quakes as Terrasunder Apex rises from the bedrock. "Nothing moves me."' },
ap_final: { defId: 'ignisaur', level: 20, dialog: 'A blinding light fills the Apex Pillar. The Cosmos Sovereign — a being of pure elemental harmony — awaits your challenge.' }
};
exports.CONDUCTOR_DATA = {
vc_5: { name: 'Conductor Mira', resonantDefId: 'thornback', level: 3, dialog: '"I am Mira, a wandering Conductor. Let us see if your Resonants can match mine!"' },
sg_5: { name: 'Conductor Kael', resonantDefId: 'geoxus', level: 7, dialog: '"Kael here. The grotto tests all who enter — are you worthy?"' },
oc_5: { name: 'Conductor Vex', resonantDefId: 'pyrowolf', level: 12, dialog: '"Vex commands the flames of the crags. Prepare for battle!"' },
ap_5: { name: 'Conductor Zephyra', resonantDefId: 'zephyrus', level: 17, dialog: '"I am Zephyra, the last guardian before the Sovereign. Prove your worth."' }
};
exports.SHOP_ITEMS = [
{ id: 'prism_basic', name: 'Harmonizer Prism', description: 'Basic capture prism — moderate catch rate.', cost: 30, icon: '💎' },
{ id: 'prism_hyper', name: 'Hyper-Conductive Prism', description: 'Premium prism — high catch rate.', cost: 80, icon: '🔮' },
{ id: 'elixir_hp', name: 'Core Restorer', description: 'Restores 50% HP to active Resonant.', cost: 25, icon: '❤️' },
{ id: 'elixir_status', name: 'Resonance Elixir', description: 'Heals all status conditions on a Resonant.', cost: 35, icon: '✨' }
];
function getZone(id) { return exports.ZONES[id]; }
};
// ── 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.createDefaultState = createDefaultState;
exports.saveState = saveState;
exports.loadState = loadState;
exports.resetSave = resetSave;
exports.gainXp = gainXp;
exports.shouldEvolve = shouldEvolve;
exports.evolveResonant = evolveResonant;
exports.addItem = addItem;
exports.removeItem = removeItem;
exports.hasItem = hasItem;
const resonants_1 = require("../data/resonants");
const SAVE_KEY = 'aethelgard_save_v1';
function createResonant(defId, level) {
const def = (0, resonants_1.getResonantDef)(defId);
if (!def)
throw new Error(`Unknown resonant: ${defId}`);
const hp = Math.floor(def.baseHp * (1 + level * 0.15));
return {
defId, level, currentHp: hp, maxHp: hp,
atk: Math.floor(def.baseAtk * (1 + level * 0.12)),
def: Math.floor(def.baseDef * (1 + level * 0.12)),
spd: Math.floor(def.baseSpd * (1 + level * 0.1)),
xp: 0, xpToNext: level * 30 + 20,
statusEffect: null,
capturedMoves: def.moves.filter(m => m.unlockLevel <= level)
};
}
function createDefaultState() {
return {
name: 'Conductor', shards: 100,
party: [createResonant('emberkit', 3)],
inventory: [{ itemId: 'prism_basic', count: 5 }, { itemId: 'elixir_hp', count: 3 }],
unlockedZones: ['verdant_canopy'], completedBosses: [],
currentZone: 'verdant_canopy', currentNode: '',
dexEntries: ['emberkit'], totalBattles: 0, totalCaptures: 0
};
}
function saveState(state) {
try {
localStorage.setItem(SAVE_KEY, JSON.stringify(state));
}
catch (e) { /* quota */ }
}
function loadState() {
try {
const raw = localStorage.getItem(SAVE_KEY);
if (!raw)
return null;
return JSON.parse(raw);
}
catch {
return null;
}
}
function resetSave() {
localStorage.removeItem(SAVE_KEY);
}
function gainXp(r, amount) {
r.xp += amount;
if (r.xp >= r.xpToNext) {
r.level++;
const def = (0, resonants_1.getResonantDef)(r.defId);
r.maxHp = Math.floor(def.baseHp * (1 + r.level * 0.15));
r.currentHp = r.maxHp;
r.atk = Math.floor(def.baseAtk * (1 + r.level * 0.12));
r.def = Math.floor(def.baseDef * (1 + r.level * 0.12));
r.spd = Math.floor(def.baseSpd * (1 + r.level * 0.1));
r.xp -= r.xpToNext;
r.xpToNext = r.level * 30 + 20;
// Unlock new moves
const newMoves = def.moves.filter(m => m.unlockLevel <= r.level && !r.capturedMoves.find(cm => cm.name === m.name));
r.capturedMoves.push(...newMoves);
return true; // leveled up
}
return false;
}
function shouldEvolve(r) {
const def = (0, resonants_1.getResonantDef)(r.defId);
return !!(def && def.evolutionLevel === r.level);
}
function evolveResonant(r) {
const def = (0, resonants_1.getResonantDef)(r.defId);
if (!def || !def.evolvesInto)
return;
r.defId = def.evolvesInto;
// Full heal on evolution
const newDef = (0, resonants_1.getResonantDef)(r.defId);
r.maxHp = Math.floor(newDef.baseHp * (1 + r.level * 0.15));
r.currentHp = r.maxHp;
r.atk = Math.floor(newDef.baseAtk * (1 + r.level * 0.12));
r.def = Math.floor(newDef.baseDef * (1 + r.level * 0.12));
r.spd = Math.floor(newDef.baseSpd * (1 + r.level * 0.1));
}
function addItem(state, itemId, count) {
const existing = state.inventory.find(i => i.itemId === itemId);
if (existing)
existing.count += count;
else
state.inventory.push({ itemId, count });
}
function removeItem(state, itemId, count) {
const item = state.inventory.find(i => i.itemId === itemId);
if (!item || item.count < count)
return false;
item.count -= count;
if (item.count <= 0)
state.inventory = state.inventory.filter(i => i.itemId !== itemId);
return true;
}
function hasItem(state, itemId, count) {
const item = state.inventory.find(i => i.itemId === itemId);
return !!(item && item.count >= count);
}
};
// ── module: src/systems/synth.ts ──
__mods["src/systems/synth.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.synth = exports.SynthManager = void 0;
class SynthManager {
constructor() {
this.ctx = null;
this.enabled = true;
}
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).connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + duration);
}
hover() { this.playTone(880, 0.06, 'sine', 0.05); }
select() { this.playTone(660, 0.1, 'square', 0.12); setTimeout(() => this.playTone(990, 0.08, 'square', 0.1), 60); }
hitPhysical() {
if (!this.ctx || !this.enabled)
return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(60, this.ctx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.15);
osc.connect(gain).connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.15);
}
hitElemental() {
if (!this.ctx || !this.enabled)
return;
this.playTone(440, 0.12, 'square', 0.18);
setTimeout(() => this.playTone(660, 0.1, 'square', 0.15), 50);
setTimeout(() => this.playTone(880, 0.15, 'sawtooth', 0.12), 100);
}
superEffective() {
if (!this.ctx || !this.enabled)
return;
[523, 659, 784, 1047].forEach((f, i) => {
setTimeout(() => this.playTone(f, 0.15, 'square', 0.2), i * 60);
});
}
levelUp() {
if (!this.ctx || !this.enabled)
return;
[440, 554, 659, 880, 1108].forEach((f, i) => {
setTimeout(() => this.playTone(f, 0.2, 'square', 0.18), i * 100);
});
}
capture() {
if (!this.ctx || !this.enabled)
return;
[330, 440, 554, 660, 880].forEach((f, i) => {
setTimeout(() => this.playTone(f, 0.12, 'sine', 0.15), i * 80);
});
}
evolve() {
if (!this.ctx || !this.enabled)
return;
[262, 330, 392, 523, 659, 784, 1047].forEach((f, i) => {
setTimeout(() => this.playTone(f, 0.25, 'sine', 0.15), i * 120);
});
}
defeat() {
if (!this.ctx || !this.enabled)
return;
[440, 370, 311, 262].forEach((f, i) => {
setTimeout(() => this.playTone(f, 0.25, 'triangle', 0.15), i * 150);
});
}
heal() {
if (!this.ctx || !this.enabled)
return;
[392, 440, 523, 659].forEach((f, i) => {
setTimeout(() => this.playTone(f, 0.18, 'sine', 0.1), i * 100);
});
}
toggle() { this.enabled = !this.enabled; }
}
exports.SynthManager = SynthManager;
exports.synth = new SynthManager();
};
// ── module: src/systems/combat.ts ──
__mods["src/systems/combat.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.calcDamage = calcDamage;
exports.applyDamage = applyDamage;
exports.isFainted = isFainted;
exports.calcCaptureRate = calcCaptureRate;
exports.applyStatusEffect = applyStatusEffect;
exports.processStatusEffects = processStatusEffects;
exports.healResonant = healResonant;
exports.fullHealParty = fullHealParty;
exports.selectEnemyMove = selectEnemyMove;
exports.getHpColor = getHpColor;
exports.xpForDefeat = xpForDefeat;
const types_1 = require("../types");
const resonants_1 = require("../data/resonants");
function calcDamage(attacker, defender, movePower) {
const atkDef = (0, resonants_1.getResonantDef)(attacker.defId);
const defDef = (0, resonants_1.getResonantDef)(defender.defId);
const typeAdv = types_1.TYPE_ADVANTAGES[atkDef.type];
let typeMod = 1;
if (typeAdv.strong.includes(defDef.type))
typeMod = 1.5;
else if (typeAdv.weak.includes(defDef.type))
typeMod = 0.75;
const base = (((2 * attacker.level / 5 + 2) * movePower * (attacker.atk / defender.def)) / 50 + 2);
const random = 0.85 + Math.random() * 0.15;
let damage = Math.max(1, Math.floor(base * typeMod * random));
// Status effects reduce attack
if (attacker.statusEffect === 'Burn') {
damage = Math.floor(damage * 0.8);
}
if (attacker.statusEffect === 'Paralyze') {
damage = Math.floor(damage * 0.75);
}
const isCrit = Math.random() < 0.1;
if (isCrit)
damage = Math.floor(damage * 1.5);
return { damage, isCrit, typeMod };
}
function applyDamage(r, amount) {
r.currentHp = Math.max(0, r.currentHp - amount);
}
function isFainted(r) {
return r.currentHp <= 0;
}
function calcCaptureRate(enemy, prismTier) {
const hpRatio = enemy.currentHp / enemy.maxHp;
let base = (1 - hpRatio) * 0.85 + 0.15; // More HP = harder to catch
if (enemy.statusEffect)
base += 0.2;
if (prismTier === 'hyper')
base *= 1.6;
return Math.min(0.95, Math.max(0.05, base));
}
function applyStatusEffect(r, effect) {
r.statusEffect = effect;
}
function processStatusEffects(r) {
if (!r.statusEffect)
return null;
switch (r.statusEffect) {
case 'Burn':
const burnDmg = Math.max(1, Math.floor(r.maxHp * 0.06));
r.currentHp = Math.max(0, r.currentHp - burnDmg);
if (Math.random() < 0.2) {
r.statusEffect = null;
return `${r.defId} is healed of Burn!`;
}
return null;
case 'Poison':
const poisDmg = Math.max(1, Math.floor(r.maxHp * 0.05));
r.currentHp = Math.max(0, r.currentHp - poisDmg);
if (Math.random() < 0.2) {
r.statusEffect = null;
return `${r.defId} is healed of Poison!`;
}
return null;
case 'Paralyze':
if (Math.random() < 0.15) {
r.statusEffect = null;
return `${r.defId} shakes off Paralysis!`;
}
return null;
case 'Sleep':
if (Math.random() < 0.3) {
r.statusEffect = null;
return `${r.defId} wakes up!`;
}
return null;
}
return null;
}
function healResonant(r, amount) {
r.currentHp = Math.min(r.maxHp, r.currentHp + amount);
}
function fullHealParty(party) {
party.forEach(r => { r.currentHp = r.maxHp; r.statusEffect = null; });
}
// AI enemy move selection
function selectEnemyMove(enemy) {
const def = (0, resonants_1.getResonantDef)(enemy.defId);
const availableMoves = def.moves.filter(m => m.unlockLevel <= enemy.level);
// Prefer stronger moves, with some randomness
if (Math.random() < 0.3 && availableMoves.length > 1) {
return Math.floor(Math.random() * availableMoves.length);
}
let bestIdx = 0;
for (let i = 1; i < availableMoves.length; i++) {
if (availableMoves[i].power > availableMoves[bestIdx].power)
bestIdx = i;
}
return bestIdx;
}
function getHpColor(ratio) {
if (ratio > 0.5)
return '#22c55e';
if (ratio > 0.25)
return '#eab308';
return '#ef4444';
}
function xpForDefeat(level) {
return Math.floor(15 + level * 8);
}
};
// ── module: src/ui/sprites.ts ──
__mods["src/ui/sprites.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderResonantSVG = renderResonantSVG;
exports.renderResonantSVGInline = renderResonantSVGInline;
const resonants_1 = require("../data/resonants");
const types_1 = require("../types");
function shapePath(shape, size) {
const cx = size / 2, cy = size / 2, r = size * 0.38;
switch (shape) {
case 'circle': return `<circle cx="${cx}" cy="${cy}" r="${r}"/>`;
case 'hexagon': {
const pts = [];
for (let i = 0; i < 6; i++) {
const a = Math.PI / 3 * i - Math.PI / 6;
pts.push(`${cx + r * Math.cos(a)},${cy + r * Math.sin(a)}`);
}
return `<polygon points="${pts.join(' ')}"/>`;
}
case 'diamond': return `<rect x="${cx - r * 0.7}" y="${cy - r * 0.7}" width="${r * 1.4}" height="${r * 1.4}" rx="6" transform="rotate(45 ${cx} ${cy})"/>`;
case 'star': {
const pts = [];
for (let i = 0; i < 10; i++) {
const a = Math.PI / 5 * i - Math.PI / 2;
const rad = i % 2 === 0 ? r : r * 0.45;
pts.push(`${cx + rad * Math.cos(a)},${cy + rad * Math.sin(a)}`);
}
return `<polygon points="${pts.join(' ')}"/>`;
}
case 'triangle': {
const pts = [];
for (let i = 0; i < 3; i++) {
const a = Math.PI * 2 / 3 * i - Math.PI / 2;
pts.push(`${cx + r * Math.cos(a)},${cy + r * Math.sin(a)}`);
}
return `<polygon points="${pts.join(' ')}" rx="4"/>`;
}
default: return `<circle cx="${cx}" cy="${cy}" r="${r}"/>`;
}
}
function featureOverlay(params, size) {
const cx = size / 2, cy = size / 2, r = size * 0.38;
let svg = '';
if (params.features.includes('eyes')) {
const eyeY = cy - r * 0.15;
svg += `<circle cx="${cx - r * 0.2}" cy="${eyeY}" r="${r * 0.1}" fill="white"/>`;
svg += `<circle cx="${cx + r * 0.2}" cy="${eyeY}" r="${r * 0.1}" fill="white"/>`;
svg += `<circle cx="${cx - r * 0.18}" cy="${eyeY + 1}" r="${r * 0.05}" fill="#1a1040"/>`;
svg += `<circle cx="${cx + r * 0.22}" cy="${eyeY + 1}" r="${r * 0.05}" fill="#1a1040"/>`;
}
if (params.features.includes('horns')) {
svg += `<polygon points="${cx - r * 0.3},${cy - r * 0.6} ${cx - r * 0.5},${cy - r * 1.2} ${cx - r * 0.1},${cy - r * 0.7}" fill="${params.accentColor}" opacity="0.8"/>`;
svg += `<polygon points="${cx + r * 0.3},${cy - r * 0.6} ${cx + r * 0.5},${cy - r * 1.2} ${cx + r * 0.1},${cy - r * 0.7}" fill="${params.accentColor}" opacity="0.8"/>`;
}
if (params.features.includes('wings')) {
svg += `<ellipse cx="${cx - r * 0.9}" cy="${cy - r * 0.2}" rx="${r * 0.5}" ry="${r * 0.3}" fill="${params.accentColor}" opacity="0.4" transform="rotate(-20 ${cx - r * 0.9} ${cy})"/>`;
svg += `<ellipse cx="${cx + r * 0.9}" cy="${cy - r * 0.2}" rx="${r * 0.5}" ry="${r * 0.3}" fill="${params.accentColor}" opacity="0.4" transform="rotate(20 ${cx + r * 0.9} ${cy})"/>`;
}
if (params.features.includes('tail')) {
svg += `<path d="M${cx},${cy + r * 0.7} Q${cx + r * 0.5},${cy + r * 1.3} ${cx + r * 0.2},${cy + r * 1.4}" stroke="${params.secondaryColor}" stroke-width="3" fill="none" stroke-linecap="round"/>`;
}
if (params.features.includes('crown')) {
svg += `<polygon points="${cx - r * 0.35},${cy - r * 0.7} ${cx - r * 0.25},${cy - r * 1.15} ${cx - r * 0.08},${cy - r * 0.85} ${cx + r * 0.08},${cy - r * 1.15} ${cx + r * 0.25},${cy - r * 0.85} ${cx + r * 0.35},${cy - r * 0.7}" fill="${params.accentColor}" opacity="0.9"/>`;
}
if (params.features.includes('aura')) {
svg += `<circle cx="${cx}" cy="${cy}" r="${r * 1.2}" fill="none" stroke="${params.primaryColor}" stroke-width="2" opacity="0.3">`;
svg += `<animate attributeName="r" values="${r * 1.1};${r * 1.35};${r * 1.1}" dur="2s" repeatCount="indefinite"/>`;
svg += `<animate attributeName="opacity" values="0.4;0.1;0.4" dur="2s" repeatCount="indefinite"/></circle>`;
}
if (params.features.includes('spikes')) {
for (let i = 0; i < 6; i++) {
const a = Math.PI / 3 * i;
svg += `<line x1="${cx + r * 0.85 * Math.cos(a)}" y1="${cy + r * 0.85 * Math.sin(a)}" x2="${cx + r * 1.15 * Math.cos(a)}" y2="${cy + r * 1.15 * Math.sin(a)}" stroke="${params.secondaryColor}" stroke-width="3" stroke-linecap="round"/>`;
}
}
if (params.features.includes('petals')) {
for (let i = 0; i < 4; i++) {
const a = Math.PI / 2 * i + Math.PI / 4;
svg += `<ellipse cx="${cx + r * 0.7 * Math.cos(a)}" cy="${cy + r * 0.7 * Math.sin(a)}" rx="${r * 0.2}" ry="${r * 0.35}" fill="${params.accentColor}" opacity="0.5" transform="rotate(${i * 45} ${cx + r * 0.7 * Math.cos(a)} ${cy + r * 0.7 * Math.sin(a)})"/>`;
}
}
return svg;
}
function renderResonantSVG(defId, size = 120) {
const def = (0, resonants_1.getResonantDef)(defId);
if (!def)
return `<svg width="${size}" height="${size}"><text x="50%" y="50%" text-anchor="middle" fill="#888">?</text></svg>`;
const p = def.spriteParams;
const glowColor = types_1.ELEMENT_COLORS[def.type] || '#8b5cf6';
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="g_${defId}_${size}" cx="40%" cy="35%">
<stop offset="0%" stop-color="${p.accentColor}"/>
<stop offset="60%" stop-color="${p.primaryColor}"/>
<stop offset="100%" stop-color="${p.secondaryColor}"/>
</radialGradient>
<filter id="glow_${defId}_${size}">
<feGaussianBlur stdDeviation="3" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
${shapePath(p.shape, size)}
${featureOverlay(p, size)}
</svg>`;
}
function renderResonantSVGInline(defId, size = 120) {
const container = document.createElement('div');
container.innerHTML = renderResonantSVG(defId, size);
return container.firstChild;
}
};
// ── module: src/ui/game.ts ──
__mods["src/ui/game.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 });
exports.renderMainMenu = renderMainMenu;
exports.renderMap = renderMap;
exports.setupBattleActions = setupBattleActions;
exports.renderShop = renderShop;
exports.renderDex = renderDex;
exports.showZoneSelect = showZoneSelect;
exports.init = init;
const types_1 = require("../types");
const resonants_1 = require("../data/resonants");
const zones_1 = require("../data/zones");
const player_1 = require("../state/player");
const combat = __importStar(require("../systems/combat"));
const synth_1 = require("../systems/synth");
const sprites_1 = require("./sprites");
// ===== SCREEN MANAGEMENT =====
let currentScreen = 'menu';
let playerState;
let battleState = null;
let battleCallback = null;
function showScreen(name) {
document.querySelectorAll('.game-screen').forEach(el => el.classList.remove('active'));
const target = document.getElementById(`screen-${name}`);
if (target) {
target.classList.add('active');
}
currentScreen = name;
}
function showToast(msg, duration = 2000) {
const overlay = document.getElementById('overlay-toast');
overlay.innerHTML = `<div class="toast-msg">${msg}</div>`;
overlay.classList.remove('hidden');
setTimeout(() => overlay.classList.add('hidden'), duration);
}
// ===== MAIN MENU =====
function renderMainMenu() {
const el = document.getElementById('screen-menu');
el.innerHTML = `
<div class="glass-card" style="padding:48px 56px;text-align:center;max-width:480px;width:90%;animation:bounceIn 0.5s ease">
<h1 class="retro-title" style="font-size:22px;color:#c084fc;margin-bottom:8px;text-shadow:0 0 20px rgba(192,132,252,0.5)">AETHELGARD</h1>
<p style="color:#a78bfa;font-size:13px;letter-spacing:4px;margin-bottom:36px">RESONANT CHRONICLES</p>
<div id="menu-buttons" style="display:flex;flex-direction:column;gap:12px;align-items:center"></div>
</div>`;
const btns = document.getElementById('menu-buttons');
const hasSave = localStorage.getItem('aethelgard_save_v1');
if (hasSave) {
addBtn(btns, 'Continue', () => { initGame(true); renderMap(); }, 'success');
}
addBtn(btns, 'New Game', () => { initGame(false); pickStarter(); }, '');
addBtn(btns, 'Reset Progress', () => {
if (confirm('Erase all progress? This cannot be undone.')) {
localStorage.removeItem('aethelgard_save_v1');
renderMainMenu();
}
}, 'danger');
}
function initGame(load) {
synth_1.synth.init();
playerState = load ? JSON.parse(localStorage.getItem('aethelgard_save_v1')) : (0, player_1.createDefaultState)();
}
function addBtn(parent, text, onClick, cls = '') {
const btn = document.createElement('button');
btn.className = `glass-btn ${cls}`;
btn.style.width = '200px';
btn.textContent = text;
btn.addEventListener('click', () => { synth_1.synth.select(); onClick(); });
btn.addEventListener('mouseenter', () => synth_1.synth.hover());
parent.appendChild(btn);
}
// ===== STARTER SELECTION =====
function pickStarter() {
const el = document.getElementById('screen-menu');
const starters = ['emberkit', 'droplet', 'seedling'];
const starterNames = ['Emberkit (Pyro)', 'Droplet (Hydro)', 'Seedling (Flora)'];
el.innerHTML = `
<div class="glass-card" style="padding:36px 48px;text-align:center;max-width:520px;width:90%;animation:bounceIn 0.4s ease">
<h2 class="retro-title" style="font-size:16px;color:#c084fc;margin-bottom:24px">Choose Your Resonant</h2>
<div id="starter-grid" style="display:flex;gap:20px;justify-content:center;flex-wrap:wrap"></div>
</div>`;
const grid = document.getElementById('starter-grid');
starters.forEach((id, i) => {
const card = document.createElement('div');
card.className = 'glass-card';
card.style.cssText = 'padding:20px;width:140px;text-align:center;cursor:pointer;transition:transform 0.2s,border-color 0.2s';
card.innerHTML = `
<div style="margin-bottom:8px">${(0, sprites_1.renderResonantSVG)(id, 80)}</div>
<p style="font-weight:700;font-size:13px;margin-bottom:4px">${starterNames[i]}</p>`;
card.addEventListener('mouseenter', () => { synth_1.synth.hover(); card.style.borderColor = types_1.ELEMENT_COLORS[(0, resonants_1.getResonantDef)(id).type]; });
card.addEventListener('click', () => {
synth_1.synth.select();
playerState.party = [(0, player_1.createResonant)(id, 3)];
playerState.dexEntries.push(id);
(0, player_1.saveState)(playerState);
showToast(`${starterNames[i]} joins your party!`);
setTimeout(() => renderMap(), 800);
});
grid.appendChild(card);
});
}
// ===== MAP RENDERING =====
function renderMap() {
showScreen('map');
const zone = zones_1.ZONES[playerState.currentZone];
if (!zone)
return;
const el = document.getElementById('screen-map');
el.innerHTML = `
<div style="width:100%;max-width:800px;height:100%;display:flex;flex-direction:column">
<div class="glass-card" style="padding:16px 24px;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0">
<div>
<h2 class="retro-title" style="font-size:14px;color:${zone.color}">${zone.name}</h2>
<p style="font-size:12px;color:#94a3b8;margin-top:4px">${zone.description}</p>
</div>
<div style="display:flex;gap:8px">
${playerState.unlockedZones.length > 1 ? `<button class="glass-btn" id="btn-zones" style="padding:6px 14px;font-size:12px">🗺️ Zones</button>` : ''}
<button class="glass-btn" id="btn-shop" style="padding:6px 14px;font-size:12px">🏪 Shop</button>
<button class="glass-btn" id="btn-dex" style="padding:6px 14px;font-size:12px">📖 Dex</button>
<button class="glass-btn" id="btn-party" style="padding:6px 14px;font-size:12px">👥 Party</button>
</div>
</div>
<div class="glass-card" style="flex:1;position:relative;overflow:hidden;border-radius:16px" id="map-canvas"></div>
<div style="padding:8px;text-align:center">
<span style="font-size:12px;color:#94a3b8">💎 ${playerState.shards} Aether Shards</span>
</div>
</div>`;
const canvas = document.getElementById('map-canvas');
// Draw zone background gradient
canvas.style.background = `radial-gradient(ellipse at center, ${zone.color}15 0%, transparent 70%), linear-gradient(180deg, #0f0a1e, #1a1040)`;
// Draw nodes
zone.nodes.forEach(node => {
if (!node.unlocked)
return;
const nodeEl = document.createElement('div');
const colors = { wild: '#22c55e', cache: '#facc15', conductor: '#ef4444', spring: '#3b82f6', boss: '#c084fc' };
const icons = { wild: '🌿', cache: '📦', conductor: '⚔️', spring: '💧', boss: '👑' };
nodeEl.style.cssText = `position:absolute;left:${node.x}%;top:${node.y}%;transform:translate(-50%,-50%);text-align:center;cursor:pointer;transition:all 0.2s;z-index:1`;
const color = colors[node.type] || '#8b5cf6';
nodeEl.innerHTML = `
<div style="width:48px;height:48px;border-radius:50%;background:${color}30;border:2px solid ${color};display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:0 0 12px ${color}40">${icons[node.type] || '?'}</div>
<p style="font-size:10px;margin-top:4px;color:${node.completed ? '#6b7280' : '#e2e8f0'};font-weight:600">${node.label}${node.completed ? ' ✓' : ''}</p>`;
nodeEl.addEventListener('mouseenter', () => { synth_1.synth.hover(); nodeEl.style.transform = 'translate(-50%,-50%) scale(1.15)'; });
nodeEl.addEventListener('mouseleave', () => { nodeEl.style.transform = 'translate(-50%,-50%) scale(1)'; });
nodeEl.addEventListener('click', () => handleNodeClick(node));
canvas.appendChild(nodeEl);
});
// Draw connecting lines between nodes
const svgOverlay = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgOverlay.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:0';
for (let i = 0; i < zone.nodes.length - 1; i++) {
const a = zone.nodes[i], b = zone.nodes[i + 1];
if (!a.unlocked || !b.unlocked)
continue;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', `${a.x}%`);
line.setAttribute('y1', `${a.y}%`);
line.setAttribute('x2', `${b.x}%`);
line.setAttribute('y2', `${b.y}%`);
line.setAttribute('stroke', zone.color + '30');
line.setAttribute('stroke-width', '2');
svgOverlay.appendChild(line);
}
canvas.insertBefore(svgOverlay, canvas.firstChild);
// Button handlers
const btnZones = document.getElementById('btn-zones');
if (btnZones)
btnZones.addEventListener('click', () => { synth_1.synth.select(); showZoneSelect(); });
document.getElementById('btn-shop').addEventListener('click', () => { synth_1.synth.select(); renderShop(); });
document.getElementById('btn-dex').addEventListener('click', () => { synth_1.synth.select(); renderDex(); });
document.getElementById('btn-party').addEventListener('click', () => { synth_1.synth.select(); renderPartyScreen(); });
}
function handleNodeClick(node) {
if (node.completed)
return;
synth_1.synth.select();
switch (node.type) {
case 'wild':
startWildBattle(playerState.currentZone, node.id);
break;
case 'cache':
openCache(node);
break;
case 'conductor':
startConductorBattle(node.id);
break;
case 'spring':
healSpring();
break;
case 'boss':
startBossBattle(node.id);
break;
}
}
function openCache(node) {
node.completed = true;
const rewards = [
() => { playerState.shards += Math.floor(15 + Math.random() * 20); showToast('Found Aether Shards!'); },
() => { (0, player_1.addItem)(playerState, 'prism_basic', 2); showToast('Found 2 Harmonizer Prisms!'); },
() => { (0, player_1.addItem)(playerState, 'elixir_hp', 2); showToast('Found 2 Core Restorers!'); },
() => { playerState.shards += 30; (0, player_1.addItem)(playerState, 'prism_hyper', 1); showToast('Treasure! Shards + Hyper Prism!'); }
];
rewards[Math.floor(Math.random() * rewards.length)]();
(0, player_1.saveState)(playerState);
renderMap();
}
function healSpring() {
synth_1.synth.heal();
combat.fullHealParty(playerState.party);
showToast('Your Resonants are fully restored!');
(0, player_1.saveState)(playerState);
renderMap();
}
// ===== WILD BATTLE =====
function startWildBattle(zoneId, nodeId) {
const zone = zones_1.ZONES[zoneId];
if (!zone)
return;
// Pick a random resonant from the zone's element bias
const eligibleIds = Object.keys(resonants_1.RESONANT_DB).filter(id => {
const def = resonants_1.RESONANT_DB[id];
return zone.elementBias.includes(def.type);
});
const enemyDefId = eligibleIds[Math.floor(Math.random() * eligibleIds.length)];
const level = zone.levelRange[0] + Math.floor(Math.random() * (zone.levelRange[1] - zone.levelRange[0]));
battleState = {
playerResonant: playerState.party[0],
enemyResonant: (0, player_1.createResonant)(enemyDefId, level),
turn: 'player', phase: 'select', log: ['A wild Resonant appears!'], isWild: true, zoneId, nodeId
};
renderBattle();
setupBattleActions();
}
function startConductorBattle(nodeId) {
const data = zones_1.CONDUCTOR_DATA[nodeId];
if (!data)
return;
battleState = {
playerResonant: playerState.party[0],
enemyResonant: (0, player_1.createResonant)(data.resonantDefId, data.level),
turn: 'player', phase: 'select', log: [data.dialog], isWild: false, zoneId: playerState.currentZone, nodeId
};
renderBattle();
setupBattleActions();
}
function startBossBattle(nodeId) {
const data = zones_1.BOSS_DATA[nodeId];
if (!data)
return;
battleState = {
playerResonant: playerState.party[0],
enemyResonant: (0, player_1.createResonant)(data.defId, data.level),
turn: 'player', phase: 'select', log: [data.dialog], isWild: false, zoneId: playerState.currentZone, nodeId
};
renderBattle();
setupBattleActions();
}
// ===== BATTLE RENDERING =====
function renderBattle() {
if (!battleState)
return;
showScreen('battle');
const el = document.getElementById('screen-battle');
const pr = battleState.playerResonant;
const er = battleState.enemyResonant;
const pDef = (0, resonants_1.getResonantDef)(pr.defId);
const eDef = (0, resonants_1.getResonantDef)(er.defId);
el.innerHTML = `
<div style="width:100%;max-width:720px;height:100%;display:flex;flex-direction:column">
<!-- Enemy area -->
<div class="glass-card" style="padding:16px;margin-bottom:8px;display:flex;align-items:center;gap:16px;animation:slideInRight 0.4s ease" id="enemy-area">
<div id="enemy-sprite">${(0, sprites_1.renderResonantSVG)(er.defId, 90)}</div>
<div style="flex:1">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
<span style="font-weight:700;font-size:15px">${eDef.name}</span>
<span class="type-badge el-${eDef.type.toLowerCase()}">${eDef.type}</span>
</div>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-size:11px;color:#94a3b8">Lv.${er.level}</span>
<div class="hp-bar-bg" style="flex:1"><div class="hp-bar-fill" id="enemy-hp-bar" style="width:${(er.currentHp / er.maxHp) * 100}%;background:${combat.getHpColor(er.currentHp / er.maxHp)}"></div></div>
<span style="font-size:11px;color:#94a3b8">${er.currentHp}/${er.maxHp}</span>
</div>
${er.statusEffect ? `<span style="font-size:10px;color:#f87171;margin-top:2px">⚠ ${er.statusEffect}</span>` : ''}
</div>
</div>
<!-- Battle log -->
<div class="glass-card" style="padding:12px 16px;flex:0 0 auto;max-height:120px;overflow-y:auto;margin-bottom:8px;font-size:13px;color:#cbd5e1;line-height:1.6" id="battle-log">
${battleState.log.map(l => `<div>${l}</div>`).join('')}
</div>
<!-- Player area -->
<div class="glass-card" style="padding:16px;margin-bottom:8px;display:flex;align-items:center;gap:16px;animation:slideInLeft 0.4s ease" id="player-area">
<div id="player-sprite">${(0, sprites_1.renderResonantSVG)(pr.defId, 90)}</div>
<div style="flex:1">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
<span style="font-weight:700;font-size:15px">${pDef.name}</span>
<span class="type-badge el-${pDef.type.toLowerCase()}">${pDef.type}</span>
</div>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-size:11px;color:#94a3b8">Lv.${pr.level}</span>
<div class="hp-bar-bg" style="flex:1"><div class="hp-bar-fill" id="player-hp-bar" style="width:${(pr.currentHp / pr.maxHp) * 100}%;background:${combat.getHpColor(pr.currentHp / pr.maxHp)}"></div></div>
<span style="font-size:11px;color:#94a3b8">${pr.currentHp}/${pr.maxHp}</span>
</div>
${pr.statusEffect ? `<span style="font-size:10px;color:#f87171;margin-top:2px">⚠ ${pr.statusEffect}</span>` : ''}
<div class="xp-bar-bg"><div class="xp-bar-fill" style="width:${(pr.xp / pr.xpToNext) * 100}%"></div></div>
</div>
</div>
<!-- Action buttons -->
<div id="battle-actions" style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;flex-shrink:0">
${renderActionButtons()}
</div>
</div>`;
// Scroll log to bottom
const logEl = document.getElementById('battle-log');
if (logEl)
logEl.scrollTop = logEl.scrollHeight;
}
function renderActionButtons() {
if (!battleState || battleState.phase !== 'select')
return '';
const pr = battleState.playerResonant;
const pDef = (0, resonants_1.getResonantDef)(pr.defId);
let html = `<button class="glass-btn" id="btn-attack">⚔️ Attack</button>`;
if (battleState.isWild) {
html += `<button class="glass-btn" id="btn-prism">💎 Prism</button>`;
}
else {
html += `<div></div>`; // spacer
}
html += `<button class="glass-btn" id="btn-exchange">🔄 Exchange</button>`;
html += `<button class="glass-btn" id="btn-items">🎒 Items</button>`;
return html;
}
function addBattleLog(msg) {
if (!battleState)
return;
battleState.log.push(msg);
const logEl = document.getElementById('battle-log');
if (logEl) {
const div = document.createElement('div');
div.textContent = msg;
logEl.appendChild(div);
logEl.scrollTop = logEl.scrollHeight;
}
}
function updateHpBars() {
if (!battleState)
return;
const prBar = document.getElementById('player-hp-bar');
const erBar = document.getElementById('enemy-hp-bar');
if (prBar) {
prBar.style.width = `${(battleState.playerResonant.currentHp / battleState.playerResonant.maxHp) * 100}%`;
prBar.style.background = combat.getHpColor(battleState.playerResonant.currentHp / battleState.playerResonant.maxHp);
}
if (erBar) {
erBar.style.width = `${(battleState.enemyResonant.currentHp / battleState.enemyResonant.maxHp) * 100}%`;
erBar.style.background = combat.getHpColor(battleState.enemyResonant.currentHp / battleState.enemyResonant.maxHp);
}
}
function shakeElement(id) {
const el = document.getElementById(id);
if (el) {
el.classList.add('shake');
setTimeout(() => el.classList.remove('shake'), 400);
}
}
function flashElement(id) {
const el = document.getElementById(id);
if (el) {
el.classList.add('flash-hit');
setTimeout(() => el.classList.remove('flash-hit'), 300);
}
}
// ===== BATTLE ACTIONS =====
function setupBattleActions() {
const btnAttack = document.getElementById('btn-attack');
const btnPrism = document.getElementById('btn-prism');
const btnExchange = document.getElementById('btn-exchange');
const btnItems = document.getElementById('btn-items');
if (btnAttack)
btnAttack.addEventListener('click', () => showMoveSelect());
if (btnPrism)
btnPrism.addEventListener('click', () => attemptCapture());
if (btnExchange)
btnExchange.addEventListener('click', () => showExchangeMenu());
if (btnItems)
btnItems.addEventListener('click', () => showItemMenu());
}
function showMoveSelect() {
if (!battleState || battleState.phase !== 'select')
return;
synth_1.synth.select();
const pr = battleState.playerResonant;
const actionsEl = document.getElementById('battle-actions');
actionsEl.innerHTML = '';
pr.capturedMoves.forEach((move, i) => {
const btn = document.createElement('button');
btn.className = 'glass-btn';
btn.style.cssText = 'padding:10px;font-size:12px;text-align:left;display:flex;justify-content:space-between';
const elColor = types_1.ELEMENT_COLORS[move.element] || '#8b5cf6';
btn.innerHTML = `<span>${move.name}</span><span style="color:${elColor};font-size:10px">${move.element} Pwr:${move.power}</span>`;
btn.addEventListener('click', () => executePlayerMove(i));
actionsEl.appendChild(btn);
});
const backBtn = document.createElement('button');
backBtn.className = 'glass-btn';
backBtn.textContent = '← Back';
backBtn.style.gridColumn = '1 / -1';
backBtn.addEventListener('click', () => renderBattle());
actionsEl.appendChild(backBtn);
}
function executePlayerMove(moveIdx) {
if (!battleState || battleState.phase !== 'select')
return;
const bs = battleState;
const move = bs.playerResonant.capturedMoves[moveIdx];
if (!move)
return;
bs.phase = 'animating';
// Process status effects on player resonant first
const pStatusMsg = combat.processStatusEffects(bs.playerResonant);
if (pStatusMsg)
addBattleLog(pStatusMsg);
// Check accuracy
if (Math.random() * 100 >= move.accuracy) {
addBattleLog(`${bs.playerResonant.defId} uses ${move.name}... but it missed!`);
setTimeout(() => enemyTurn(), 800);
return;
}
const result = combat.calcDamage(bs.playerResonant, bs.enemyResonant, move.power);
combat.applyDamage(bs.enemyResonant, result.damage);
updateHpBars();
let msg = `${bs.playerResonant.defId} uses ${move.name}!`;
if (result.isCrit) {
msg += ' Critical hit!';
synth_1.synth.hitElemental();
}
else if (result.typeMod > 1) {
msg += ' Super effective!';
synth_1.synth.superEffective();
}
else if (result.typeMod < 1) {
msg += ' Not very effective...';
synth_1.synth.hitPhysical();
}
else
synth_1.synth.hitPhysical();
addBattleLog(msg);
flashElement('enemy-area');
shakeElement('enemy-sprite');
// Random status effect chance for elemental moves
if (move.element !== 'Cosmos' && Math.random() < 0.12) {
const effects = ['Burn', 'Paralyze', 'Poison'];
combat.applyStatusEffect(bs.enemyResonant, effects[Math.floor(Math.random() * effects.length)]);
addBattleLog(`${bs.enemyResonant.defId} is afflicted!`);
}
if (combat.isFainted(bs.enemyResonant)) {
setTimeout(() => handleEnemyDefeat(), 600);
}
else {
setTimeout(() => enemyTurn(), 1000);
}
}
function enemyTurn() {
if (!battleState)
return;
const bs = battleState;
bs.phase = 'animating';
// Process status effects on enemy
const eStatusMsg = combat.processStatusEffects(bs.enemyResonant);
if (eStatusMsg)
addBattleLog(eStatusMsg);
if (combat.isFainted(bs.enemyResonant)) {
handleEnemyDefeat();
return;
}
// Sleep check - enemy skips turn
if (bs.enemyResonant.statusEffect === 'Sleep') {
addBattleLog(`${bs.enemyResonant.defId} is asleep!`);
bs.phase = 'select';
bs.turn = 'player';
renderBattle();
setupBattleActions();
return;
}
const moveIdx = combat.selectEnemyMove(bs.enemyResonant);
const eDef = (0, resonants_1.getResonantDef)(bs.enemyResonant.defId);
const move = eDef.moves[moveIdx];
if (!move) {
bs.phase = 'select';
return;
}
// Check accuracy
if (Math.random() * 100 >= move.accuracy) {
addBattleLog(`${bs.enemyResonant.defId} uses ${move.name}... but it missed!`);
setTimeout(() => { battleState.phase = 'select'; renderBattle(); setupBattleActions(); }, 800);
return;
}
const result = combat.calcDamage(bs.enemyResonant, bs.playerResonant, move.power);
combat.applyDamage(bs.playerResonant, result.damage);
updateHpBars();
let msg = `${bs.enemyResonant.defId} uses ${move.name}!`;
if (result.isCrit) {
msg += ' Critical hit!';
synth_1.synth.hitElemental();
}
else if (result.typeMod > 1) {
msg += ' Super effective!';
synth_1.synth.superEffective();
}
else
synth_1.synth.hitPhysical();
addBattleLog(msg);
flashElement('player-area');
shakeElement('player-sprite');
// Random status effect on player
if (move.element !== 'Cosmos' && Math.random() < 0.1) {
const effects = ['Burn', 'Paralyze', 'Poison'];
combat.applyStatusEffect(bs.playerResonant, effects[Math.floor(Math.random() * effects.length)]);
addBattleLog(`${bs.playerResonant.defId} is afflicted!`);
}
if (combat.isFainted(bs.playerResonant)) {
setTimeout(() => handlePlayerDefeat(), 600);
}
else {
setTimeout(() => { battleState.phase = 'select'; renderBattle(); setupBattleActions(); }, 800);
}
}
function attemptCapture() {
if (!battleState || !battleState.isWild)
return;
const bs = battleState;
synth_1.synth.select();
const prismTier = (0, player_1.hasItem)(playerState, 'prism_hyper', 1) && Math.random() > 0.5 ? 'hyper' : 'basic';
const itemId = prismTier === 'hyper' ? 'prism_hyper' : 'prism_basic';
if (!(0, player_1.removeItem)(playerState, itemId, 1)) {
addBattleLog('No prisms available!');
return;
}
bs.phase = 'animating';
const rate = combat.calcCaptureRate(bs.enemyResonant, prismTier);
const enemyDefId = bs.enemyResonant.defId;
const nodeId = bs.nodeId;
const zoneId = bs.zoneId;
addBattleLog(`Throwing ${prismTier === 'hyper' ? 'Hyper-Conductive' : 'Harmonizer'} Prism...`);
setTimeout(() => {
if (Math.random() < rate) {
// Capture success!
synth_1.synth.capture();
const captured = bs.enemyResonant;
playerState.party.push(captured);
if (!playerState.dexEntries.includes(captured.defId))
playerState.dexEntries.push(captured.defId);
playerState.totalCaptures++;
addBattleLog(`${(0, resonants_1.getResonantDef)(captured.defId).name} was captured!`);
showToast(`Captured ${(0, resonants_1.getResonantDef)(captured.defId).name}!`, 2500);
if (nodeId) {
const zone = zones_1.ZONES[zoneId];
const node = zone?.nodes.find(n => n.id === nodeId);
if (node)
node.completed = true;
}
(0, player_1.saveState)(playerState);
setTimeout(() => { battleState = null; renderMap(); }, 1500);
}
else {
addBattleLog(`${(0, resonants_1.getResonantDef)(enemyDefId).name} broke free!`);
bs.phase = 'select';
setTimeout(() => enemyTurn(), 600);
}
}, 1200);
}
function showExchangeMenu() {
if (!battleState || playerState.party.length < 2)
return;
const bs = battleState;
synth_1.synth.select();
const actionsEl = document.getElementById('battle-actions');
actionsEl.innerHTML = '';
playerState.party.forEach((r, i) => {
const def = (0, resonants_1.getResonantDef)(r.defId);
const btn = document.createElement('button');
btn.className = 'glass-btn';
btn.style.cssText = 'padding:10px;font-size:12px;text-align:left;display:flex;justify-content:space-between';
btn.innerHTML = `<span>${def.name} Lv.${r.level}</span><span style="color:${combat.getHpColor(r.currentHp / r.maxHp)}">HP ${r.currentHp}/${r.maxHp}</span>`;
if (i === 0) {
btn.style.opacity = '0.5';
return;
} // active resonant
btn.addEventListener('click', () => {
synth_1.synth.select();
playerState.party[0] = r;
battleState.playerResonant = r;
addBattleLog(`Switched to ${def.name}!`);
renderBattle();
setupBattleActions();
});
actionsEl.appendChild(btn);
});
const backBtn = document.createElement('button');
backBtn.className = 'glass-btn';
backBtn.textContent = '← Back';
backBtn.style.gridColumn = '1 / -1';
backBtn.addEventListener('click', () => renderBattle());
actionsEl.appendChild(backBtn);
}
function showItemMenu() {
synth_1.synth.select();
const actionsEl = document.getElementById('battle-actions');
actionsEl.innerHTML = '';
playerState.inventory.forEach(item => {
if (item.count <= 0)
return;
const shopItem = zones_1.SHOP_ITEMS.find(s => s.id === item.itemId);
if (!shopItem)
return;
const btn = document.createElement('button');
btn.className = 'glass-btn';
btn.style.cssText = 'padding:10px;font-size:12px;text-align:left;display:flex;justify-content:space-between';
btn.innerHTML = `<span>${shopItem.icon} ${shopItem.name}</span><span style="color:#94a3b8">×${item.count}</span>`;
const itemId = item.itemId;
btn.addEventListener('click', () => useBattleItem(itemId));
actionsEl.appendChild(btn);
});
const backBtn = document.createElement('button');
backBtn.className = 'glass-btn';
backBtn.textContent = '← Back';
backBtn.style.gridColumn = '1 / -1';
backBtn.addEventListener('click', () => renderBattle());
actionsEl.appendChild(backBtn);
}
function useBattleItem(itemId) {
if (!battleState)
return;
synth_1.synth.select();
switch (itemId) {
case 'elixir_hp':
if (!(0, player_1.removeItem)(playerState, itemId, 1))
return;
const healAmt = Math.floor(battleState.playerResonant.maxHp * 0.5);
combat.healResonant(battleState.playerResonant, healAmt);
addBattleLog(`Used Core Restorer! Healed ${healAmt} HP.`);
synth_1.synth.heal();
updateHpBars();
setTimeout(() => enemyTurn(), 600);
break;
case 'elixir_status':
if (!(0, player_1.removeItem)(playerState, itemId, 1))
return;
battleState.playerResonant.statusEffect = null;
addBattleLog('Used Resonance Elixir! Status cleared.');
synth_1.synth.heal();
setTimeout(() => enemyTurn(), 600);
break;
default:
addBattleLog("Can't use that in battle.");
renderBattle();
setupBattleActions();
}
}
function handleEnemyDefeat() {
if (!battleState)
return;
const bs = battleState; // capture reference
synth_1.synth.defeat();
const xp = combat.xpForDefeat(bs.enemyResonant.level);
const shardsReward = Math.floor(5 + bs.enemyResonant.level * 3);
addBattleLog(`${(0, resonants_1.getResonantDef)(bs.enemyResonant.defId).name} fainted!`);
addBattleLog(`Gained ${xp} XP and ${shardsReward} Aether Shards.`);
playerState.shards += shardsReward;
playerState.totalBattles++;
const leveled = (0, player_1.gainXp)(bs.playerResonant, xp);
if (leveled) {
synth_1.synth.levelUp();
addBattleLog(`${bs.playerResonant.defId} reached Level ${bs.playerResonant.level}!`);
showToast(`Level Up! Lv.${bs.playerResonant.level}`, 2000);
// Check evolution
if ((0, player_1.shouldEvolve)(bs.playerResonant)) {
const oldName = bs.playerResonant.defId;
(0, player_1.evolveResonant)(bs.playerResonant);
synth_1.synth.evolve();
addBattleLog(`${oldName} evolved into ${bs.playerResonant.defId}!`);
showToast(`Evolved to ${(0, resonants_1.getResonantDef)(bs.playerResonant.defId).name}!`, 2500);
playerState.dexEntries.push(bs.playerResonant.defId);
const spriteEl = document.getElementById('player-sprite');
if (spriteEl) {
spriteEl.classList.add('evolve-flash');
setTimeout(() => spriteEl.classList.remove('evolve-flash'), 1200);
}
}
}
// Mark node complete and unlock next zone nodes if boss defeated
const nodeId = bs.nodeId;
const zoneId = bs.zoneId;
let gameComplete = false;
if (nodeId) {
const zone = zones_1.ZONES[zoneId];
const node = zone?.nodes.find(n => n.id === nodeId);
if (node) {
node.completed = true;
// If boss, unlock next zone
if (node.type === 'boss') {
playerState.completedBosses.push(node.id);
const zoneOrder = ['verdant_canopy', 'sunken_grotto', 'obsidian_crags', 'apex_pillar'];
const idx = zoneOrder.indexOf(zoneId);
if (idx < zoneOrder.length - 1) {
const nextZoneId = zoneOrder[idx + 1];
if (!playerState.unlockedZones.includes(nextZoneId)) {
playerState.unlockedZones.push(nextZoneId);
zones_1.ZONES[nextZoneId].nodes.forEach(n => n.unlocked = true);
addBattleLog(`🎉 ${zones_1.ZONES[nextZoneId].name} unlocked!`);
}
}
}
// Check if final boss defeated
if (node.id === 'ap_final') {
gameComplete = true;
}
}
}
(0, player_1.saveState)(playerState);
setTimeout(() => {
battleState = null;
if (gameComplete) {
showVictoryScreen();
}
else {
renderMap();
}
}, 1500);
}
function handlePlayerDefeat() {
if (!battleState)
return;
const bs = battleState;
synth_1.synth.defeat();
addBattleLog(`${bs.playerResonant.defId} fainted...`);
// Heal to half HP and continue
setTimeout(() => {
bs.playerResonant.currentHp = Math.floor(bs.playerResonant.maxHp * 0.3);
bs.playerResonant.statusEffect = null;
addBattleLog('Your Resonant was revived at camp.');
showToast('Defeated... but you recover.', 2000);
(0, player_1.saveState)(playerState);
setTimeout(() => { battleState = null; renderMap(); }, 1500);
}, 1500);
}
// ===== SHOP =====
function renderShop() {
showScreen('shop');
const el = document.getElementById('screen-shop');
el.innerHTML = `
<div class="glass-card" style="padding:32px;max-width:500px;width:90%;animation:bounceIn 0.4s ease">
<h2 class="retro-title" style="font-size:16px;color:#c084fc;margin-bottom:20px;text-align:center">🏪 Camp Shop</h2>
<p style="text-align:center;font-size:13px;color:#94a3b8;margin-bottom:20px">💎 ${playerState.shards} Aether Shards</p>
<div id="shop-items" style="display:flex;flex-direction:column;gap:10px"></div>
<button class="glass-btn" id="btn-shop-back" style="margin-top:20px;width:100%">← Back to Map</button>
</div>`;
const container = document.getElementById('shop-items');
zones_1.SHOP_ITEMS.forEach(item => {
const owned = playerState.inventory.find(i => i.itemId === item.id)?.count || 0;
const card = document.createElement('div');
card.className = 'glass-card';
card.style.cssText = 'padding:14px;display:flex;justify-content:space-between;align-items:center';
card.innerHTML = `
<div>
<span style="font-size:20px">${item.icon}</span>
<strong style="margin-left:8px;font-size:13px">${item.name}</strong>
<p style="font-size:11px;color:#94a3b8;margin-top:2px">${item.description}</p>
${owned > 0 ? `<span style="font-size:10px;color:#6ee7b7">Owned: ${owned}</span>` : ''}
</div>
<button class="glass-btn success" data-item="${item.id}" style="padding:6px 14px;font-size:12px;white-space:nowrap">${item.cost} 💎</button>`;
container.appendChild(card);
const buyBtn = card.querySelector('button[data-item]');
if (buyBtn) {
buyBtn.addEventListener('click', () => {
if (playerState.shards < item.cost) {
showToast('Not enough shards!');
return;
}
synth_1.synth.select();
playerState.shards -= item.cost;
(0, player_1.addItem)(playerState, item.id, 1);
(0, player_1.saveState)(playerState);
showToast(`Bought ${item.name}!`);
renderShop();
});
}
});
document.getElementById('btn-shop-back').addEventListener('click', () => { synth_1.synth.select(); renderMap(); });
}
// ===== DEX =====
function renderDex() {
showScreen('dex');
const el = document.getElementById('screen-dex');
const entries = Object.values(resonants_1.RESONANT_DB);
let html = `
<div style="width:100%;max-width:700px;height:100%;display:flex;flex-direction:column">
<div class="glass-card" style="padding:16px 24px;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0">
<h2 class="retro-title" style="font-size:14px;color:#c084fc">📖 Resonant Dex</h2>
<span style="font-size:12px;color:#94a3b8">${playerState.dexEntries.length}/18 discovered</span>
</div>
<div class="glass-card" style="flex:1;overflow-y:auto;padding:16px;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;align-content:start">`;
entries.forEach(def => {
const discovered = playerState.dexEntries.includes(def.id);
html += `
<div class="glass-card" style="padding:14px;text-align:center;opacity:${discovered ? '1' : '0.35'}">
${discovered ? `<div style="margin-bottom:8px">${(0, sprites_1.renderResonantSVG)(def.id, 60)}</div>` : '<div style="width:60px;height:60px;margin:0 auto 8px;border-radius:50%;background:#1e293b;display:flex;align-items:center;justify-content:center;font-size:24px">?</div>'}
<p style="font-weight:700;font-size:13px">${discovered ? def.name : '???'}</p>
${discovered ? `<span class="type-badge el-${def.type.toLowerCase()}" style="margin-top:4px">${def.type}</span>` : ''}
</div>`;
});
html += `</div>
<button class="glass-btn" id="btn-dex-back" style="margin-top:12px;width:100%;flex-shrink:0">← Back to Map</button>
</div>`;
el.innerHTML = html;
document.getElementById('btn-dex-back').addEventListener('click', () => { synth_1.synth.select(); renderMap(); });
}
// ===== PARTY SCREEN =====
function renderPartyScreen() {
showScreen('dialog');
const el = document.getElementById('screen-dialog');
let html = `
<div class="glass-card" style="padding:32px;max-width:500px;width:90%;animation:bounceIn 0.4s ease">
<h2 class="retro-title" style="font-size:16px;color:#c084fc;margin-bottom:20px;text-align:center">👥 Your Party</h2>
<div id="party-list" style="display:flex;flex-direction:column;gap:10px"></div>
<button class="glass-btn" id="btn-party-back" style="margin-top:20px;width:100%">← Back to Map</button>
</div>`;
el.innerHTML = html;
const listEl = document.getElementById('party-list');
playerState.party.forEach((r, i) => {
const def = (0, resonants_1.getResonantDef)(r.defId);
const card = document.createElement('div');
card.className = 'glass-card';
card.style.cssText = 'padding:14px;display:flex;align-items:center;gap:12px';
card.innerHTML = `
<div style="flex-shrink:0">${(0, sprites_1.renderResonantSVG)(r.defId, 50)}</div>
<div style="flex:1">
<p style="font-weight:700;font-size:13px">${def.name} <span class="type-badge el-${def.type.toLowerCase()}" style="font-size:9px;padding:1px 6px">Lv.${r.level}</span></p>
<div class="hp-bar-bg" style="margin-top:4px"><div class="hp-bar-fill" style="width:${(r.currentHp / r.maxHp) * 100}%;background:${combat.getHpColor(r.currentHp / r.maxHp)}"></div></div>
<p style="font-size:10px;color:#94a3b8;margin-top:2px">HP ${r.currentHp}/${r.maxHp} | ATK ${r.atk} | DEF ${r.def} | SPD ${r.spd}</p>
</div>`;
listEl.appendChild(card);
});
document.getElementById('btn-party-back').addEventListener('click', () => { synth_1.synth.select(); renderMap(); });
}
// ===== ZONE SELECT (when multiple zones unlocked) =====
function showZoneSelect() {
showScreen('dialog');
const el = document.getElementById('screen-dialog');
let html = `
<div class="glass-card" style="padding:36px 48px;text-align:center;max-width:500px;width:90%;animation:bounceIn 0.4s ease">
<h2 class="retro-title" style="font-size:16px;color:#c084fc;margin-bottom:24px">🗺️ Select Zone</h2>
<div id="zone-list" style="display:flex;flex-direction:column;gap:10px"></div>
<button class="glass-btn" id="btn-zone-back" style="margin-top:20px;width:100%">← Back to Map</button>
</div>`;
el.innerHTML = html;
const listEl = document.getElementById('zone-list');
playerState.unlockedZones.forEach(zoneId => {
const zone = zones_1.ZONES[zoneId];
if (!zone)
return;
const btn = document.createElement('button');
btn.className = 'glass-btn';
btn.style.width = '100%';
const isCurrent = playerState.currentZone === zoneId;
btn.innerHTML = `<span style="color:${zone.color}">●</span> ${zone.name}${isCurrent ? ' (current)' : ''}`;
if (isCurrent)
btn.style.opacity = '0.6';
btn.addEventListener('click', () => {
synth_1.synth.select();
playerState.currentZone = zoneId;
(0, player_1.saveState)(playerState);
renderMap();
});
listEl.appendChild(btn);
});
document.getElementById('btn-zone-back').addEventListener('click', () => { synth_1.synth.select(); renderMap(); });
}
// ===== VICTORY SCREEN =====
function showVictoryScreen() {
showScreen('dialog');
const el = document.getElementById('screen-dialog');
el.innerHTML = `
<div class="victory-overlay">
<div class="victory-card glass-card">
<h1 class="retro-title" style="font-size:20px;color:#c084fc;margin-bottom:16px;text-shadow:0 0 30px rgba(192,132,252,0.6)">🏆 VICTORY 🏆</h1>
<p style="font-size:14px;color:#e2e8f0;margin-bottom:8px">You have conquered all of Aethelgard!</p>
<p style="font-size:13px;color:#94a3b8;margin-bottom:24px">The Cosmos Sovereign has been harmonized. Peace returns to the land.</p>
<div style="display:flex;flex-direction:column;gap:6px;font-size:12px;color:#cbd5e1;margin-bottom:24px;text-align:left;padding:0 20px">
<p>Total Battles: ${playerState.totalBattles}</p>
<p>Resonants Captured: ${playerState.totalCaptures + playerState.dexEntries.length}</p>
<p>Dex Completion: ${playerState.dexEntries.length}/18</p>
<p>Aether Shards Earned: ${playerState.shards}</p>
</div>
<button class="glass-btn success" id="btn-victory-menu" style="width:200px;margin:0 auto">Return to Menu</button>
</div>
</div>`;
document.getElementById('btn-victory-menu').addEventListener('click', () => { synth_1.synth.select(); renderMainMenu(); });
}
// ===== GAME INIT =====
function init() {
const saved = localStorage.getItem('aethelgard_save_v1');
if (saved) {
renderMainMenu();
}
else {
renderMainMenu();
}
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"a6badb3d-1198-4f83-bbd0-47249d6b2fe5","tokensIn":4787878,"tokensOut":49466,"tokensTotal":4837344,"turns":87,"toolCalls":86,"failedToolCalls":0,"timestamp":"2026-07-31T06:39:58.537Z"} -->