← Creature Collection results

Creature Collection

Qwen 3.6 27b MTP · typescript

Initial view of Creature Collection

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 137,041 bytes · SHA-256 557a78da2e2b
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aetheris: Echoes of the Prism</title>
<style>
/* ── css: layout ── */
:root {
  --bg-deep: #0a0e1a;
  --glass-bg: rgba(255,255,255,0.08);
  --glass-border: rgba(255,255,255,0.15);
  --glass-shadow: rgba(0,0,0,0.4);
  --accent: #7b68ee;
  --accent-glow: #9d8df1;
  --text-primary: #e8e6f0;
  --text-secondary: #a09cb5;
  --hp-green: #4ade80;
  --hp-yellow: #fbbf24;
  --hp-red: #ef4444;
  --pyra: #ff6b35;
  --aqua: #3b82f6;
  --terra: #84cc16;
  --lux: #facc15;
  --umbra: #7c3aed;
  --void-type: #e040fb;
  --prism: #ffffff;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: var(--bg-deep); font-family: 'Segoe UI', system-ui, sans-serif; color: var(--text-primary); }

#game-container { position: relative; width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center; overflow: hidden; }
#game-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; image-rendering: pixelated; z-index: 1; }

#ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; }
.screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: none; pointer-events: auto; }
.screen.active { display: flex; }

#title-screen { flex-direction: column; align-items: center; justify-content: center; background: radial-gradient(ellipse at center, #1a1040 0%, #0a0e1a 70%); z-index: 20; pointer-events: auto; }
#title-text { font-size: clamp(3rem, 8vw, 6rem); letter-spacing: 0.3em; color: var(--accent-glow); text-shadow: 0 0 40px rgba(123,104,238,0.5), 0 0 80px rgba(123,104,238,0.2); animation: titlePulse 3s ease-in-out infinite; }
#subtitle { font-size: clamp(0.9rem, 2vw, 1.4rem); color: var(--text-secondary); letter-spacing: 0.5em; margin-top: 0.5rem; text-transform: uppercase; }

@keyframes titlePulse {
  0%, 100% { opacity: 0.85; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.02); }
}

.glass-btn { pointer-events: auto; margin-top: 2rem; padding: 0.9rem 3rem; font-size: 1.1rem; background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 12px; color: var(--text-primary); cursor: pointer; backdrop-filter: blur(10px); transition: all 0.2s; letter-spacing: 0.1em; }
.glass-btn:hover { background: rgba(123,104,238,0.25); border-color: var(--accent-glow); box-shadow: 0 0 20px rgba(123,104,238,0.3); transform: translateY(-2px); }
.glass-btn:active { transform: translateY(0); }

.hidden { display: none !important; }

#battle-screen { flex-direction: column; background: #0a0e1a; z-index: 15; pointer-events: auto; }
#battle-canvas { width: 100%; height: 100%; }
#battle-ui { position: absolute; bottom: 0; left: 0; right: 0; pointer-events: auto; z-index: 25; }

#overworld-ui { flex-direction: column; z-index: 12; pointer-events: none; }
#hud-top { display: flex; justify-content: space-between; padding: 12px 16px; background: linear-gradient(to bottom, rgba(10,14,26,0.8), transparent); pointer-events: auto; z-index: 13; position: relative; }
#zone-name { font-size: 1rem; color: var(--text-secondary); letter-spacing: 0.15em; text-transform: uppercase; }
.icon-btn { background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 8px; color: var(--text-primary); font-size: 1.3rem; padding: 6px 12px; cursor: pointer; backdrop-filter: blur(5px); }

#touch-controls { position: absolute; bottom: 20px; left: 0; right: 0; display: flex; justify-content: space-between; align-items: center; padding: 0 20px; pointer-events: auto; z-index: 14; }
#dpad { display: grid; grid-template-columns: repeat(3, 50px); grid-template-rows: repeat(2, 50px); gap: 4px; }
#dpad button { background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 8px; color: var(--text-primary); font-size: 1.2rem; cursor: pointer; backdrop-filter: blur(5px); }
.action-btn { width: 60px; height: 60px; border-radius: 50%; background: rgba(123,104,238,0.3); border: 2px solid var(--accent-glow); color: white; font-size: 1.2rem; font-weight: bold; cursor: pointer; }

#modal-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 50; backdrop-filter: blur(4px); pointer-events: auto; }
.modal { background: linear-gradient(135deg, rgba(20,15,40,0.95), rgba(10,14,26,0.98)); border: 1px solid var(--glass-border); border-radius: 16px; padding: 24px; max-width: 90vw; max-height: 85vh; overflow-y: auto; backdrop-filter: blur(20px); box-shadow: 0 20px 60px rgba(0,0,0,0.5); min-width: 300px; pointer-events: auto; }
.modal h2 { font-size: 1.4rem; margin-bottom: 16px; color: var(--accent-glow); letter-spacing: 0.1em; }

.hp-bar-container { width: 100%; height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; overflow: hidden; margin-top: 4px; }
.hp-bar-fill { height: 100%; border-radius: 4px; transition: width 0.5s ease; background: var(--hp-green); }

@keyframes floatUp { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-60px); } }
.float-text { position: absolute; font-weight: bold; pointer-events: none; animation: floatUp 1s ease-out forwards; text-shadow: 0 2px 4px rgba(0,0,0,0.5); z-index: 50; }

@keyframes shakeX { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } }
.shake { animation: shakeX 0.3s ease; }

@keyframes critFlash { 0% { filter: brightness(1); } 50% { filter: brightness(2.5); } 100% { filter: brightness(1); } }
.crit-flash { animation: critFlash 0.4s ease; }

#dialog-box { background: linear-gradient(to top, rgba(10,14,26,0.95), rgba(10,14,26,0.8)); border-top: 1px solid var(--glass-border); padding: 16px 20px; min-height: 80px; font-size: 0.95rem; line-height: 1.5; backdrop-filter: blur(10px); pointer-events: auto; }

#action-menu { display: flex; gap: 8px; padding: 12px; background: rgba(10,14,26,0.7); border-top: 1px solid var(--glass-border); flex-wrap: wrap; pointer-events: auto; }
.action-btn-choice { padding: 8px 16px; background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 8px; color: var(--text-primary); cursor: pointer; font-size: 0.9rem; transition: all 0.2s; }
.action-btn-choice:hover { background: rgba(123,104,238,0.25); border-color: var(--accent-glow); }

#ability-list { padding: 12px; display: grid; gap: 6px; max-height: 200px; overflow-y: auto; pointer-events: auto; }
.ability-btn { padding: 10px 14px; background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 8px; color: var(--text-primary); cursor: pointer; text-align: left; font-size: 0.85rem; transition: all 0.2s; }
.ability-btn:hover { background: rgba(123,104,238,0.25); border-color: var(--accent-glow); }
.ability-btn .ab-name { font-weight: bold; }
.ability-btn .ab-info { color: var(--text-secondary); font-size: 0.75rem; margin-top: 2px; }

#enemy-area, #player-area { padding: 10px 16px; pointer-events: auto; }
.creature-card { display: inline-block; background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 10px; padding: 8px 14px; margin: 4px; min-width: 150px; backdrop-filter: blur(5px); }
.creature-card .name { font-weight: bold; font-size: 0.9rem; }
.creature-card .level { color: var(--text-secondary); font-size: 0.75rem; }

#btn-menu { pointer-events: auto; }

@media (max-width: 600px) {
  #touch-controls.hidden { display: none !important; }
}
</style>
</head>
<body>
<div id="game-container">
  <canvas id="game-canvas"></canvas>
  <div id="ui-layer">
    <div id="title-screen" class="screen active">
      <h1 id="title-text">AETHERIS</h1>
      <p id="subtitle">Echoes of the Prism</p>
      <button id="btn-new" class="glass-btn">New Binding</button>
      <button id="btn-continue" class="glass-btn hidden">Continue</button>
    </div>
    <div id="battle-screen" class="screen">
      <canvas id="battle-canvas"></canvas>
      <div id="battle-ui">
        <div id="enemy-area"></div>
        <div id="player-area"></div>
        <div id="dialog-box"></div>
        <div id="action-menu" class="hidden"></div>
        <div id="ability-list" class="hidden"></div>
      </div>
    </div>
    <div id="overworld-ui" class="screen">
      <div id="hud-top">
        <span id="zone-name"></span>
        <button id="btn-menu" class="icon-btn">☰</button>
      </div>
      <div id="touch-controls" class="hidden">
        <div id="dpad">
          <button data-dir="up">▲</button>
          <button data-dir="left">◄</button>
          <button data-dir="right">►</button>
          <button data-dir="down">▼</button>
        </div>
        <button id="btn-action" class="action-btn">A</button>
      </div>
    </div>
    <div id="modal-overlay" class="hidden"></div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./state":"src/state.ts","./data/creatures":"src/data/creatures.ts","./data/zones":"src/data/zones.ts","./overworld":"src/overworld.ts","./battle":"src/battle.ts","./renderer":"src/renderer.ts","./ui":"src/ui.ts","./audio":"src/audio.ts"},"src/data/creatures.ts":{"../types":"src/types.ts"},"src/data/abilities.ts":{"../types":"src/types.ts"},"src/data/zones.ts":{"../types":"src/types.ts"},"src/state.ts":{"./types":"src/types.ts","./data/creatures":"src/data/creatures.ts","./data/zones":"src/data/zones.ts"},"src/battle.ts":{"./types":"src/types.ts","./data/abilities":"src/data/abilities.ts","./data/creatures":"src/data/creatures.ts","./data/zones":"src/data/zones.ts"},"src/overworld.ts":{"./types":"src/types.ts","./data/zones":"src/data/zones.ts","./data/creatures":"src/data/creatures.ts","./state":"src/state.ts"},"src/renderer.ts":{"./types":"src/types.ts","./data/creatures":"src/data/creatures.ts","./data/zones":"src/data/zones.ts","./overworld":"src/overworld.ts","./battle":"src/battle.ts"},"src/ui.ts":{"./types":"src/types.ts","./data/creatures":"src/data/creatures.ts","./data/abilities":"src/data/abilities.ts","./data/zones":"src/data/zones.ts","./overworld":"src/overworld.ts","./state":"src/state.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 state_1 = require("./state");
const creatures_1 = require("./data/creatures");
const zones_1 = require("./data/zones");
const overworld_1 = require("./overworld");
const battle_1 = require("./battle");
const renderer_1 = require("./renderer");
const ui = __importStar(require("./ui"));
const audio = __importStar(require("./audio"));
// === GAME STATE ===
let state = (0, state_1.createInitialState)();
let tutorialStep = -1; // -1 = no tutorial shown yet
let inTutorial = false;
let battleResolveCallback = null;
let captureItemUsed = '';
// === SCREEN MANAGEMENT ===
const titleScreen = document.getElementById('title-screen');
const battleScreen = document.getElementById('battle-screen');
const overworldUI = document.getElementById('overworld-ui');
function showScreen(name) {
    state.screen = name;
    titleScreen.classList.toggle('active', name === 'title');
    battleScreen.classList.toggle('active', name === 'battle');
    overworldUI.classList.toggle('active', name === 'overworld');
    // Show/hide touch controls based on screen
    const tc = document.getElementById('touch-controls');
    if (tc) {
        if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
            tc.classList.toggle('hidden', name !== 'overworld');
        }
    }
}
// === INITIALIZATION ===
function init() {
    (0, renderer_1.resizeCanvas)();
    window.addEventListener('resize', renderer_1.resizeCanvas);
    // Check for save game
    if ((0, state_1.hasSave)()) {
        document.getElementById('btn-continue')?.classList.remove('hidden');
    }
    // Event listeners
    document.getElementById('btn-new').onclick = startNewGame;
    document.getElementById('btn-continue').onclick = continueGame;
    document.getElementById('btn-menu').onclick = openMainMenu;
    // Modal event delegation
    document.getElementById('modal-overlay').addEventListener('click', handleModalClick);
    // Keyboard input
    window.addEventListener('keydown', handleKeyDown);
    // Touch controls
    setupTouchControls();
    // Start render loop
    requestAnimationFrame(gameLoop);
}
function startNewGame() {
    audio.initAudio();
    state = (0, state_1.createInitialState)();
    tutorialStep = 0;
    inTutorial = true;
    showScreen('overworld');
    ui.showModal(ui.showTutorialStep(0));
}
function continueGame() {
    audio.initAudio();
    const saved = (0, state_1.loadGame)();
    if (saved) {
        state = saved;
        tutorialStep = -1;
        inTutorial = false;
        showScreen('overworld');
    }
}
// === INPUT HANDLING ===
let moveCooldown = 0;
function handleKeyDown(e) {
    const modalOpen = document.getElementById('modal-overlay').classList.contains('active');
    if (state.screen === 'overworld' && !modalOpen) {
        audio.initAudio();
        const key = e.key.toLowerCase();
        let dx = 0, dy = 0;
        switch (key) {
            case 'w':
            case 'arrowup':
                dy = -1;
                break;
            case 's':
            case 'arrowdown':
                dy = 1;
                break;
            case 'a':
            case 'arrowleft':
                dx = -1;
                break;
            case 'd':
            case 'arrowright':
                dx = 1;
                break;
            case 'escape':
                openMainMenu();
                return;
            default: return;
        }
        e.preventDefault();
        movePlayer(dx, dy);
    }
    else if (state.screen === 'title') {
        audio.initAudio();
        const key = e.key.toLowerCase();
        if (key === 'enter' || key === ' ') {
            // Start new game on Enter/Space at title screen
            startNewGame();
        }
    }
    else if (state.screen === 'battle') {
        audio.initAudio();
        const key = e.key.toLowerCase();
        if (key === 'escape' || key === 'b') {
            // In battle, B/Escape could show menu or confirm dialog advance
            const bs = (0, battle_1.getBattleState)();
            if (bs && !(0, battle_1.isAnimating)()) {
                ui.hideActionMenu();
                ui.hideAbilityList();
                showBattleActions();
            }
        }
    }
}
function setupTouchControls() {
    const dpad = document.getElementById('dpad');
    if (!dpad)
        return;
    // Show touch controls on mobile
    if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
        dpad.classList.remove('hidden');
    }
    dpad.querySelectorAll('button').forEach(btn => {
        const dir = btn.getAttribute('data-dir');
        if (!dir)
            return;
        const handler = () => {
            audio.initAudio();
            let dx = 0, dy = 0;
            switch (dir) {
                case 'up':
                    dy = -1;
                    break;
                case 'down':
                    dy = 1;
                    break;
                case 'left':
                    dx = -1;
                    break;
                case 'right':
                    dx = 1;
                    break;
            }
            movePlayer(dx, dy);
        };
        btn.addEventListener('click', handler);
        btn.addEventListener('touchstart', (e) => { e.preventDefault(); handler(); });
    });
    const actionBtn = document.getElementById('btn-action');
    if (actionBtn) {
        actionBtn.addEventListener('click', () => {
            audio.initAudio();
            interactAtPosition();
        });
    }
}
function movePlayer(dx, dy) {
    if (moveCooldown > 0)
        return;
    const nx = state.playerX + dx;
    const ny = state.playerY + dy;
    if (!(0, overworld_1.isWalkable)(state.currentZone, nx, ny))
        return;
    state.playerX = nx;
    state.playerY = ny;
    moveCooldown = 6; // frames between moves
    audio.playStep();
    // Check for interactions at new position
    checkPositionInteractions();
}
function interactAtPosition() {
    if (state.screen !== 'overworld')
        return;
    checkPositionInteractions();
}
function checkPositionInteractions() {
    // Check shrine
    if ((0, overworld_1.checkShrine)(state)) {
        ui.showModal(ui.showShrineMenu(state));
        return;
    }
    // Check zone exit
    const nextZone = (0, overworld_1.checkZoneExit)(state);
    if (nextZone) {
        state.currentZone = nextZone;
        state.playerX = overworld_1.MAP_W - 3;
        state.playerY = overworld_1.MAP_H - 3;
        ui.showDialog(`Entered ${zones_1.ZONES[nextZone].name}!`);
        return;
    }
    // Check wild encounter
    const encounter = (0, overworld_1.checkEncounter)(state);
    if (encounter) {
        startWildBattle(encounter);
    }
}
// === BATTLE SYSTEM ===
function startWildBattle(encounter) {
    if (!encounter || state.party.length === 0)
        return;
    showScreen('battle');
    audio.playBattleStart();
    const bs = (0, battle_1.startBattle)(state.party, encounter.enemies, !encounter.isBoss, state.currentZone);
    ui.updateBattleCards(state);
    ui.updateEnemyCards(bs.enemies);
    ui.showDialog(bs.dialogText);
    ui.hideActionMenu();
    ui.hideAbilityList();
    // Show battle action menu after a delay
    setTimeout(() => {
        if ((0, battle_1.isPlayerTurn)())
            showBattleActions();
    }, 500);
}
function showBattleActions() {
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    ui.showActionMenu([
        { label: '⚔ Attack', action: () => showAbilities() },
        { label: '🎒 Bag', action: () => useItemInBattle() },
        { label: '🔄 Switch', action: () => switchCreature() },
        { label: '🕸 Capture', action: () => attemptCapture() }
    ]);
}
function showAbilities() {
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    const actor = (0, battle_1.getCurrentActor)();
    if (!actor)
        return;
    ui.showAbilityList(actor.abilities, (abilityId) => {
        // Find the player index of current actor
        const pIdx = bs.playerParty.indexOf(actor);
        if (pIdx < 0)
            return;
        const msgs = (0, battle_1.executeMove)(pIdx, abilityId);
        ui.showDialog(msgs.join(' '));
        ui.updateBattleCards(state);
        ui.updateEnemyCards(bs.enemies);
        // Check battle end
        setTimeout(() => {
            const result = (0, battle_1.checkBattleEnd)();
            if (result) {
                handleBattleResult(result);
            }
            else {
                (0, battle_1.advanceTurn)();
                processNextTurn();
            }
        }, 800);
    });
}
function useItemInBattle() {
    // Use a healing item on active creature
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    const actor = (0, battle_1.getCurrentActor)();
    if (!actor || actor.currentHp >= actor.maxHp) {
        ui.showDialog('No items needed or available.');
        return;
    }
    // Try prism elixir first, then aether draught
    let itemId = '';
    if ((0, state_1.hasItem)(state, 'prism-elixir'))
        itemId = 'prism-elixir';
    else if ((0, state_1.hasItem)(state, 'aether-draught'))
        itemId = 'aether-draught';
    if (!itemId) {
        ui.showDialog('No healing items available!');
        return;
    }
    (0, state_1.removeInventoryItem)(state, itemId);
    const itemDef = Object.values(zones_1.ZONES).find(() => false) || {}; // dummy
    actor.currentHp = Math.min(actor.maxHp, actor.currentHp + (itemId === 'prism-elixir' ? 999 : 50));
    ui.showDialog(`Used ${itemId === 'prism-elixir' ? 'Prism Elixir' : 'Aether Draught'}! HP restored.`);
    ui.updateBattleCards(state);
    setTimeout(() => {
        (0, battle_1.advanceTurn)();
        processNextTurn();
    }, 600);
}
function switchCreature() {
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    // Find next alive creature
    let nextIdx = -1;
    for (let i = 0; i < state.party.length; i++) {
        if (state.party[i].currentHp > 0 && i !== bs.activePlayerIndex) {
            nextIdx = i;
            break;
        }
    }
    if (nextIdx < 0) {
        // Try any alive creature
        for (let i = 0; i < state.party.length; i++) {
            if (state.party[i].currentHp > 0) {
                nextIdx = i;
                break;
            }
        }
    }
    if (nextIdx >= 0) {
        bs.activePlayerIndex = nextIdx;
        const tmpl = (0, creatures_1.getCreatureTemplate)(state.party[nextIdx].templateId);
        ui.showDialog(`Switched to ${tmpl.name}!`);
        ui.updateBattleCards(state);
        setTimeout(() => showBattleActions(), 500);
    }
    else {
        ui.showDialog('No other creatures available!');
    }
}
function attemptCapture() {
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    // Find best capture item
    let itemId = '';
    if ((0, state_1.hasItem)(state, 'prism-binder'))
        itemId = 'prism-binder';
    else if ((0, state_1.hasItem)(state, 'resonance-cage'))
        itemId = 'resonance-cage';
    else if ((0, state_1.hasItem)(state, 'aether-net'))
        itemId = 'aether-net';
    if (!itemId) {
        ui.showDialog('No capture items available!');
        return;
    }
    (0, state_1.removeInventoryItem)(state, itemId);
    captureItemUsed = itemId;
    (0, battle_1.startCapture)();
    // Show capture mini-game
    ui.hideActionMenu();
    ui.hideAbilityList();
    ui.showModal(ui.showCaptureMiniGame());
    // Setup tap button
    const tapBtn = document.querySelector('.capture-tap-btn');
    if (tapBtn) {
        let taps = 0;
        const interval = setInterval(() => {
            taps++;
            const fill = document.getElementById('capture-fill');
            if (fill)
                fill.style.width = `${Math.min(taps * 8, 100)}%`;
        }, 200);
        // Auto-resolve after 3 seconds
        setTimeout(() => {
            clearInterval(interval);
            const success = (0, battle_1.resolveCapture)(itemId);
            ui.hideModal();
            if (success) {
                handleCaptureSuccess(bs, itemId);
            }
            else {
                ui.showDialog('The creature broke free!');
                audio.playDefeat();
                setTimeout(() => showBattleActions(), 1000);
            }
        }, 3000);
    }
}
function handleCaptureSuccess(bs, itemId) {
    if (!bs)
        return;
    // Find weakest enemy to capture
    const target = bs.enemies.reduce((best, e) => {
        if (e.currentHp <= 0)
            return best;
        const ratio = e.currentHp / e.maxHp;
        return (!best || ratio < (best.currentHp / best.maxHp)) ? e : best;
    }, null);
    if (!target)
        return;
    // Add to party
    if (state.party.length < 6) {
        state.party.push(target);
        state.collectedCreatures.add(target.templateId);
        ui.showDialog(`Captured ${(0, creatures_1.getCreatureTemplate)(target.templateId).name}!`);
        audio.playCapture();
    }
    else {
        // Party full - release weakest or store in reserve
        const released = state.party.shift();
        if (released) {
            ui.showDialog(`Party full! Released ${(0, creatures_1.getCreatureTemplate)(released.templateId).name}. Captured ${(0, creatures_1.getCreatureTemplate)(target.templateId).name}!`);
        }
        state.party.push(target);
        state.collectedCreatures.add(target.templateId);
        audio.playCapture();
    }
    // Remove captured enemy from battle
    const idx = bs.enemies.indexOf(target);
    if (idx >= 0)
        bs.enemies.splice(idx, 1);
    // Check if all enemies gone
    if (bs.enemies.length === 0 || bs.enemies.every(e => e.currentHp <= 0)) {
        handleBattleResult('victory');
    }
    else {
        (0, battle_1.advanceTurn)();
        processNextTurn();
    }
}
function handleBattleResult(result) {
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    (0, battle_1.resetCapture)();
    if (result === 'victory') {
        audio.playVictory();
        // Check if it was a guardian battle
        const zone = zones_1.ZONES[state.currentZone];
        if (zone.guardianId && bs.enemies.some(e => e.templateId === zone.guardianId)) {
            state.defeatedGuardians.push(state.currentZone);
            audio.playGuardianDefeated();
            // Award catalyst item
            const catalysts = {
                'ember_pass': 'pyre-shard',
                'deep_trench': 'aqua-core',
                'stoneheart_cavern': 'terra-gem',
                'prism_spire': 'void-fragment'
            };
            if (catalysts[state.currentZone]) {
                (0, state_1.addInventoryItem)(state, catalysts[state.currentZone]);
                ui.showDialog(`Guardian defeated! Received ${catalysts[state.currentZone].replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())}!`);
            }
            // Story progression
            if (state.storyStage < 3) {
                state.storyStage++;
            }
            // Check for post-game
            if (state.defeatedGuardians.length >= 4 && !state.postGame) {
                state.postGame = true;
                ui.showDialog('All Anchors defeated! The Between awaits...');
            }
        }
        else {
            // Regular wild battle - EXP gain
            let totalExpMsg = '';
            for (const enemy of bs.enemies) {
                const expGain = Math.floor(enemy.level * 3 + state.party[0].level);
                for (const pc of state.party) {
                    if (pc.currentHp > 0) {
                        const msgs = (0, state_1.gainExp)(pc, expGain);
                        totalExpMsg += msgs.join(' ');
                    }
                }
            }
            // Random item drops
            if (Math.random() < 0.3)
                (0, state_1.addInventoryItem)(state, 'aether-draught');
            if (Math.random() < 0.15)
                (0, state_1.addInventoryItem)(state, 'resonance-cage');
            ui.showDialog(`Victory! ${totalExpMsg}`);
        }
        // Return to overworld after delay
        setTimeout(() => {
            showScreen('overworld');
            ui.hideDialog();
            ui.hideActionMenu();
            ui.hideAbilityList();
            (0, state_1.saveGame)(state);
        }, 2000);
    }
    else {
        // Defeat - revive at shrine with half HP
        audio.playDefeat();
        for (const c of state.party) {
            c.currentHp = Math.floor(c.maxHp * 0.5);
            if (c.status)
                c.status = undefined;
        }
        ui.showDialog('Your creatures fainted... Revived at the last shrine.');
        // Return to shrine position
        const sPos = (0, overworld_1.checkShrine)(state) ? { x: state.playerX, y: state.playerY } : null;
        if (!sPos) {
            // Reset to zone center
            state.playerX = Math.floor(overworld_1.MAP_W / 2);
            state.playerY = Math.floor(overworld_1.MAP_H / 2);
        }
        setTimeout(() => {
            showScreen('overworld');
            ui.hideDialog();
            ui.hideActionMenu();
            ui.hideAbilityList();
            (0, state_1.saveGame)(state);
        }, 2000);
    }
}
function processNextTurn() {
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    // Skip fainted actors in turn order
    while (bs.currentTurnIndex < bs.turnOrder.length) {
        const idx = bs.turnOrder[bs.currentTurnIndex];
        let actor = null;
        let isPlayer = false;
        if (idx < bs.playerParty.length) {
            actor = bs.playerParty[idx];
            isPlayer = true;
        }
        else {
            const eIdx = idx - bs.playerParty.length;
            if (eIdx >= 0 && eIdx < bs.enemies.length) {
                actor = bs.enemies[eIdx];
            }
        }
        if (!actor || actor.currentHp <= 0) {
            (0, battle_1.advanceTurn)();
            continue;
        }
        // Check shock status - skip turn
        if (actor.status?.effect === 'Shock' && Math.random() < 0.3) {
            ui.showDialog(`${(0, creatures_1.getCreatureTemplate)(actor.templateId).name} is paralyzed and can't move!`);
            (0, battle_1.advanceTurn)();
            setTimeout(processNextTurn, 800);
            return;
        }
        if (isPlayer) {
            bs.activePlayerIndex = idx;
            ui.updateBattleCards(state);
            showBattleActions();
            return;
        }
        else {
            // Enemy turn - auto execute after delay
            setTimeout(() => {
                const msgs = (0, battle_1.executeEnemyTurn)();
                ui.showDialog(msgs.join(' '));
                ui.updateBattleCards(state);
                ui.updateEnemyCards(bs.enemies);
                setTimeout(() => {
                    const result = (0, battle_1.checkBattleEnd)();
                    if (result)
                        handleBattleResult(result);
                    else {
                        (0, battle_1.advanceTurn)();
                        processNextTurn();
                    }
                }, 800);
            }, 600);
            return;
        }
    }
    // All turns done - new round
    recalcTurnOrderAndContinue();
}
function recalcTurnOrderAndContinue() {
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    // Process status effects for new round
    const statusMsgs = [];
    for (const c of [...bs.playerParty, ...bs.enemies]) {
        if (!c.status || c.currentHp <= 0)
            continue;
        const name = (0, creatures_1.getCreatureTemplate)(c.templateId).name;
        switch (c.status.effect) {
            case 'Burn': {
                const dmg = Math.floor(c.atk * 0.5);
                c.currentHp -= dmg;
                if (c.currentHp < 0)
                    c.currentHp = 0;
                statusMsgs.push(`${name} is hurt by burn!`);
                break;
            }
            case 'Regen': {
                const heal = Math.floor(c.maxHp * 0.15);
                c.currentHp = Math.min(c.maxHp, c.currentHp + heal);
                statusMsgs.push(`${name} regenerates HP!`);
                break;
            }
        }
        c.status.turns--;
        if (c.status.turns <= 0)
            c.status = undefined;
    }
    if (statusMsgs.length > 0) {
        ui.showDialog(statusMsgs.join(' '));
    }
    // Recalculate turn order
    const all = [...bs.playerParty.map((_, i) => ({ idx: i, spd: bs.playerParty[i].spd })),
        ...bs.enemies.map((_, i) => ({ idx: bs.playerParty.length + i, spd: bs.enemies[i].spd }))];
    const alive = all.filter(e => {
        if (e.idx < bs.playerParty.length)
            return bs.playerParty[e.idx].currentHp > 0;
        return bs.enemies[e.idx - bs.playerParty.length].currentHp > 0;
    });
    alive.sort((a, b) => b.spd - a.spd);
    bs.turnOrder = alive.map(e => e.idx);
    bs.currentTurnIndex = 0;
    setTimeout(processNextTurn, statusMsgs.length > 0 ? 1000 : 300);
}
// === MENU SYSTEM ===
function openMainMenu() {
    if (state.screen !== 'overworld')
        return;
    ui.showModal(ui.showMainMenu(state));
}
function handleModalClick(e) {
    const target = e.target;
    // Use closest() to find the button with data attributes (handles clicks on child elements)
    const menuBtn = target.closest('[data-menu]');
    const shrineBtn = target.closest('[data-shrine]');
    const tutBtn = target.closest('[data-tut]');
    const starterBtn = target.closest('[data-starter]');
    const buyBtn = target.closest('[data-buy]');
    const transformBtn = target.closest('[data-tidx]');
    const dataMenu = menuBtn ? menuBtn.getAttribute('data-menu') : null;
    const dataShrine = shrineBtn ? shrineBtn.getAttribute('data-shrine') : null;
    const dataTut = tutBtn ? tutBtn.getAttribute('data-tut') : null;
    const dataStarter = starterBtn ? starterBtn.getAttribute('data-starter') : null;
    const dataBuy = buyBtn ? buyBtn.getAttribute('data-buy') : null;
    const dataTidx = transformBtn ? transformBtn.getAttribute('data-tidx') : null;
    // Tutorial buttons
    if (dataTut) {
        switch (dataTut) {
            case 'next':
                tutorialStep++;
                ui.showModal(ui.showTutorialStep(tutorialStep));
                break;
            case 'back':
                tutorialStep--;
                ui.showModal(ui.showTutorialStep(tutorialStep));
                break;
            case 'done':
                inTutorial = false;
                tutorialStep = -1;
                // Give starter creature
                ui.hideModal();
                ui.showModal(ui.showStarterChoice());
                break;
        }
        return;
    }
    // Starter choice
    if (dataStarter && starterBtn) {
        const creature = (0, state_1.createCreature)(dataStarter, 3);
        state.party.push(creature);
        state.collectedCreatures.add(dataStarter);
        (0, state_1.addInventoryItem)(state, 'aether-net', 5);
        (0, state_1.addInventoryItem)(state, 'resonance-cage', 2);
        (0, state_1.addInventoryItem)(state, 'aether-draught', 3);
        ui.hideModal();
        audio.startAmbient();
        return;
    }
    // Main menu buttons
    if (dataMenu) {
        switch (dataMenu) {
            case 'team':
                ui.showModal(ui.showTeamScreen(state));
                break;
            case 'bag':
                ui.showModal(ui.showBagScreen(state));
                break;
            case 'encyclopedia':
                ui.showModal(ui.showBestiaryScreen(state));
                break;
            case 'map':
                ui.showModal(ui.showMapScreen(state));
                break;
            case 'save':
                (0, state_1.saveGame)(state);
                ui.hideModal();
                ui.showDialog('Game saved!');
                setTimeout(() => ui.hideDialog(), 1000);
                break;
            case 'close':
                ui.hideModal();
                break;
        }
        return;
    }
    // Shrine buttons
    if (dataShrine) {
        switch (dataShrine) {
            case 'heal':
                (0, overworld_1.healParty)(state.party);
                ui.showDialog('Party fully healed!');
                setTimeout(() => ui.hideModal(), 800);
                break;
            case 'save':
                (0, state_1.saveGame)(state);
                ui.showDialog('Game saved at shrine.');
                setTimeout(() => ui.hideModal(), 800);
                break;
            case 'shop':
                ui.showModal(ui.showShopScreen(state));
                break;
            case 'close':
                ui.hideModal();
                break;
        }
        return;
    }
    // Shop buy buttons
    if (dataBuy) {
        (0, state_1.addInventoryItem)(state, dataBuy);
        const item = Object.values(zones_1.ZONES).find(() => false); // dummy to keep import
        ui.showDialog(`Received ${dataBuy.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())}!`);
        setTimeout(() => {
            ui.hideModal();
            ui.showModal(ui.showShopScreen(state));
        }, 500);
        return;
    }
    // Transform button
    if (dataTidx !== null) {
        const idx = parseInt(dataTidx);
        const creature = state.party[idx];
        const tmpl = (0, creatures_1.getCreatureTemplate)(creature.templateId);
        if (tmpl.catalystItem && (0, state_1.hasItem)(state, tmpl.catalystItem)) {
            (0, state_1.removeInventoryItem)(state, tmpl.catalystItem);
            // Simple transform - just update stats and abilities
            creature.level += 5; // Boost level on transform
            const newStats = (0, creatures_1.getCreatureTemplate)(tmpl.transformId);
            if (newStats) {
                creature.templateId = newStats.id;
                creature.affinities = [...newStats.affinities];
                creature.maxHp = Math.floor(newStats.baseStats.hp * (1 + creature.level * 0.4));
                creature.atk = Math.floor(newStats.baseStats.atk * (1 + creature.level * 0.35));
                creature.def = Math.floor(newStats.baseStats.def * (1 + creature.level * 0.35));
                creature.spd = Math.floor(newStats.baseStats.spd * (1 + creature.level * 0.3));
                creature.currentHp = creature.maxHp;
                creature.abilities = newStats.abilities.map(aid => ({ abilityId: aid, pp: 25 }));
            }
            audio.playTransform();
            ui.hideModal();
            ui.showDialog(`${(0, creatures_1.getCreatureTemplate)(creature.templateId).name} transformed!`);
            setTimeout(() => ui.hideDialog(), 1500);
        }
        else {
            ui.showDialog(`Need ${tmpl.catalystItem?.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())} to transform.`);
        }
        return;
    }
    // Back button (generic)
    if (target.classList.contains('back-btn')) {
        ui.hideModal();
        ui.showModal(ui.showMainMenu(state));
        return;
    }
    // Click on overlay background (not inside modal) closes it
    const modalContent = target.closest('.modal');
    if (!modalContent && target === document.getElementById('modal-overlay')) {
        ui.hideModal();
    }
}
// === GAME LOOP ===
let lastTime = 0;
function gameLoop(timestamp) {
    const dt = timestamp - lastTime;
    lastTime = timestamp;
    if (moveCooldown > 0)
        moveCooldown--;
    // Render based on current screen
    if (state.screen === 'overworld' || state.screen === undefined) {
        (0, renderer_1.renderOverworld)(state);
    }
    else if (state.screen === 'battle') {
        (0, renderer_1.renderBattle)();
        (0, renderer_1.updateBattleEffects)();
    }
    requestAnimationFrame(gameLoop);
}
// Start the game
init();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TYPE_CHART = exports.AFFINITY_COLORS = exports.AFFINITIES = void 0;
exports.getEffectiveness = getEffectiveness;
// === AFFINITY TYPES ===
exports.AFFINITIES = ['Pyra', 'Aqua', 'Terra', 'Lux', 'Umbra', 'Void', 'Prism'];
exports.AFFINITY_COLORS = {
    Pyra: '#ff6b35', Aqua: '#3b82f6', Terra: '#84cc16',
    Lux: '#facc15', Umbra: '#7c3aed', Void: '#e040fb', Prism: '#ffffff'
};
// Type chart: key beats value
exports.TYPE_CHART = {
    Pyra: ['Aqua'], // Pyra > Aqua
    Aqua: ['Terra'], // Aqua > Terra
    Terra: ['Pyra'], // Terra > Pyra (cycle)
    Lux: ['Pyra', 'Terra'], // Lux beats Pyra & Terra
    Umbra: ['Lux', 'Aqua'], // Umbra beats Lux & Aqua
    Void: ['Pyra', 'Aqua', 'Terra', 'Lux', 'Umbra', 'Prism'], // Void beats all slightly
    Prism: [] // Prism resists everything, weak to Void only
};
function getEffectiveness(atkType, defType) {
    if (defType === 'Prism' && atkType !== 'Void')
        return 0.5; // Prism resists all except Void
    if (atkType === 'Void')
        return defType === 'Prism' ? 2.0 : 1.3; // Void beats all slightly, double vs Prism
    const targets = exports.TYPE_CHART[atkType];
    if (targets.includes(defType))
        return 2.0;
    return 1.0;
}
};

// ── module: src/data/creatures.ts ──
__mods["src/data/creatures.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CREATURES = void 0;
exports.getCreatureTemplate = getCreatureTemplate;
exports.CREATURES = {
    emberling: {
        id: 'emberling', name: 'Emberling', lore: 'A small spark-wisp born from geothermal vents. It curls into a warm ball when startled.',
        affinities: ['Pyra'], baseStats: { hp: 45, atk: 38, def: 22, spd: 30 },
        abilities: ['spark-bolt', 'ember-tail', 'flame-wave', 'inferno-crest'],
        transformId: 'blazewing', transformLevel: 25, catalystItem: 'pyre-shard'
    },
    tidepup: {
        id: 'tidepup', name: 'Tidepup', lore: 'A playful stream-dweller that shapes water into spinning shields. Known to guide lost travelers.',
        affinities: ['Aqua'], baseStats: { hp: 50, atk: 28, def: 32, spd: 26 },
        abilities: ['droplet-jab', 'tidal-swirl', 'frost-bite', 'deluge-curse'],
        transformId: 'leviathan_pup', transformLevel: 25, catalystItem: 'aqua-core'
    },
    mossback: {
        id: 'mossback', name: 'Mossback', lore: 'A gentle land-shaper with a shell of living crystal. Ancient roots grow beneath its feet.',
        affinities: ['Terra'], baseStats: { hp: 58, atk: 24, def: 36, spd: 18 },
        abilities: ['root-tangle', 'stone-throw', 'earth-pulse', 'crystal-bastion'],
        transformId: 'titan_mossback', transformLevel: 25, catalystItem: 'terra-gem'
    },
    glowmoth: {
        id: 'glowmoth', name: 'Glowmoth', lore: 'Drawn to luminous stones, its wings scatter prismatic dust that heals minor wounds.',
        affinities: ['Lux'], baseStats: { hp: 35, atk: 26, def: 18, spd: 40 },
        abilities: ['light-burst', 'glow-dust', 'radiant-wing'],
        transformId: 'radiant_moth', transformLevel: 30, catalystItem: 'lux-orb'
    },
    shadowfin: {
        id: 'shadowfin', name: 'Shadowfin', lore: 'A sleek predator that swims through darkness. Its silhouette shifts before striking.',
        affinities: ['Umbra'], baseStats: { hp: 40, atk: 35, def: 20, spd: 38 },
        abilities: ['dark-claw', 'shadow-step', 'void-gaze'],
        transformId: 'umbral_stalker', transformLevel: 30, catalystItem: 'umbra-essence'
    },
    voidling: {
        id: 'voidling', name: 'Voidling', lore: 'A fragment of the Between. Neither alive nor dead, it hums with unstable energy.',
        affinities: ['Void'], baseStats: { hp: 38, atk: 32, def: 16, spd: 42 },
        abilities: ['null-pulse', 'phase-shift', 'entropy-blast'],
        transformId: 'entropy_wraith', transformLevel: 30, catalystItem: 'void-fragment'
    },
    prismite: {
        id: 'prismite', name: 'Prismite', lore: 'A crystalline being that refracts all energies. Its core holds a sliver of the First Light.',
        affinities: ['Prism'], baseStats: { hp: 52, atk: 20, def: 40, spd: 22 },
        abilities: ['refract', 'prismatic-wall', 'light-prison'],
        transformId: 'prism_guardian', transformLevel: 30, catalystItem: 'prism-heart'
    },
    cinderfox: {
        id: 'cinderfox', name: 'Cinderfox', lore: 'A cunning fire-stalker with three tails of living flame. It leaves scorched pawprints.',
        affinities: ['Pyra'], baseStats: { hp: 42, atk: 36, def: 20, spd: 35 },
        abilities: ['ember-tail', 'flame-bite', 'fire-veil'],
        transformId: 'inferno_fox', transformLevel: 30, catalystItem: 'pyre-shard'
    },
    aquadrake: {
        id: 'aquadrake', name: 'Aquadrake', lore: 'A serpentine water-weaver that coils around ancient pillars. Its scales shimmer like deep-sea glass.',
        affinities: ['Aqua'], baseStats: { hp: 48, atk: 30, def: 28, spd: 24 },
        abilities: ['tidal-swirl', 'frost-bite', 'water-bind']
    },
    stonehorn: {
        id: 'stonehorn', name: 'Stonehorn', lore: 'A mountain-born guardian with horns of solidified magma. It charges when the earth trembles.',
        affinities: ['Terra'], baseStats: { hp: 62, atk: 30, def: 42, spd: 14 },
        abilities: ['stone-throw', 'earth-pulse', 'horn-charge']
    },
    dawnwisp: {
        id: 'dawnwisp', name: 'Dawnwisp', lore: 'A floating orb of morning light that sings in frequencies only crystals can hear.',
        affinities: ['Lux'], baseStats: { hp: 32, atk: 30, def: 16, spd: 44 },
        abilities: ['light-burst', 'sun-ray', 'heal-glow']
    },
    nightshade_cat: {
        id: 'nightshade_cat', name: 'Nightshade Cat', lore: 'A feline shadow that walks between worlds. Its eyes glow with stolen starlight.',
        affinities: ['Umbra'], baseStats: { hp: 38, atk: 34, def: 22, spd: 40 },
        abilities: ['dark-claw', 'shadow-step', 'night-howl']
    },
    riftworm: {
        id: 'riftworm', name: 'Riftworm', lore: 'A burrowing creature that feeds on dimensional tears. Its body phases in and out of reality.',
        affinities: ['Void'], baseStats: { hp: 44, atk: 28, def: 18, spd: 36 },
        abilities: ['null-pulse', 'phase-shift', 'rift-bite']
    },
    crystal_shard: {
        id: 'crystal_shard', name: 'Crystal Shard', lore: 'A sentient fragment of the Prism Spire. It resonates with nearby energy flows.',
        affinities: ['Prism'], baseStats: { hp: 46, atk: 22, def: 38, spd: 20 },
        abilities: ['refract', 'crystal-spikes', 'prismatic-wall']
    },
    pyre_golem: {
        id: 'pyre_golem', name: 'Pyre Golem', lore: 'The first Anchor. A mountain of living flame that guards the Ember Pass.',
        affinities: ['Pyra'], baseStats: { hp: 120, atk: 55, def: 45, spd: 18 },
        abilities: ['flame-wave', 'inferno-crest', 'magma-fall', 'pyre-roar']
    },
    abyssal_tide: {
        id: 'abyssal_tide', name: 'Abyssal Tide', lore: 'The second Anchor. A colossal water serpent that commands the Deep Trench.',
        affinities: ['Aqua'], baseStats: { hp: 130, atk: 50, def: 48, spd: 22 },
        abilities: ['deluge-curse', 'tidal-swirl', 'abyssal-pressure', 'frost-bite']
    },
    earthwarden: {
        id: 'earthwarden', name: 'Earthwarden', lore: 'The third Anchor. A crystalline colossus rooted in the Stoneheart Cavern.',
        affinities: ['Terra'], baseStats: { hp: 140, atk: 45, def: 60, spd: 12 },
        abilities: ['crystal-bastion', 'earth-pulse', 'stone-quake', 'horn-charge']
    },
    void_anchor: {
        id: 'void_anchor', name: 'Void Anchor', lore: 'The final Anchor. A being of pure Between-energy, existing in all places and none.',
        affinities: ['Void'], baseStats: { hp: 150, atk: 60, def: 40, spd: 38 },
        abilities: ['entropy-blast', 'phase-shift', 'void-collapse', 'null-pulse']
    },
    // TRANSFORMED FORMS (9)
    blazewing: {
        id: 'blazewing', name: 'Blazewing', lore: 'Emberling ascended. Wings of pure flame, eyes like twin suns.',
        affinities: ['Pyra'], baseStats: { hp: 72, atk: 58, def: 34, spd: 46 },
        abilities: ['spark-bolt', 'flame-wave', 'inferno-crest', 'skyfire-descent'], isTransformed: true
    },
    leviathan_pup: {
        id: 'leviathan_pup', name: 'Leviathan Pup', lore: 'Tidepup evolved. A miniature ocean titan with coral armor and tidal fury.',
        affinities: ['Aqua'], baseStats: { hp: 78, atk: 46, def: 50, spd: 32 },
        abilities: ['droplet-jab', 'tidal-swirl', 'deluge-curse', 'ocean-fury'], isTransformed: true
    },
    titan_mossback: {
        id: 'titan_mossback', name: 'Titan Mossback', lore: 'Mossback ultimate form. A walking fortress with crystal spires.',
        affinities: ['Terra'], baseStats: { hp: 90, atk: 42, def: 65, spd: 20 },
        abilities: ['root-tangle', 'earth-pulse', 'crystal-bastion', 'titan-stomp'], isTransformed: true
    },
    radiant_moth: {
        id: 'radiant_moth', name: 'Radiant Moth', lore: 'Glowmoth luminous ascension. Wings cast dawn-light across valleys.',
        affinities: ['Lux'], baseStats: { hp: 50, atk: 42, def: 26, spd: 52 },
        abilities: ['light-burst', 'glow-dust', 'radiant-wing', 'dawn-cascade'], isTransformed: true
    },
    umbral_stalker: {
        id: 'umbral_stalker', name: 'Umbral Stalker', lore: 'Shadowfin dark evolution. A predator between heartbeats.',
        affinities: ['Umbra'], baseStats: { hp: 56, atk: 52, def: 30, spd: 48 },
        abilities: ['dark-claw', 'shadow-step', 'void-gaze', 'nightmare-rend'], isTransformed: true
    },
    entropy_wraith: {
        id: 'entropy_wraith', name: 'Entropy Wraith', lore: 'Voidling terrifying form. A swirling vortex of unraveling reality.',
        affinities: ['Void'], baseStats: { hp: 54, atk: 50, def: 24, spd: 54 },
        abilities: ['null-pulse', 'phase-shift', 'entropy-blast', 'reality-tear'], isTransformed: true
    },
    prism_guardian: {
        id: 'prism_guardian', name: 'Prism Guardian', lore: 'Prismite awakened. A towering crystal sentinel bending all light.',
        affinities: ['Prism'], baseStats: { hp: 74, atk: 32, def: 58, spd: 28 },
        abilities: ['refract', 'prismatic-wall', 'light-prison', 'prism-judgment'], isTransformed: true
    },
    inferno_fox: {
        id: 'inferno_fox', name: 'Inferno Fox', lore: 'Cinderfox blazing form. Nine tails of white-hot flame.',
        affinities: ['Pyra'], baseStats: { hp: 58, atk: 54, def: 28, spd: 46 },
        abilities: ['ember-tail', 'flame-bite', 'fire-veil', 'nine-tail-fury'], isTransformed: true
    },
    deep_drake: {
        id: 'deep_drake', name: 'Deep Drake', lore: 'Aquadrake oceanic evolution. A serpentine leviathan with abyssal pearl scales.',
        affinities: ['Aqua'], baseStats: { hp: 70, atk: 48, def: 42, spd: 30 },
        abilities: ['tidal-swirl', 'frost-bite', 'water-bind', 'deep-current'], isTransformed: true
    }
};
function getCreatureTemplate(id) {
    return exports.CREATURES[id];
}
};

// ── module: src/data/abilities.ts ──
__mods["src/data/abilities.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ABILITIES = void 0;
exports.getAbility = getAbility;
exports.ABILITIES = {
    // === PYRA ABILITIES (8) ===
    'spark-bolt': { id: 'spark-bolt', name: 'Spark Bolt', power: 40, accuracy: 100, affinity: 'Pyra', pp: 25, maxPp: 25, target: 'single', effect: 'none' },
    'ember-tail': { id: 'ember-tail', name: 'Ember Tail', power: 35, accuracy: 95, affinity: 'Pyra', pp: 20, maxPp: 20, target: 'single', effect: 'burn', effectChance: 30 },
    'flame-wave': { id: 'flame-wave', name: 'Flame Wave', power: 65, accuracy: 90, affinity: 'Pyra', pp: 15, maxPp: 15, target: 'single', effect: 'burn', effectChance: 20 },
    'inferno-crest': { id: 'Inferno Crest', name: 'Inferno Crest', power: 90, accuracy: 80, affinity: 'Pyra', pp: 10, maxPp: 10, target: 'single', effect: 'burn', effectChance: 40 },
    'flame-bite': { id: 'flame-bite', name: 'Flame Bite', power: 50, accuracy: 95, affinity: 'Pyra', pp: 20, maxPp: 20, target: 'single', effect: 'burn', effectChance: 25 },
    'fire-veil': { id: 'fire-veil', name: 'Fire Veil', power: 0, accuracy: 100, affinity: 'Pyra', pp: 30, maxPp: 30, target: 'self', effect: 'atk-up' },
    'magma-fall': { id: 'magma-fall', name: 'Magma Fall', power: 75, accuracy: 85, affinity: 'Pyra', pp: 12, maxPp: 12, target: 'single', effect: 'burn', effectChance: 35 },
    'pyre-roar': { id: 'pyre-roar', name: 'Pyre Roar', power: 0, accuracy: 100, affinity: 'Pyra', pp: 20, maxPp: 20, target: 'self', effect: 'speed-boost' },
    'skyfire-descent': { id: 'skyfire-descent', name: 'Skyfire Descent', power: 100, accuracy: 75, affinity: 'Pyra', pp: 8, maxPp: 8, target: 'single', effect: 'burn', effectChance: 50 },
    'nine-tail-fury': { id: 'nine-tail-fury', name: 'Nine-Tail Fury', power: 45, accuracy: 90, affinity: 'Pyra', pp: 12, maxPp: 12, target: 'single', effect: 'multi-hit' },
    // === AQUA ABILITIES (8) ===
    'droplet-jab': { id: 'droplet-jab', name: 'Droplet Jab', power: 35, accuracy: 100, affinity: 'Aqua', pp: 25, maxPp: 25, target: 'single', effect: 'none' },
    'tidal-swirl': { id: 'tidal-swirl', name: 'Tidal Swirl', power: 60, accuracy: 90, affinity: 'Aqua', pp: 18, maxPp: 18, target: 'single', effect: 'frost', effectChance: 25 },
    'frost-bite': { id: 'frost-bite', name: 'Frost Bite', power: 45, accuracy: 95, affinity: 'Aqua', pp: 20, maxPp: 20, target: 'single', effect: 'frost', effectChance: 35 },
    'deluge-curse': { id: 'deluge-curse', name: 'Deluge Curse', power: 80, accuracy: 85, affinity: 'Aqua', pp: 12, maxPp: 12, target: 'single', effect: 'shock', effectChance: 20 },
    'water-bind': { id: 'water-bind', name: 'Water Bind', power: 40, accuracy: 90, affinity: 'Aqua', pp: 20, maxPp: 20, target: 'single', effect: 'frost', effectChance: 30 },
    'abyssal-pressure': { id: 'abyssal-pressure', name: 'Abyssal Pressure', power: 70, accuracy: 85, affinity: 'Aqua', pp: 14, maxPp: 14, target: 'single', effect: 'def-down' },
    'ocean-fury': { id: 'ocean-fury', name: 'Ocean Fury', power: 95, accuracy: 80, affinity: 'Aqua', pp: 10, maxPp: 10, target: 'single', effect: 'frost', effectChance: 40 },
    'deep-current': { id: 'deep-current', name: 'Deep Current', power: 55, accuracy: 95, affinity: 'Aqua', pp: 16, maxPp: 16, target: 'single', effect: 'drain' },
    // === TERRA ABILITIES (8) ===
    'root-tangle': { id: 'root-tangle', name: 'Root Tangle', power: 35, accuracy: 90, affinity: 'Terra', pp: 22, maxPp: 22, target: 'single', effect: 'frost', effectChance: 20 },
    'stone-throw': { id: 'stone-throw', name: 'Stone Throw', power: 50, accuracy: 95, affinity: 'Terra', pp: 20, maxPp: 20, target: 'single', effect: 'none' },
    'earth-pulse': { id: 'earth-pulse', name: 'Earth Pulse', power: 65, accuracy: 85, affinity: 'Terra', pp: 15, maxPp: 15, target: 'all-enemies', effect: 'none' },
    'crystal-bastion': { id: 'Crystal Bastion', name: 'Crystal Bastion', power: 0, accuracy: 100, affinity: 'Terra', pp: 25, maxPp: 25, target: 'self', effect: 'barrier' },
    'horn-charge': { id: 'horn-charge', name: 'Horn Charge', power: 70, accuracy: 90, affinity: 'Terra', pp: 14, maxPp: 14, target: 'single', effect: 'crit-boost' },
    'stone-quake': { id: 'stone-quake', name: 'Stone Quake', power: 85, accuracy: 80, affinity: 'Terra', pp: 12, maxPp: 12, target: 'all-enemies', effect: 'none' },
    'titan-stomp': { id: 'titan-stomp', name: 'Titan Stomp', power: 95, accuracy: 80, affinity: 'Terra', pp: 10, maxPp: 10, target: 'all-enemies', effect: 'def-down' },
    // === LUX ABILITIES (7) ===
    'light-burst': { id: 'light-burst', name: 'Light Burst', power: 45, accuracy: 95, affinity: 'Lux', pp: 20, maxPp: 20, target: 'single', effect: 'none' },
    'glow-dust': { id: 'glow-dust', name: 'Glow Dust', power: 30, accuracy: 100, affinity: 'Lux', pp: 25, maxPp: 25, target: 'self', effect: 'regen' },
    'radiant-wing': { id: 'radiant-wing', name: 'Radiant Wing', power: 60, accuracy: 90, affinity: 'Lux', pp: 18, maxPp: 18, target: 'single', effect: 'speed-boost' },
    'sun-ray': { id: 'sun-ray', name: 'Sun Ray', power: 75, accuracy: 85, affinity: 'Lux', pp: 14, maxPp: 14, target: 'single', effect: 'burn', effectChance: 20 },
    'heal-glow': { id: 'heal-glow', name: 'Heal Glow', power: 0, accuracy: 100, affinity: 'Lux', pp: 30, maxPp: 30, target: 'self', effect: 'heal' },
    'dawn-cascade': { id: 'dawn-cascade', name: 'Dawn Cascade', power: 85, accuracy: 80, affinity: 'Lux', pp: 12, maxPp: 12, target: 'all-enemies', effect: 'none' },
    // === UMBRA ABILITIES (7) ===
    'dark-claw': { id: 'dark-claw', name: 'Dark Claw', power: 50, accuracy: 95, affinity: 'Umbra', pp: 20, maxPp: 20, target: 'single', effect: 'none' },
    'shadow-step': { id: 'shadow-step', name: 'Shadow Step', power: 40, accuracy: 100, affinity: 'Umbra', pp: 25, maxPp: 25, target: 'self', effect: 'speed-boost' },
    'void-gaze': { id: 'void-gaze', name: 'Void Gaze', power: 55, accuracy: 85, affinity: 'Umbra', pp: 16, maxPp: 16, target: 'single', effect: 'shock', effectChance: 30 },
    'night-howl': { id: 'night-howl', name: 'Night Howl', power: 0, accuracy: 100, affinity: 'Umbra', pp: 25, maxPp: 25, target: 'self', effect: 'atk-up' },
    'nightmare-rend': { id: 'nightmare-rend', name: 'Nightmare Rend', power: 90, accuracy: 80, affinity: 'Umbra', pp: 10, maxPp: 10, target: 'single', effect: 'shock', effectChance: 40 },
    // === VOID ABILITIES (7) ===
    'null-pulse': { id: 'null-pulse', name: 'Null Pulse', power: 45, accuracy: 90, affinity: 'Void', pp: 20, maxPp: 20, target: 'single', effect: 'none' },
    'phase-shift': { id: 'phase-shift', name: 'Phase Shift', power: 30, accuracy: 100, affinity: 'Void', pp: 25, maxPp: 25, target: 'self', effect: 'barrier' },
    'entropy-blast': { id: 'entropy-blast', name: 'Entropy Blast', power: 75, accuracy: 85, affinity: 'Void', pp: 14, maxPp: 14, target: 'single', effect: 'def-down' },
    'rift-bite': { id: 'rift-bite', name: 'Rift Bite', power: 50, accuracy: 90, affinity: 'Void', pp: 20, maxPp: 20, target: 'single', effect: 'drain' },
    'void-collapse': { id: 'void-collapse', name: 'Void Collapse', power: 100, accuracy: 75, affinity: 'Void', pp: 8, maxPp: 8, target: 'all-enemies', effect: 'none' },
    'reality-tear': { id: 'reality-tear', name: 'Reality Tear', power: 80, accuracy: 80, affinity: 'Void', pp: 12, maxPp: 12, target: 'single', effect: 'crit-boost' },
    // === PRISM ABILITIES (5) ===
    'refract': { id: 'refract', name: 'Refract', power: 40, accuracy: 95, affinity: 'Prism', pp: 22, maxPp: 22, target: 'single', effect: 'none' },
    'prismatic-wall': { id: 'prismatic-wall', name: 'Prismatic Wall', power: 0, accuracy: 100, affinity: 'Prism', pp: 30, maxPp: 30, target: 'self', effect: 'barrier' },
    'light-prison': { id: 'light-prison', name: 'Light Prison', power: 55, accuracy: 85, affinity: 'Prism', pp: 16, maxPp: 16, target: 'single', effect: 'frost', effectChance: 30 },
    'crystal-spikes': { id: 'crystal-spikes', name: 'Crystal Spikes', power: 50, accuracy: 90, affinity: 'Prism', pp: 18, maxPp: 18, target: 'single', effect: 'none' },
    'prism-judgment': { id: 'prism-judgment', name: 'Prism Judgment', power: 95, accuracy: 80, affinity: 'Prism', pp: 10, maxPp: 10, target: 'all-enemies', effect: 'crit-boost' },
};
function getAbility(id) {
    return exports.ABILITIES[id];
}
};

// ── module: src/data/zones.ts ──
__mods["src/data/zones.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.STORY_STAGES = exports.ITEMS = exports.ZONES = void 0;
exports.ZONES = {
    ember_pass: {
        id: 'ember_pass', name: 'Ember Pass',
        gradientStart: '#1a0505', gradientEnd: '#3d1200',
        particleColor: '#ff6b35', encounterRate: 0.18,
        storyText: 'The Ember Pass glows with ancient heat. Crystalline formations pulse like heartbeats along the canyon walls.',
        guardianId: 'pyre_golem', guardianLevel: 12,
        wildCreatures: [
            { templateId: 'emberling', minLvl: 3, maxLvl: 7 },
            { templateId: 'cinderfox', minLvl: 4, maxLvl: 8 }
        ]
    },
    deep_trench: {
        id: 'deep_trench', name: 'Deep Trench',
        gradientStart: '#050a1a', gradientEnd: '#0a2040',
        particleColor: '#3b82f6', encounterRate: 0.2,
        storyText: 'Below the surface, bioluminescent currents flow through obsidian canyons. The water here remembers everything.',
        guardianId: 'abyssal_tide', guardianLevel: 18,
        unlockedByZone: 'ember_pass',
        wildCreatures: [
            { templateId: 'tidepup', minLvl: 6, maxLvl: 12 },
            { templateId: 'aquadrake', minLvl: 7, maxLvl: 13 },
            { templateId: 'shadowfin', minLvl: 8, maxLvl: 14 }
        ]
    },
    stoneheart_cavern: {
        id: 'stoneheart_cavern', name: 'Stoneheart Cavern',
        gradientStart: '#0a1505', gradientEnd: '#1a3010',
        particleColor: '#84cc16', encounterRate: 0.22,
        storyText: 'The cavern walls breathe with slow crystalline growths. Ancient roots weave through veins of raw aether.',
        guardianId: 'earthwarden', guardianLevel: 25,
        unlockedByZone: 'deep_trench',
        wildCreatures: [
            { templateId: 'mossback', minLvl: 10, maxLvl: 16 },
            { templateId: 'stonehorn', minLvl: 11, maxLvl: 17 },
            { templateId: 'glowmoth', minLvl: 9, maxLvl: 15 }
        ]
    },
    prism_spire: {
        id: 'prism_spire', name: 'Prism Spire',
        gradientStart: '#0f0a20', gradientEnd: '#201040',
        particleColor: '#ffffff', encounterRate: 0.25,
        storyText: 'The Spire pierces the sky with refracted light. Here, all affinities converge into pure crystalline energy.',
        guardianId: 'void_anchor', guardianLevel: 32,
        unlockedByZone: 'stoneheart_cavern',
        wildCreatures: [
            { templateId: 'prismite', minLvl: 14, maxLvl: 20 },
            { templateId: 'crystal_shard', minLvl: 15, maxLvl: 21 },
            { templateId: 'dawnwisp', minLvl: 13, maxLvl: 19 },
            { templateId: 'voidling', minLvl: 16, maxLvl: 22 }
        ]
    },
    shadow_veil: {
        id: 'shadow_veil', name: 'Shadow Veil',
        gradientStart: '#0a0515', gradientEnd: '#150a30',
        particleColor: '#7c3aed', encounterRate: 0.28,
        storyText: 'A realm between realms. Shadows move with purpose here, and the air tastes of forgotten memories.',
        guardianId: 'void_anchor', guardianLevel: 40,
        unlockedByZone: 'prism_spire',
        wildCreatures: [
            { templateId: 'nightshade_cat', minLvl: 18, maxLvl: 26 },
            { templateId: 'shadowfin', minLvl: 19, maxLvl: 27 },
            { templateId: 'riftworm', minLvl: 20, maxLvl: 28 },
            { templateId: 'voidling', minLvl: 21, maxLvl: 29 }
        ]
    },
    the_between: {
        id: 'the_between', name: 'The Between',
        gradientStart: '#050508', gradientEnd: '#100a18',
        particleColor: '#e040fb', encounterRate: 0.3,
        storyText: 'Beyond the anchors lies the raw fabric of Aetheris. Reality thins here, and ancient energies swirl unchecked.',
        unlockedByZone: 'shadow_veil',
        wildCreatures: [
            { templateId: 'voidling', minLvl: 25, maxLvl: 35 },
            { templateId: 'riftworm', minLvl: 26, maxLvl: 36 },
            { templateId: 'prismite', minLvl: 24, maxLvl: 34 },
            { templateId: 'nightshade_cat', minLvl: 27, maxLvl: 37 }
        ]
    }
};
exports.ITEMS = {
    // Capture items (3 tiers)
    'aether-net': { id: 'aether-net', name: 'Aether Net', type: 'capture', description: 'Basic capture device. Low success rate.', capturePower: 1 },
    'resonance-cage': { id: 'resonance-cage', name: 'Resonance Cage', type: 'capture', description: 'Mid-tier capture tool with harmonic dampeners.', capturePower: 2 },
    'prism-binder': { id: 'prism-binder', name: 'Prism Binder', type: 'capture', description: 'Premium capture device. High success rate.', capturePower: 3 },
    // Consumables
    'aether-draught': { id: 'aether-draught', name: 'Aether Draught', type: 'consumable', description: 'Restores 50 HP to a creature.', healAmount: 50 },
    'prism-elixir': { id: 'prism-elixir', name: 'Prism Elixir', type: 'consumable', description: 'Fully restores a creatures HP.', healAmount: 999 },
    // Catalysts for transformation
    'pyre-shard': { id: 'pyre-shard', name: 'Pyre Shard', type: 'catalyst', description: 'A fragment of eternal flame. Used to transform Pyra creatures.' },
    'aqua-core': { id: 'aqua-core', name: 'Aqua Core', type: 'catalyst', description: 'Condensed ocean energy. Transforms Aqua creatures.' },
    'terra-gem': { id: 'terra-gem', name: 'Terra Gem', type: 'catalyst', description: 'Living crystal from the deep earth. Transforms Terra creatures.' },
    'lux-orb': { id: 'lux-orb', name: 'Lux Orb', type: 'catalyst', description: 'Pure dawn-light condensed into form. Transforms Lux creatures.' },
    'umbra-essence': { id: 'umbra-essence', name: 'Umbra Essence', type: 'catalyst', description: 'Distilled shadow-stuff. Transforms Umbra creatures.' },
    'void-fragment': { id: 'void-fragment', name: 'Void Fragment', type: 'catalyst', description: 'A piece of the Between itself. Transforms Void creatures.' },
    'prism-heart': { id: 'prism-heart', name: 'Prism Heart', type: 'catalyst', description: 'The core of a Prism Guardian. Transforms Prism creatures.' },
    // Field tools
    'echo-compass': { id: 'echo-compass', name: 'Echo Compass', type: 'tool', description: 'Reveals hidden paths on the overworld map.' }
};
exports.STORY_STAGES = [
    { text: 'The Aether flows thin. The Anchors stir in their slumber, and the world trembles.', color: '#ff6b35' },
    { text: 'You have broken the first seal. Deeper currents call from below.', color: '#3b82f6' },
    { text: 'The earth remembers what was lost. Ancient roots awaken in response to your journey.', color: '#84cc16' },
    { text: 'All paths converge at the Spire. The final Anchor awaits beyond the veil of shadow.', color: '#e040fb' }
];
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCreature = createCreature;
exports.createInitialState = createInitialState;
exports.addInventoryItem = addInventoryItem;
exports.removeInventoryItem = removeInventoryItem;
exports.hasItem = hasItem;
exports.saveGame = saveGame;
exports.loadGame = loadGame;
exports.hasSave = hasSave;
exports.gainExp = gainExp;
exports.canTransform = canTransform;
exports.transformCreature = transformCreature;
const creatures_1 = require("./data/creatures");
function createCreature(templateId, level) {
    const tmpl = (0, creatures_1.getCreatureTemplate)(templateId);
    if (!tmpl)
        throw new Error(`Unknown creature template: ${templateId}`);
    // Stat growth formula: base * (1 + level * 0.5) with some variance
    const hp = Math.floor(tmpl.baseStats.hp * (1 + level * 0.4));
    const atk = Math.floor(tmpl.baseStats.atk * (1 + level * 0.35));
    const def = Math.floor(tmpl.baseStats.def * (1 + level * 0.35));
    const spd = Math.floor(tmpl.baseStats.spd * (1 + level * 0.3));
    // EXP curve: level^2 * 8
    const expToNext = Math.floor(Math.pow(level + 1, 2) * 8);
    return {
        templateId,
        level,
        currentHp: hp,
        maxHp: hp,
        atk, def, spd,
        affinities: [...tmpl.affinities],
        abilities: tmpl.abilities.map(aid => ({ abilityId: aid, pp: 25 })), // default PP
        exp: 0,
        expToNext
    };
}
function createInitialState() {
    return {
        screen: 'title',
        playerX: 4, playerY: 4,
        currentZone: 'ember_pass',
        party: [],
        inventory: {
            'aether-net': 5,
            'resonance-cage': 2,
            'aether-draught': 3
        },
        unlockedZones: ['ember_pass'],
        defeatedGuardians: [],
        collectedCreatures: new Set(),
        storyStage: 0,
        postGame: false,
        shrineVisited: null
    };
}
function addInventoryItem(state, itemId, count = 1) {
    state.inventory[itemId] = (state.inventory[itemId] || 0) + count;
}
function removeInventoryItem(state, itemId, count = 1) {
    if ((state.inventory[itemId] || 0) >= count) {
        state.inventory[itemId] -= count;
        if (state.inventory[itemId] <= 0)
            delete state.inventory[itemId];
        return true;
    }
    return false;
}
function hasItem(state, itemId) {
    return (state.inventory[itemId] || 0) > 0;
}
// Save/Load
const SAVE_KEY = 'aetheris_save';
function saveGame(state) {
    const data = JSON.stringify({
        ...state,
        collectedCreatures: Array.from(state.collectedCreatures)
    });
    localStorage.setItem(SAVE_KEY, data);
}
function loadGame() {
    try {
        const raw = localStorage.getItem(SAVE_KEY);
        if (!raw)
            return null;
        const parsed = JSON.parse(raw);
        parsed.collectedCreatures = new Set(parsed.collectedCreatures);
        parsed.screen = 'overworld';
        return parsed;
    }
    catch {
        return null;
    }
}
function hasSave() {
    return !!localStorage.getItem(SAVE_KEY);
}
// EXP and leveling
function gainExp(creature, amount) {
    const messages = [];
    creature.exp += amount;
    while (creature.exp >= creature.expToNext && creature.level < 50) {
        creature.exp -= creature.expToNext;
        creature.level++;
        // Stat growth on level up
        const tmpl = (0, creatures_1.getCreatureTemplate)(creature.templateId);
        creature.maxHp = Math.floor(tmpl.baseStats.hp * (1 + creature.level * 0.4));
        creature.atk = Math.floor(tmpl.baseStats.atk * (1 + creature.level * 0.35));
        creature.def = Math.floor(tmpl.baseStats.def * (1 + creature.level * 0.35));
        creature.spd = Math.floor(tmpl.baseStats.spd * (1 + creature.level * 0.3));
        creature.currentHp = creature.maxHp; // Full heal on level up
        creature.expToNext = Math.floor(Math.pow(creature.level + 1, 2) * 8);
        messages.push(`${tmpl.name} reached Level ${creature.level}!`);
        // Check for new abilities at certain levels
        const newAbilities = tmpl.abilities.filter(aid => {
            return !creature.abilities.some(a => a.abilityId === aid);
        });
        if (newAbilities.length > 0) {
            creature.abilities.push(...newAbilities.map(aid => ({ abilityId: aid, pp: 25 })));
            messages.push(`Learned new ability!`);
        }
    }
    return messages;
}
// Check if a creature can transform
function canTransform(creature) {
    const tmpl = (0, creatures_1.getCreatureTemplate)(creature.templateId);
    if (!tmpl.transformId || !tmpl.transformLevel)
        return false;
    return creature.level >= tmpl.transformLevel && !(0, creatures_1.getCreatureTemplate)(tmpl.transformId).isTransformed === false;
}
function transformCreature(creature, catalystId) {
    const tmpl = (0, creatures_1.getCreatureTemplate)(creature.templateId);
    if (!tmpl.transformId || !tmpl.transformLevel)
        return false;
    if (creature.level < tmpl.transformLevel)
        return false;
    if (tmpl.catalystItem !== catalystId)
        return false;
    // Transform: replace template, keep level and exp
    const newTmpl = (0, creatures_1.getCreatureTemplate)(tmpl.transformId);
    creature.templateId = newTmpl.id;
    creature.affinities = [...newTmpl.affinities];
    // Recalculate stats with new base
    creature.maxHp = Math.floor(newTmpl.baseStats.hp * (1 + creature.level * 0.4));
    creature.atk = Math.floor(newTmpl.baseStats.atk * (1 + creature.level * 0.35));
    creature.def = Math.floor(newTmpl.baseStats.def * (1 + creature.level * 0.35));
    creature.spd = Math.floor(newTmpl.baseStats.spd * (1 + creature.level * 0.3));
    creature.currentHp = creature.maxHp;
    // Update abilities to new template's set
    creature.abilities = newTmpl.abilities.map(aid => ({ abilityId: aid, pp: 25 }));
    return true;
}
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initAudio = initAudio;
exports.toggleMute = toggleMute;
exports.isMuted = isMuted;
exports.playStep = playStep;
exports.playBattleStart = playBattleStart;
exports.playHit = playHit;
exports.playCrit = playCrit;
exports.playCapture = playCapture;
exports.playLevelUp = playLevelUp;
exports.playVictory = playVictory;
exports.playDefeat = playDefeat;
exports.playMenuSelect = playMenuSelect;
exports.playTransform = playTransform;
exports.playGuardianDefeated = playGuardianDefeated;
exports.startAmbient = startAmbient;
exports.stopAmbient = stopAmbient;
// Simple chiptune/ambient audio using Web Audio API
let ctx = null;
let masterGain = null;
let muted = false;
function initAudio() {
    if (ctx)
        return;
    ctx = new AudioContext();
    masterGain = ctx.createGain();
    masterGain.gain.value = 0.3;
    masterGain.connect(ctx.destination);
}
function toggleMute() {
    muted = !muted;
    if (masterGain)
        masterGain.gain.value = muted ? 0 : 0.3;
    return muted;
}
function isMuted() { return muted; }
function playTone(freq, duration, type = 'square', vol = 0.15) {
    if (!ctx || !masterGain)
        return;
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.type = type;
    osc.frequency.value = freq;
    gain.gain.setValueAtTime(vol, ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
    osc.connect(gain);
    gain.connect(masterGain);
    osc.start();
    osc.stop(ctx.currentTime + duration);
}
function playStep() {
    playTone(200 + Math.random() * 100, 0.05, 'sine', 0.05);
}
function playBattleStart() {
    const notes = [262, 330, 392, 523];
    notes.forEach((n, i) => setTimeout(() => playTone(n, 0.15, 'square', 0.1), i * 80));
}
function playHit() {
    playTone(150, 0.1, 'sawtooth', 0.12);
}
function playCrit() {
    playTone(400, 0.05, 'square', 0.15);
    setTimeout(() => playTone(600, 0.1, 'square', 0.15), 50);
}
function playCapture() {
    const notes = [392, 440, 523, 659];
    notes.forEach((n, i) => setTimeout(() => playTone(n, 0.2, 'sine', 0.1), i * 100));
}
function playLevelUp() {
    const notes = [262, 330, 392, 523, 659];
    notes.forEach((n, i) => setTimeout(() => playTone(n, 0.15, 'square', 0.1), i * 80));
}
function playVictory() {
    const melody = [523, 587, 659, 784, 659, 784, 1047];
    melody.forEach((n, i) => setTimeout(() => playTone(n, 0.2, 'square', 0.1), i * 120));
}
function playDefeat() {
    const notes = [392, 349, 330, 262];
    notes.forEach((n, i) => setTimeout(() => playTone(n, 0.3, 'sine', 0.1), i * 200));
}
function playMenuSelect() {
    playTone(440, 0.05, 'sine', 0.08);
}
function playTransform() {
    for (let i = 0; i < 8; i++) {
        setTimeout(() => playTone(200 + i * 100, 0.15, 'sine', 0.08), i * 60);
    }
}
function playGuardianDefeated() {
    const notes = [262, 330, 392, 523, 659, 784, 1047];
    notes.forEach((n, i) => setTimeout(() => playTone(n, 0.25, 'square', 0.1), i * 100));
}
// Ambient pad for overworld
let ambientOsc = null;
let ambientGain = null;
function startAmbient() {
    if (!ctx || !masterGain)
        return;
    stopAmbient();
    ambientOsc = ctx.createOscillator();
    ambientGain = ctx.createGain();
    ambientOsc.type = 'sine';
    ambientOsc.frequency.value = 80;
    ambientGain.gain.value = 0.02;
    ambientOsc.connect(ambientGain);
    ambientGain.connect(masterGain);
    ambientOsc.start();
    // Second harmonic
    const osc2 = ctx.createOscillator();
    const gain2 = ctx.createGain();
    osc2.type = 'sine';
    osc2.frequency.value = 120;
    gain2.gain.value = 0.015;
    osc2.connect(gain2);
    gain2.connect(masterGain);
    osc2.start();
}
function stopAmbient() {
    try {
        ambientOsc?.stop();
    }
    catch { }
    ambientOsc = null;
}
};

// ── module: src/battle.ts ──
__mods["src/battle.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startBattle = startBattle;
exports.getBattleState = getBattleState;
exports.getParticles = getParticles;
exports.getFloatTexts = getFloatTexts;
exports.isAnimating = isAnimating;
exports.getShakeAmount = getShakeAmount;
exports.executeMove = executeMove;
exports.executeEnemyTurn = executeEnemyTurn;
exports.processStatusEffects = processStatusEffects;
exports.checkBattleEnd = checkBattleEnd;
exports.startCapture = startCapture;
exports.captureTap = captureTap;
exports.resolveCapture = resolveCapture;
exports.resetCapture = resetCapture;
exports.advanceTurn = advanceTurn;
exports.isPlayerTurn = isPlayerTurn;
exports.isEnemyTurn = isEnemyTurn;
exports.getCurrentActor = getCurrentActor;
const abilities_1 = require("./data/abilities");
const creatures_1 = require("./data/creatures");
const types_1 = require("./types");
const zones_1 = require("./data/zones");
let battleState = null;
let particles = [];
let floatTexts = [];
let animating = false;
let captureMode = false;
let captureTimer = 0;
let shakeAmount = 0;
function startBattle(playerParty, enemyTemplates, isWild, zoneId) {
    const enemies = enemyTemplates.map(e => {
        const c = createEnemyCreature(e.templateId, e.level);
        return c;
    });
    // Filter out fainted creatures from party
    const activeParty = playerParty.filter(c => c.currentHp > 0);
    if (activeParty.length === 0) {
        // All fainted - game over scenario
        activeParty.push(playerParty[0]); // revive first one with 1 HP
        activeParty[0].currentHp = 1;
    }
    battleState = {
        playerParty: activeParty,
        enemies,
        activePlayerIndex: 0,
        turnOrder: [],
        currentTurnIndex: 0,
        phase: 'choose',
        dialogText: `A wild ${(0, creatures_1.getCreatureTemplate)(enemies[0].templateId).name} appears!`,
        isWild,
        zoneId
    };
    // Calculate initial turn order based on SPD
    recalcTurnOrder();
    particles = [];
    floatTexts = [];
    animating = false;
    captureMode = false;
    return battleState;
}
function createEnemyCreature(templateId, level) {
    const tmpl = (0, creatures_1.getCreatureTemplate)(templateId);
    if (!tmpl)
        throw new Error(`Unknown creature: ${templateId}`);
    const hp = Math.floor(tmpl.baseStats.hp * (1 + level * 0.4));
    return {
        templateId,
        level,
        currentHp: hp, maxHp: hp,
        atk: Math.floor(tmpl.baseStats.atk * (1 + level * 0.35)),
        def: Math.floor(tmpl.baseStats.def * (1 + level * 0.35)),
        spd: Math.floor(tmpl.baseStats.spd * (1 + level * 0.3)),
        affinities: [...tmpl.affinities],
        abilities: tmpl.abilities.slice(0, 4).map(aid => ({ abilityId: aid, pp: 25 })),
        exp: 0,
        expToNext: Math.floor(Math.pow(level + 1, 2) * 8)
    };
}
function recalcTurnOrder() {
    if (!battleState)
        return;
    const bs = battleState;
    const all = [...bs.playerParty.map((_, i) => ({ idx: i, spd: bs.playerParty[i].spd, isPlayer: true })),
        ...bs.enemies.map((_, i) => ({ idx: bs.playerParty.length + i, spd: bs.enemies[i].spd, isPlayer: false }))];
    // Filter out fainted
    const alive = all.filter(e => {
        if (e.isPlayer)
            return bs.playerParty[e.idx].currentHp > 0;
        return bs.enemies[e.idx - bs.playerParty.length].currentHp > 0;
    });
    // Sort by SPD descending, then priority
    alive.sort((a, b) => b.spd - a.spd);
    battleState.turnOrder = alive.map(e => e.idx);
    battleState.currentTurnIndex = 0;
}
function getBattleState() { return battleState; }
function getParticles() { return particles; }
function getFloatTexts() { return floatTexts; }
function isAnimating() { return animating; }
function getShakeAmount() { return shakeAmount; }
function addParticle(x, y, color, count = 5) {
    for (let i = 0; i < count; i++) {
        particles.push({
            x, y,
            vx: (Math.random() - 0.5) * 4,
            vy: (Math.random() - 0.5) * 4 - 2,
            life: 30 + Math.random() * 20,
            maxLife: 50,
            color,
            size: 2 + Math.random() * 3
        });
    }
}
function addFloatText(x, y, text, color) {
    floatTexts.push({ x, y, text, color, life: 60, size: undefined });
}
// Damage formula: (0.4 * Level * ATK / DEF * Multiplier) + random
function calcDamage(attacker, defender, ability) {
    if (ability.power === 0)
        return 0;
    const level = attacker.level;
    const atk = attacker.atk;
    const def = Math.max(defender.def, 1);
    // Type effectiveness
    let multiplier = 1.0;
    for (const aff of defender.affinities) {
        multiplier *= (0, types_1.getEffectiveness)(ability.affinity, aff);
    }
    // STAB bonus
    if (attacker.affinities.includes(ability.affinity)) {
        multiplier *= 1.25;
    }
    const base = Math.floor((0.4 * level * atk / def) * multiplier);
    const randomFactor = 0.85 + Math.random() * 0.15; // +/- 7.5%
    let damage = Math.max(1, Math.floor(base * randomFactor));
    // Critical hit (1/16 chance, or boosted by ability)
    const critChance = ability.effect === 'crit-boost' ? 0.2 : 0.0625;
    if (Math.random() < critChance) {
        damage = Math.floor(damage * 1.5);
    }
    return damage;
}
function applyStatusEffect(target, effect) {
    target.status = { effect, turns: 3 };
}
function executeMove(playerIdx, abilityId) {
    if (!battleState || animating)
        return [];
    const messages = [];
    animating = true;
    const attacker = battleState.playerParty[playerIdx];
    const abilityDef = (0, abilities_1.getAbility)(abilityId);
    if (!abilityDef) {
        animating = false;
        return ['Unknown ability'];
    }
    // Check PP
    const abEntry = attacker.abilities.find(a => a.abilityId === abilityId);
    if (!abEntry || abEntry.pp <= 0) {
        messages.push('No energy left for that move!');
        animating = false;
        return messages;
    }
    abEntry.pp--;
    const attackerName = (0, creatures_1.getCreatureTemplate)(attacker.templateId).name;
    // Self-target moves
    if (abilityDef.target === 'self') {
        switch (abilityDef.effect) {
            case 'heal': {
                const healAmt = Math.floor(attacker.maxHp * 0.4);
                attacker.currentHp = Math.min(attacker.maxHp, attacker.currentHp + healAmt);
                messages.push(`${attackerName} used ${abilityDef.name}! Restored HP.`);
                addParticle(200, 300, '#4ade80', 10);
                break;
            }
            case 'atk-up': {
                attacker.atk = Math.floor(attacker.atk * 1.3);
                messages.push(`${attackerName} used ${abilityDef.name}! ATK increased!`);
                addParticle(200, 300, '#ff6b35', 8);
                break;
            }
            case 'speed-boost': {
                attacker.spd = Math.floor(attacker.spd * 1.4);
                messages.push(`${attackerName} used ${abilityDef.name}! SPD increased!`);
                addParticle(200, 300, '#facc15', 8);
                break;
            }
            case 'barrier': {
                applyStatusEffect(attacker, 'Barrier');
                messages.push(`${attackerName} used ${abilityDef.name}! Barrier active.`);
                addParticle(200, 300, '#ffffff', 8);
                break;
            }
            case 'regen': {
                applyStatusEffect(attacker, 'Regen');
                messages.push(`${attackerName} used ${abilityDef.name}! Regenerating.`);
                addParticle(200, 300, '#4ade80', 8);
                break;
            }
        }
        battleState.dialogText = messages.join(' ');
        animating = false;
        return messages;
    }
    // Determine targets
    const targets = [];
    if (abilityDef.target === 'all-enemies') {
        targets.push(...battleState.enemies.filter(e => e.currentHp > 0));
    }
    else {
        // Single target - pick first alive enemy, or random
        const aliveEnemies = battleState.enemies.filter(e => e.currentHp > 0);
        if (aliveEnemies.length === 0) {
            animating = false;
            return ['No targets!'];
        }
        targets.push(aliveEnemies[Math.floor(Math.random() * aliveEnemies.length)]);
    }
    let totalDamage = 0;
    for (const target of targets) {
        // Check accuracy
        if (Math.random() * 100 > abilityDef.accuracy) {
            messages.push(`${attackerName} used ${abilityDef.name}... but missed!`);
            continue;
        }
        // Check barrier on target
        if (target.status?.effect === 'Barrier') {
            target.status = undefined;
            messages.push(`${(0, creatures_1.getCreatureTemplate)(target.templateId).name}'s Barrier absorbed the attack!`);
            addParticle(600, 250, '#ffffff', 12);
            continue;
        }
        let damage = calcDamage(attacker, target, abilityDef);
        const isCrit = damage > Math.floor((0.4 * attacker.level * attacker.atk / Math.max(target.def, 1)) * 1.3);
        // Drain effect
        if (abilityDef.effect === 'drain') {
            const healAmt = Math.floor(damage * 0.5);
            attacker.currentHp = Math.min(attacker.maxHp, attacker.currentHp + healAmt);
        }
        target.currentHp -= damage;
        totalDamage += damage;
        if (target.currentHp < 0)
            target.currentHp = 0;
        const targetName = (0, creatures_1.getCreatureTemplate)(target.templateId).name;
        messages.push(`${attackerName} used ${abilityDef.name}!`);
        // Type effectiveness message
        let effMsg = '';
        for (const aff of target.affinities) {
            const eff = (0, types_1.getEffectiveness)(abilityDef.affinity, aff);
            if (eff > 1)
                effMsg = 'It is super effective!';
            else if (eff < 1)
                effMsg = 'It is not very effective...';
        }
        if (effMsg)
            messages.push(effMsg);
        // Particles based on type effectiveness
        const isSuperEff = targets.some(t => t.affinities.some(a => (0, types_1.getEffectiveness)(abilityDef.affinity, a) > 1));
        addParticle(600, 250, abilityDef.affinity === 'Pyra' ? '#ff6b35' :
            abilityDef.affinity === 'Aqua' ? '#3b82f6' :
                abilityDef.affinity === 'Terra' ? '#84cc16' :
                    abilityDef.affinity === 'Lux' ? '#facc15' :
                        abilityDef.affinity === 'Umbra' ? '#7c3aed' :
                            abilityDef.affinity === 'Void' ? '#e040fb' : '#ffffff', isSuperEff ? 20 : 8);
        if (isCrit) {
            shakeAmount = 10;
            addFloatText(600, 230, `CRIT ${damage}!`, '#ff4444');
        }
        else {
            shakeAmount = isSuperEff ? 6 : 3;
            addFloatText(600, 250, `${damage}`, isSuperEff ? '#ffff00' : '#ffffff');
        }
        // Apply special effect
        if (abilityDef.effectChance && Math.random() * 100 < abilityDef.effectChance) {
            const eff = abilityDef.effect;
            if (['burn', 'frost', 'shock'].includes(eff)) {
                applyStatusEffect(target, eff);
                messages.push(`${targetName} got ${eff.toLowerCase()}!`);
            }
        }
        // Multi-hit: do extra damage
        if (abilityDef.effect === 'multi-hit') {
            const extraDmg = Math.floor(damage * 0.5);
            target.currentHp -= extraDmg;
            if (target.currentHp < 0)
                target.currentHp = 0;
            messages.push(`Hit again for ${extraDmg}!`);
        }
        // Def-down effect
        if (abilityDef.effect === 'def-down') {
            target.def = Math.floor(target.def * 0.75);
            messages.push(`${targetName}'s DEF dropped!`);
        }
    }
    battleState.dialogText = messages.join(' ');
    animating = false;
    return messages;
}
// Enemy AI turn
function executeEnemyTurn() {
    if (!battleState || animating)
        return [];
    const messages = [];
    animating = true;
    // Find current enemy in turn order
    const allCreatures = [...battleState.playerParty, ...battleState.enemies];
    const aliveEnemies = battleState.enemies.filter(e => e.currentHp > 0);
    if (aliveEnemies.length === 0) {
        animating = false;
        return [];
    }
    // Pick random alive enemy to act
    const attacker = aliveEnemies[Math.floor(Math.random() * aliveEnemies.length)];
    const attackerName = (0, creatures_1.getCreatureTemplate)(attacker.templateId).name;
    // Pick random ability
    const abEntry = attacker.abilities[Math.floor(Math.random() * attacker.abilities.length)];
    if (!abEntry) {
        animating = false;
        return [];
    }
    const abilityDef = (0, abilities_1.getAbility)(abEntry.abilityId);
    if (!abilityDef || abilityDef.power === 0 && abilityDef.target !== 'self') {
        // Try another or skip
        messages.push(`${attackerName} hesitates...`);
        animating = false;
        return messages;
    }
    // Target: pick random alive player creature (prefer active)
    const alivePlayers = battleState.playerParty.filter(c => c.currentHp > 0);
    if (alivePlayers.length === 0) {
        animating = false;
        return [];
    }
    const target = alivePlayers[Math.floor(Math.random() * alivePlayers.length)];
    // Check barrier on enemy
    if (attacker.status?.effect === 'Barrier') {
        attacker.status = undefined;
        messages.push(`${attackerName}'s Barrier absorbed a counter-attack!`);
        animating = false;
        return messages;
    }
    const targetName = (0, creatures_1.getCreatureTemplate)(target.templateId).name;
    let damage = calcDamage(attacker, target, abilityDef);
    // Check barrier on target
    if (target.status?.effect === 'Barrier') {
        target.status = undefined;
        messages.push(`${attackerName} used ${abilityDef.name}, but ${targetName}'s Barrier absorbed it!`);
        addParticle(200, 300, '#ffffff', 12);
        animating = false;
        return messages;
    }
    target.currentHp -= damage;
    if (target.currentHp < 0)
        target.currentHp = 0;
    const isCrit = Math.random() < 0.0625;
    messages.push(`${attackerName} used ${abilityDef.name}!`);
    // Type effectiveness
    let effMsg = '';
    for (const aff of target.affinities) {
        const eff = (0, types_1.getEffectiveness)(abilityDef.affinity, aff);
        if (eff > 1)
            effMsg = 'It is super effective!';
        else if (eff < 1)
            effMsg = 'It is not very effective...';
    }
    if (effMsg)
        messages.push(effMsg);
    addParticle(200, 300, '#ef4444', 8);
    shakeAmount = isCrit ? 8 : 4;
    addFloatText(200, 280, `${damage}`, isCrit ? '#ff4444' : '#ffffff');
    // Apply status effects from enemy moves
    if (abilityDef.effectChance && Math.random() * 100 < abilityDef.effectChance) {
        const eff = abilityDef.effect;
        if (['burn', 'frost', 'shock'].includes(eff)) {
            applyStatusEffect(target, eff);
            messages.push(`${targetName} got ${eff.toLowerCase()}!`);
        }
    }
    battleState.dialogText = messages.join(' ');
    animating = false;
    return messages;
}
// Process status effects at start of turn
function processStatusEffects() {
    if (!battleState)
        return [];
    const messages = [];
    // Process all creatures
    for (const c of [...battleState.playerParty, ...battleState.enemies]) {
        if (!c.status || c.currentHp <= 0)
            continue;
        const name = (0, creatures_1.getCreatureTemplate)(c.templateId).name;
        switch (c.status.effect) {
            case 'Burn': {
                const dmg = Math.floor(c.atk * 0.5);
                c.currentHp -= dmg;
                if (c.currentHp < 0)
                    c.currentHp = 0;
                messages.push(`${name} is hurt by burn! (-${dmg})`);
                break;
            }
            case 'Frost': {
                // SPD halved already applied in turn order calc
                messages.push(`${name} is slowed by frost.`);
                break;
            }
            case 'Shock': {
                if (Math.random() < 0.3) {
                    messages.push(`${name} is paralyzed and can't move!`);
                    c.status.turns--; // skip this turn
                    return messages; // early exit - skipped turn
                }
                break;
            }
            case 'Regen': {
                const heal = Math.floor(c.maxHp * 0.15);
                c.currentHp = Math.min(c.maxHp, c.currentHp + heal);
                messages.push(`${name} regenerates HP! (+${heal})`);
                break;
            }
        }
        c.status.turns--;
        if (c.status.turns <= 0) {
            c.status = undefined;
        }
    }
    return messages;
}
// Check battle end conditions
function checkBattleEnd() {
    if (!battleState)
        return null;
    const enemiesAlive = battleState.enemies.some(e => e.currentHp > 0);
    const playersAlive = battleState.playerParty.some(c => c.currentHp > 0);
    if (!enemiesAlive)
        return 'victory';
    if (!playersAlive)
        return 'defeat';
    return null;
}
// Capture attempt - rhythm mini-game
function startCapture() {
    captureMode = true;
    captureTimer = 120; // frames (~2 seconds at 60fps)
}
let captureScore = 0;
let captureTarget = 0;
function captureTap() {
    if (!captureMode || !battleState)
        return -1;
    captureScore++;
    return captureTimer--;
}
function resolveCapture(itemId) {
    if (!battleState)
        return false;
    const item = zones_1.ITEMS[itemId];
    if (!item)
        return false;
    // Find weakest enemy (most HP remaining relative to max)
    const target = battleState.enemies.reduce((best, e) => {
        if (e.currentHp <= 0)
            return best;
        const ratio = e.currentHp / e.maxHp;
        return (!best || ratio < (best.currentHp / best.maxHp)) ? e : best;
    }, null);
    if (!target)
        return false;
    // Capture formula: base rate * item power * (1 - hpRatio) + rhythm bonus
    const hpRatio = target.currentHp / target.maxHp;
    const baseRate = 0.3;
    const itemPower = item.capturePower || 1;
    const rhythmBonus = Math.min(captureScore * 0.05, 0.3);
    const levelPenalty = Math.max(0, 1 - (target.level - 5) * 0.02);
    const successChance = baseRate * itemPower * (1 - hpRatio * 0.7) + rhythmBonus + levelPenalty * 0.1;
    captureScore = 0;
    captureTarget = 0;
    return Math.random() < Math.min(successChance, 0.95);
}
function resetCapture() {
    captureMode = false;
    captureScore = 0;
    captureTimer = 0;
}
// Advance to next turn
function advanceTurn() {
    if (!battleState)
        return;
    battleState.currentTurnIndex++;
    if (battleState.currentTurnIndex >= battleState.turnOrder.length) {
        // New round - recalculate with status effects
        const statusMsgs = processStatusEffects();
        if (statusMsgs.length > 0 && !animating) {
            battleState.dialogText = statusMsgs.join(' ');
        }
        recalcTurnOrder();
    }
}
function isPlayerTurn() {
    if (!battleState || battleState.currentTurnIndex >= battleState.turnOrder.length)
        return false;
    const idx = battleState.turnOrder[battleState.currentTurnIndex];
    return idx < battleState.playerParty.length && battleState.playerParty[idx].currentHp > 0;
}
function isEnemyTurn() {
    if (!battleState || battleState.currentTurnIndex >= battleState.turnOrder.length)
        return false;
    const idx = battleState.turnOrder[battleState.currentTurnIndex];
    return idx >= battleState.playerParty.length && battleState.enemies[idx - battleState.playerParty.length].currentHp > 0;
}
function getCurrentActor() {
    if (!battleState || battleState.currentTurnIndex >= battleState.turnOrder.length)
        return null;
    const idx = battleState.turnOrder[battleState.currentTurnIndex];
    if (idx < battleState.playerParty.length)
        return battleState.playerParty[idx];
    return battleState.enemies[idx - battleState.playerParty.length];
}
};

// ── module: src/overworld.ts ──
__mods["src/overworld.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TILE_SIZE = exports.MAP_H = exports.MAP_W = void 0;
exports.getTileAt = getTileAt;
exports.isWalkable = isWalkable;
exports.getShrinePos = getShrinePos;
exports.getGuardianPos = getGuardianPos;
exports.getExitPos = getExitPos;
exports.setCamera = setCamera;
exports.getCamera = getCamera;
exports.updateAmbientParticles = updateAmbientParticles;
exports.getAmbientParticles = getAmbientParticles;
exports.checkEncounter = checkEncounter;
exports.checkZoneExit = checkZoneExit;
exports.checkShrine = checkShrine;
exports.healParty = healParty;
const zones_1 = require("./data/zones");
// Tile map for each zone - simple grid-based maps
const TILE_SIZE = 40;
exports.TILE_SIZE = TILE_SIZE;
const MAP_W = 20;
exports.MAP_W = MAP_W;
const MAP_H = 15;
exports.MAP_H = MAP_H;
function generateZoneMap(zoneId) {
    const tiles = [];
    for (let y = 0; y < MAP_H; y++) {
        tiles[y] = [];
        for (let x = 0; x < MAP_W; x++) {
            // Border walls
            if (x === 0 || x === MAP_W - 1 || y === 0 || y === MAP_H - 1) {
                tiles[y][x] = 1;
            }
            else {
                tiles[y][x] = 0; // start all walkable
            }
        }
    }
    const cx = Math.floor(MAP_W / 2);
    const cy = Math.floor(MAP_H / 2);
    // Place shrine at center
    const shrinePos = { x: cx, y: cy };
    tiles[shrinePos.y][shrinePos.x] = 2;
    // Create tall grass patches around the map (not on paths)
    for (let y = 1; y < MAP_H - 1; y++) {
        for (let x = 1; x < MAP_W - 1; x++) {
            if (x === cx || y === cy)
                continue; // keep cross path clear
            const r = Math.random();
            if (r < 0.35)
                tiles[y][x] = 5; // tall grass
            else if (r < 0.42)
                tiles[y][x] = 1; // wall
        }
    }
    // Ensure clear paths from shrine to all edges
    for (let x = 1; x < MAP_W - 1; x++) {
        tiles[cy][x] = 0;
    }
    for (let y = 1; y < MAP_H - 1; y++) {
        tiles[cx][y] = 0;
    }
    // Guardian position (top-right area)
    const guardianPos = { x: MAP_W - 3, y: 2 };
    if (zones_1.ZONES[zoneId].guardianId) {
        tiles[guardianPos.y][guardianPos.x] = 3;
        // Clear path to guardian
        for (let x = cx; x < MAP_W - 2; x++)
            tiles[2][x] = 0;
        for (let y = cy; y > 1; y--)
            tiles[cx + (MAP_W - 3 - cx) > 0 ? Math.floor((MAP_W - 3 - cx) / 2) : cx][y] = 0;
    }
    // Zone exit (bottom-right area)
    const nextZone = Object.entries(zones_1.ZONES).find(([_, z]) => z.unlockedByZone === zoneId);
    let exitPos;
    if (nextZone) {
        exitPos = { x: MAP_W - 3, y: MAP_H - 3 };
        tiles[exitPos.y][exitPos.x] = 4;
        // Clear path to exit
        for (let x = cx; x < MAP_W - 2; x++)
            tiles[MAP_H - 3][x] = 0;
        for (let y = cy; y < MAP_H - 2; y++)
            tiles[cx + Math.floor((MAP_W - 3 - cx) / 2)][y] = 0;
    }
    return { tiles, shrinePos, guardianPos, exitPos };
}
// Pre-generate maps with seeded randomness for consistency
const zoneMaps = {};
function getZoneMap(zoneId) {
    if (!zoneMaps[zoneId]) {
        // Use a simple seed based on zone name length for consistent maps
        const origRandom = Math.random;
        let seed = zoneId.length * 137 + 42;
        Math.random = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; };
        zoneMaps[zoneId] = generateZoneMap(zoneId);
        Math.random = origRandom;
    }
    return zoneMaps[zoneId];
}
function getTileAt(zoneId, x, y) {
    const map = getZoneMap(zoneId);
    if (x < 0 || x >= MAP_W || y < 0 || y >= MAP_H)
        return 1;
    return map.tiles[y][x];
}
function isWalkable(zoneId, x, y) {
    const tile = getTileAt(zoneId, x, y);
    return tile !== 1; // only walls block movement
}
function getShrinePos(zoneId) { return getZoneMap(zoneId).shrinePos; }
function getGuardianPos(zoneId) { return getZoneMap(zoneId).guardianPos; }
function getExitPos(zoneId) { return getZoneMap(zoneId).exitPos; }
// Camera tracking for rendering
let cameraX = 0, cameraY = 0;
function setCamera(x, y) { cameraX = x; cameraY = y; }
function getCamera() { return { x: cameraX, y: cameraY }; }
let ambientParticles = [];
function updateAmbientParticles(zoneId, canvasW, canvasH) {
    const zone = zones_1.ZONES[zoneId];
    if (!zone)
        return;
    // Spawn new particles
    if (Math.random() < 0.3) {
        ambientParticles.push({
            x: Math.random() * canvasW,
            y: canvasH + 10,
            vx: (Math.random() - 0.5) * 0.5,
            vy: -0.5 - Math.random(),
            life: 120 + Math.random() * 60,
            maxLife: 180,
            size: 1 + Math.random() * 3,
            alpha: 0.3 + Math.random() * 0.4
        });
    }
    // Update existing
    ambientParticles = ambientParticles.filter(p => {
        p.x += p.vx;
        p.y += p.vy;
        p.life--;
        return p.life > 0;
    });
}
function getAmbientParticles() { return ambientParticles; }
// Track steps in tall grass for forced encounters
let tallGrassSteps = 0;
function checkEncounter(state) {
    const zone = zones_1.ZONES[state.currentZone];
    if (!zone || !zone.wildCreatures.length)
        return null;
    // Check for guardian encounter
    const gPos = getGuardianPos(state.currentZone);
    if (gPos && state.playerX === gPos.x && state.playerY === gPos.y) {
        if (zones_1.ZONES[state.currentZone].guardianId && !state.defeatedGuardians.includes(state.currentZone)) {
            return { enemies: [{ templateId: zones_1.ZONES[state.currentZone].guardianId, level: zone.guardianLevel }], isBoss: true };
        }
    }
    // Check for zone exit
    const ePos = getExitPos(state.currentZone);
    if (ePos && state.playerX === ePos.x && state.playerY === ePos.y) {
        return null; // handled separately
    }
    // Random wild encounter in tall grass or by rate
    const tile = getTileAt(state.currentZone, state.playerX, state.playerY);
    if (tile === 5) {
        tallGrassSteps++;
    }
    else {
        tallGrassSteps = Math.max(0, tallGrassSteps - 2); // Decay when not in grass
    }
    const rate = tile === 5 ? zone.encounterRate * 1.8 : zone.encounterRate;
    // Force encounter after enough steps in tall grass (guaranteed within ~6 steps)
    if (tallGrassSteps >= 6 || Math.random() < rate) {
        tallGrassSteps = 0;
        const pool = zone.wildCreatures;
        const pick = pool[Math.floor(Math.random() * pool.length)];
        let level = pick.minLvl + Math.floor(Math.random() * (pick.maxLvl - pick.minLvl + 1));
        // Post-game: harder spawns
        if (state.postGame) {
            level = Math.min(level + 5, 50);
        }
        return { enemies: [{ templateId: pick.templateId, level }] };
    }
    return null;
}
function checkZoneExit(state) {
    const ePos = getExitPos(state.currentZone);
    if (!ePos)
        return null;
    if (state.playerX === ePos.x && state.playerY === ePos.y) {
        // Find next zone
        for (const [id, z] of Object.entries(zones_1.ZONES)) {
            if (z.unlockedByZone === state.currentZone) {
                if (!state.unlockedZones.includes(id)) {
                    state.unlockedZones.push(id);
                }
                return id;
            }
        }
    }
    return null;
}
function checkShrine(state) {
    const sPos = getShrinePos(state.currentZone);
    if (sPos && state.playerX === sPos.x && state.playerY === sPos.y) {
        return true;
    }
    return false;
}
function healParty(party) {
    for (const c of party) {
        c.currentHp = c.maxHp;
        if (c.status)
            c.status = undefined;
    }
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resizeCanvas = resizeCanvas;
exports.renderOverworld = renderOverworld;
exports.renderBattle = renderBattle;
exports.updateBattleEffects = updateBattleEffects;
const creatures_1 = require("./data/creatures");
const zones_1 = require("./data/zones");
const overworld_1 = require("./overworld");
const battle_1 = require("./battle");
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
let dpr = 1;
function resizeCanvas() {
    dpr = window.devicePixelRatio || 1;
    canvas.width = window.innerWidth * dpr;
    canvas.height = window.innerHeight * dpr;
    ctx.scale(dpr, dpr);
}
// === OVERWORLD RENDERING ===
let playerAnimFrame = 0;
let playerDir = 'down';
function renderOverworld(state) {
    const w = window.innerWidth;
    const h = window.innerHeight;
    const zone = zones_1.ZONES[state.currentZone];
    if (!zone)
        return;
    // Background gradient with subtle vignette
    const grad = ctx.createRadialGradient(w / 2, h / 2, 0, w / 2, h / 2, Math.max(w, h) * 0.7);
    grad.addColorStop(0, zone.gradientEnd);
    grad.addColorStop(1, zone.gradientStart);
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, w, h);
    // Ambient particles
    (0, overworld_1.updateAmbientParticles)(state.currentZone, w, h);
    for (const p of (0, overworld_1.getAmbientParticles)()) {
        const alpha = (p.life / p.maxLife) * p.alpha;
        ctx.globalAlpha = alpha;
        ctx.fillStyle = zone.particleColor;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
        ctx.fill();
    }
    ctx.globalAlpha = 1;
    // Camera centered on player with smooth follow
    const camX = state.playerX * overworld_1.TILE_SIZE - w / 2 + overworld_1.TILE_SIZE / 2;
    const camY = state.playerY * overworld_1.TILE_SIZE - h / 2 + overworld_1.TILE_SIZE / 2;
    ctx.save();
    ctx.translate(-camX, -camY);
    // Draw tiles with subtle grid
    for (let y = 0; y < overworld_1.MAP_H; y++) {
        for (let x = 0; x < overworld_1.MAP_W; x++) {
            const tile = (0, overworld_1.getTileAt)(state.currentZone, x, y);
            const px = x * overworld_1.TILE_SIZE;
            const py = y * overworld_1.TILE_SIZE;
            // Skip off-screen tiles
            if (px + overworld_1.TILE_SIZE < camX || px > camX + w || py + overworld_1.TILE_SIZE < camY || py > camY + h)
                continue;
            switch (tile) {
                case 0: // walkable - subtle ground texture
                    ctx.fillStyle = 'rgba(255,255,255,0.02)';
                    ctx.fillRect(px, py, overworld_1.TILE_SIZE, overworld_1.TILE_SIZE);
                    // Grid line
                    ctx.strokeStyle = 'rgba(255,255,255,0.04)';
                    ctx.lineWidth = 0.5;
                    ctx.strokeRect(px + 0.5, py + 0.5, overworld_1.TILE_SIZE - 1, overworld_1.TILE_SIZE - 1);
                    break;
                case 1: // wall - dark stone blocks
                    ctx.fillStyle = '#12122a';
                    ctx.fillRect(px, py, overworld_1.TILE_SIZE, overworld_1.TILE_SIZE);
                    ctx.strokeStyle = 'rgba(100,80,160,0.3)';
                    ctx.lineWidth = 1;
                    ctx.strokeRect(px + 1, py + 1, overworld_1.TILE_SIZE - 2, overworld_1.TILE_SIZE - 2);
                    // Inner detail
                    ctx.fillStyle = 'rgba(100,80,160,0.15)';
                    ctx.fillRect(px + 4, py + 4, overworld_1.TILE_SIZE - 8, overworld_1.TILE_SIZE - 8);
                    break;
                case 2: // shrine
                    ctx.fillStyle = 'rgba(123,104,238,0.1)';
                    ctx.fillRect(px, py, overworld_1.TILE_SIZE, overworld_1.TILE_SIZE);
                    drawShrineIcon(px + overworld_1.TILE_SIZE / 2, py + overworld_1.TILE_SIZE / 2);
                    break;
                case 3: // guardian
                    ctx.fillStyle = 'rgba(239,68,68,0.1)';
                    ctx.fillRect(px, py, overworld_1.TILE_SIZE, overworld_1.TILE_SIZE);
                    drawGuardianIcon(px + overworld_1.TILE_SIZE / 2, py + overworld_1.TILE_SIZE / 2);
                    break;
                case 4: // zone exit
                    ctx.fillStyle = 'rgba(74,222,128,0.1)';
                    ctx.fillRect(px, py, overworld_1.TILE_SIZE, overworld_1.TILE_SIZE);
                    drawExitIcon(px + overworld_1.TILE_SIZE / 2, py + overworld_1.TILE_SIZE / 2);
                    break;
                case 5: // tall grass - encounter zone
                    ctx.fillStyle = 'rgba(132,204,22,0.08)';
                    ctx.fillRect(px, py, overworld_1.TILE_SIZE, overworld_1.TILE_SIZE);
                    drawGrassIcon(px + overworld_1.TILE_SIZE / 2, py + overworld_1.TILE_SIZE / 2);
                    break;
            }
        }
    }
    // Draw player character with shadow
    const px = state.playerX * overworld_1.TILE_SIZE + overworld_1.TILE_SIZE / 2;
    const py = state.playerY * overworld_1.TILE_SIZE + overworld_1.TILE_SIZE / 2;
    // Shadow
    ctx.fillStyle = 'rgba(0,0,0,0.3)';
    ctx.beginPath();
    ctx.ellipse(px, py + 14, 8, 3, 0, 0, Math.PI * 2);
    ctx.fill();
    drawPlayer(px, py, playerDir);
    ctx.restore();
    // Zone name overlay with glass effect
    ctx.fillStyle = 'rgba(10,14,26,0.5)';
    roundRect(ctx, 8, 8, 200, 32, 8);
    ctx.fill();
    ctx.font = 'bold 13px system-ui';
    ctx.fillStyle = '#a09cb5';
    ctx.textAlign = 'left';
    ctx.fillText(zone.name.toUpperCase(), 16, 29);
    // Party preview (top right) with glass effect
    let partyX = w - 16;
    for (let i = state.party.length - 1; i >= 0 && partyX > 100; i--) {
        const c = state.party[i];
        const tmpl = (0, creatures_1.getCreatureTemplate)(c.templateId);
        ctx.font = 'bold 11px system-ui';
        ctx.textAlign = 'right';
        ctx.fillStyle = '#e8e6f0';
        ctx.fillText(`${tmpl.name} Lv${c.level}`, partyX, 29);
        partyX -= 100;
    }
    // Story vignette overlay (if story stage advanced)
    if (state.storyStage > 0 && state.screen === 'overworld') {
        const vignetteAlpha = Math.min(0.3, Math.sin(Date.now() / 2000) * 0.1 + 0.2);
        ctx.fillStyle = `rgba(0,0,0,${vignetteAlpha})`;
        ctx.fillRect(0, h - 60, w, 60);
        ctx.font = 'italic 13px system-ui';
        ctx.textAlign = 'center';
        ctx.fillStyle = '#a09cb5';
        const storyTexts = [
            '',
            'The Aether flows thin. The Anchors stir in their slumber.',
            'Deeper currents call from below the surface.',
            'Ancient roots awaken in response to your journey.',
            'All paths converge at the Spire.'
        ];
        ctx.fillText(storyTexts[state.storyStage] || '', w / 2, h - 30);
    }
    playerAnimFrame++;
}
function roundRect(ctx, x, y, w, h, r) {
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y);
    ctx.quadraticCurveTo(x + w, y, x + w, y + r);
    ctx.lineTo(x + w, y + h - r);
    ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
    ctx.lineTo(x + r, y + h);
    ctx.quadraticCurveTo(x, y + h, x, y + h - r);
    ctx.lineTo(x, y + r);
    ctx.quadraticCurveTo(x, y, x + r, y);
    ctx.closePath();
}
function drawShrineIcon(x, y) {
    const t = Date.now() / 1000;
    ctx.save();
    ctx.translate(x, y);
    // Glowing diamond shape
    const glow = 0.5 + Math.sin(t * 2) * 0.3;
    ctx.fillStyle = `rgba(123,104,238,${glow})`;
    ctx.beginPath();
    ctx.moveTo(0, -12);
    ctx.lineTo(8, 0);
    ctx.lineTo(0, 12);
    ctx.lineTo(-8, 0);
    ctx.closePath();
    ctx.fill();
    // Inner glow
    ctx.fillStyle = `rgba(255,255,255,${glow * 0.5})`;
    ctx.beginPath();
    ctx.moveTo(0, -6);
    ctx.lineTo(4, 0);
    ctx.lineTo(0, 6);
    ctx.lineTo(-4, 0);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
}
function drawGuardianIcon(x, y) {
    const t = Date.now() / 1000;
    ctx.save();
    ctx.translate(x, y);
    // Pulsing red circle with skull-like shape
    const pulse = 0.6 + Math.sin(t * 3) * 0.4;
    ctx.fillStyle = `rgba(239,68,68,${pulse})`;
    ctx.beginPath();
    ctx.arc(0, 0, 10, 0, Math.PI * 2);
    ctx.fill();
    // Eyes
    ctx.fillStyle = '#fff';
    ctx.fillRect(-5, -4, 3, 3);
    ctx.fillRect(2, -4, 3, 3);
    ctx.restore();
}
function drawExitIcon(x, y) {
    const t = Date.now() / 1000;
    ctx.save();
    ctx.translate(x, y);
    // Green arrow pointing right
    const pulse = 0.5 + Math.sin(t * 2) * 0.3;
    ctx.fillStyle = `rgba(74,222,128,${pulse})`;
    ctx.beginPath();
    ctx.moveTo(-6, -8);
    ctx.lineTo(6, 0);
    ctx.lineTo(-6, 8);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
}
function drawGrassIcon(x, y) {
    const t = Date.now() / 1000;
    ctx.save();
    ctx.translate(x, y);
    // Simple grass tufts
    for (let i = -2; i <= 2; i++) {
        const sway = Math.sin(t * 3 + i) * 2;
        ctx.strokeStyle = 'rgba(132,204,22,0.3)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(i * 4, 8);
        ctx.quadraticCurveTo(i * 4 + sway, -2, i * 4 + sway * 2, -6);
        ctx.stroke();
    }
    ctx.restore();
}
function drawPlayer(x, y, dir) {
    const t = Date.now() / 1000;
    const bobY = Math.sin(t * 4) * 2;
    ctx.save();
    ctx.translate(x, y + bobY);
    // Body - simple character shape
    ctx.fillStyle = '#7b68ee';
    ctx.beginPath();
    ctx.arc(0, -2, 10, 0, Math.PI * 2);
    ctx.fill();
    // Head
    ctx.fillStyle = '#9d8df1';
    ctx.beginPath();
    ctx.arc(0, -10, 7, 0, Math.PI * 2);
    ctx.fill();
    // Eyes based on direction
    ctx.fillStyle = '#fff';
    if (dir === 'down' || dir === 'left' || dir === 'right') {
        const eyeOffX = dir === 'left' ? -3 : dir === 'right' ? 3 : 0;
        ctx.fillRect(-4 + eyeOffX, -12, 3, 3);
        ctx.fillRect(1 + eyeOffX, -12, 3, 3);
    }
    // Direction indicator (small triangle)
    ctx.fillStyle = '#fff';
    const dirOffsets = { up: [0, -16], down: [0, 8], left: [-14, -2], right: [14, -2] };
    const [dx, dy] = dirOffsets[dir] || [0, 8];
    ctx.beginPath();
    ctx.moveTo(dx, dy);
    ctx.lineTo(dx - 3, dy + 5);
    ctx.lineTo(dx + 3, dy + 5);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
}
// === BATTLE RENDERING ===
function renderBattle() {
    const bs = (0, battle_1.getBattleState)();
    if (!bs)
        return;
    const w = window.innerWidth;
    const h = window.innerHeight;
    const zone = zones_1.ZONES[bs.zoneId];
    // Background with atmospheric gradient
    const grad = ctx.createRadialGradient(w / 2, h / 2, 0, w / 2, h / 2, Math.max(w, h) * 0.6);
    grad.addColorStop(0, zone?.gradientEnd || '#1a1040');
    grad.addColorStop(1, zone?.gradientStart || '#0a0e1a');
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, w, h);
    // Atmospheric particles in background
    const t = Date.now() / 1000;
    for (let i = 0; i < 20; i++) {
        const px = ((i * 73 + t * 10) % w);
        const py = ((i * 47 + Math.sin(t + i) * 20) % h);
        ctx.globalAlpha = 0.05 + Math.sin(t * 2 + i) * 0.03;
        ctx.fillStyle = zone?.particleColor || '#ffffff';
        ctx.beginPath();
        ctx.arc(px, py, 1 + Math.sin(t + i) * 0.5, 0, Math.PI * 2);
        ctx.fill();
    }
    ctx.globalAlpha = 1;
    // Screen shake
    const shake = (0, battle_1.getShakeAmount)();
    if (shake > 0) {
        ctx.save();
        ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);
    }
    // Draw enemies
    for (let i = 0; i < bs.enemies.length; i++) {
        const e = bs.enemies[i];
        if (e.currentHp <= 0)
            continue;
        drawBattleCreature(e, w - 250 + i * 120, 80, false);
    }
    // Draw player creature
    if (bs.playerParty[bs.activePlayerIndex]) {
        const pc = bs.playerParty[bs.activePlayerIndex];
        drawBattleCreature(pc, 100, h - 250, true);
    }
    // Particles
    for (const p of (0, battle_1.getParticles)()) {
        ctx.globalAlpha = p.life / p.maxLife;
        ctx.fillStyle = p.color;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.size * (p.life / p.maxLife), 0, Math.PI * 2);
        ctx.fill();
    }
    ctx.globalAlpha = 1;
    // Float texts
    for (const ft of (0, battle_1.getFloatTexts)()) {
        ctx.globalAlpha = ft.life / 60;
        ctx.font = `bold ${ft.size || 24}px system-ui`;
        ctx.fillStyle = ft.color;
        ctx.textAlign = 'center';
        ctx.fillText(ft.text, ft.x, ft.y - (1 - ft.life / 60) * 40);
    }
    ctx.globalAlpha = 1;
    if (shake > 0) {
        ctx.restore();
    }
}
function drawBattleCreature(creature, x, y, isPlayer) {
    const tmpl = (0, creatures_1.getCreatureTemplate)(creature.templateId);
    const t = Date.now() / 1000;
    const bobY = Math.sin(t * 2) * 3;
    ctx.save();
    ctx.translate(x, y + bobY);
    // Creature silhouette - different shapes per affinity
    const mainColor = tmpl.affinities[0] === 'Pyra' ? '#ff6b35' :
        tmpl.affinities[0] === 'Aqua' ? '#3b82f6' :
            tmpl.affinities[0] === 'Terra' ? '#84cc16' :
                tmpl.affinities[0] === 'Lux' ? '#facc15' :
                    tmpl.affinities[0] === 'Umbra' ? '#7c3aed' :
                        tmpl.affinities[0] === 'Void' ? '#e040fb' : '#ffffff';
    // Body shape based on affinity
    ctx.fillStyle = mainColor;
    ctx.globalAlpha = 0.8;
    if (tmpl.affinities[0] === 'Pyra') {
        // Flame shape
        ctx.beginPath();
        ctx.moveTo(0, -35);
        ctx.quadraticCurveTo(20, -10, 15, 10);
        ctx.quadraticCurveTo(10, 25, 0, 30);
        ctx.quadraticCurveTo(-10, 25, -15, 10);
        ctx.quadraticCurveTo(-20, -10, 0, -35);
        ctx.fill();
    }
    else if (tmpl.affinities[0] === 'Aqua') {
        // Water drop shape
        ctx.beginPath();
        ctx.moveTo(0, -30);
        ctx.quadraticCurveTo(25, 0, 20, 20);
        ctx.quadraticCurveTo(15, 35, 0, 35);
        ctx.quadraticCurveTo(-15, 35, -20, 20);
        ctx.quadraticCurveTo(-25, 0, 0, -30);
        ctx.fill();
    }
    else if (tmpl.affinities[0] === 'Terra') {
        // Crystal shape
        ctx.beginPath();
        ctx.moveTo(0, -30);
        ctx.lineTo(18, -5);
        ctx.lineTo(12, 25);
        ctx.lineTo(-12, 25);
        ctx.lineTo(-18, -5);
        ctx.closePath();
        ctx.fill();
    }
    else if (tmpl.affinities[0] === 'Lux') {
        // Star shape
        for (let i = 0; i < 5; i++) {
            const angle = (i * Math.PI * 2 / 5) - Math.PI / 2;
            const r1 = 30, r2 = 14;
            ctx.lineTo(Math.cos(angle) * r1, Math.sin(angle) * r1);
            const a2 = angle + Math.PI / 5;
            ctx.lineTo(Math.cos(a2) * r2, Math.sin(a2) * r2);
        }
        ctx.closePath();
        ctx.fill();
    }
    else if (tmpl.affinities[0] === 'Umbra') {
        // Shadow shape - jagged
        ctx.beginPath();
        ctx.moveTo(-15, -25);
        ctx.lineTo(5, -35);
        ctx.lineTo(20, -15);
        ctx.lineTo(15, 10);
        ctx.lineTo(25, 20);
        ctx.lineTo(0, 30);
        ctx.lineTo(-20, 25);
        ctx.lineTo(-18, 5);
        ctx.closePath();
        ctx.fill();
    }
    else if (tmpl.affinities[0] === 'Void') {
        // Swirling void shape
        for (let i = 0; i < 3; i++) {
            const r = 12 + i * 8;
            ctx.globalAlpha = 0.4 - i * 0.1;
            ctx.beginPath();
            ctx.arc(Math.sin(t + i) * 5, Math.cos(t + i) * 5, r, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    else {
        // Prism - hexagonal crystal
        for (let i = 0; i < 6; i++) {
            const angle = i * Math.PI / 3;
            if (i === 0)
                ctx.moveTo(Math.cos(angle) * 25, Math.sin(angle) * 25);
            else
                ctx.lineTo(Math.cos(angle) * 25, Math.sin(angle) * 25);
        }
        ctx.closePath();
        ctx.fill();
    }
    // Eyes
    ctx.globalAlpha = 1;
    ctx.fillStyle = '#fff';
    ctx.fillRect(-6, -8, 4, 4);
    ctx.fillRect(2, -8, 4, 4);
    ctx.fillStyle = '#000';
    ctx.fillRect(-5, -7, 2, 2);
    ctx.fillRect(3, -7, 2, 2);
    // Status effect indicator
    if (creature.status) {
        const statusColors = { Burn: '#ff6b35', Frost: '#3b82f6', Shock: '#e040fb', Barrier: '#ffffff', Regen: '#4ade80' };
        ctx.fillStyle = statusColors[creature.status.effect] || '#fff';
        ctx.font = '10px system-ui';
        ctx.textAlign = 'center';
        ctx.fillText(creature.status.effect, 0, -35);
    }
    // HP bar below creature
    const hpRatio = creature.currentHp / creature.maxHp;
    const barW = 60;
    ctx.fillStyle = 'rgba(0,0,0,0.5)';
    ctx.fillRect(-barW / 2, 38, barW, 6);
    const hpColor = hpRatio > 0.5 ? '#4ade80' : hpRatio > 0.25 ? '#fbbf24' : '#ef4444';
    ctx.fillStyle = hpColor;
    ctx.fillRect(-barW / 2, 38, barW * hpRatio, 6);
    // Name and level
    ctx.font = 'bold 11px system-ui';
    ctx.textAlign = 'center';
    ctx.fillStyle = '#e8e6f0';
    ctx.fillText(`${tmpl.name}`, 0, -42);
    ctx.font = '9px system-ui';
    ctx.fillStyle = '#a09cb5';
    ctx.fillText(`Lv ${creature.level}`, 0, -52);
    // Affinity badge
    ctx.fillStyle = mainColor;
    ctx.beginPath();
    ctx.arc(-30, -48, 6, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
}
// Update particles and float texts
function updateBattleEffects() {
    // Update particles
    const ps = (0, battle_1.getParticles)();
    for (let i = ps.length - 1; i >= 0; i--) {
        ps[i].x += ps[i].vx;
        ps[i].y += ps[i].vy;
        ps[i].life--;
        if (ps[i].life <= 0)
            ps.splice(i, 1);
    }
    // Update float texts
    const fts = (0, battle_1.getFloatTexts)();
    for (let i = fts.length - 1; i >= 0; i--) {
        fts[i].life--;
        if (fts[i].life <= 0)
            fts.splice(i, 1);
    }
    // Decay shake
    const currentShake = (0, battle_1.getShakeAmount)();
    if (currentShake > 0) {
        // Shake decays naturally through re-rendering
    }
}
};

// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.showDialog = showDialog;
exports.hideDialog = hideDialog;
exports.showActionMenu = showActionMenu;
exports.hideActionMenu = hideActionMenu;
exports.showAbilityList = showAbilityList;
exports.hideAbilityList = hideAbilityList;
exports.updateBattleCards = updateBattleCards;
exports.updateEnemyCards = updateEnemyCards;
exports.showModal = showModal;
exports.hideModal = hideModal;
exports.showMainMenu = showMainMenu;
exports.showTeamScreen = showTeamScreen;
exports.showBagScreen = showBagScreen;
exports.showBestiaryScreen = showBestiaryScreen;
exports.showMapScreen = showMapScreen;
exports.showShrineMenu = showShrineMenu;
exports.showShopScreen = showShopScreen;
exports.showCaptureMiniGame = showCaptureMiniGame;
exports.showTutorialStep = showTutorialStep;
exports.showStarterChoice = showStarterChoice;
const creatures_1 = require("./data/creatures");
const abilities_1 = require("./data/abilities");
const zones_1 = require("./data/zones");
const types_1 = require("./types");
const state_1 = require("./state");
const modalOverlay = document.getElementById('modal-overlay');
const dialogBox = document.getElementById('dialog-box');
const actionMenu = document.getElementById('action-menu');
const abilityList = document.getElementById('ability-list');
const enemyArea = document.getElementById('enemy-area');
const playerArea = document.getElementById('player-area');
function showDialog(text) {
    dialogBox.textContent = text;
    dialogBox.style.display = 'block';
}
function hideDialog() {
    dialogBox.style.display = 'none';
}
function showActionMenu(items) {
    actionMenu.innerHTML = '';
    items.forEach(item => {
        const btn = document.createElement('button');
        btn.className = 'action-btn-choice';
        btn.textContent = item.label;
        btn.onclick = () => { item.action(); hideActionMenu(); };
        actionMenu.appendChild(btn);
    });
    actionMenu.classList.remove('hidden');
}
function hideActionMenu() {
    actionMenu.classList.add('hidden');
}
function showAbilityList(abilities, onSelect) {
    abilityList.innerHTML = '';
    abilities.forEach(ab => {
        const def = (0, abilities_1.getAbility)(ab.abilityId);
        if (!def)
            return;
        const btn = document.createElement('button');
        btn.className = 'ability-btn';
        btn.innerHTML = `<span class="ab-name">${def.name}</span> <span style="color:${types_1.AFFINITY_COLORS[def.affinity]}">[${def.affinity}]</span><div class="ab-info">PP: ${ab.pp}/${def.maxPp} | Power: ${def.power || '—'} | ${def.effect !== 'none' ? def.effect : ''}</div>`;
        if (ab.pp <= 0)
            btn.style.opacity = '0.4';
        else
            btn.onclick = () => { onSelect(ab.abilityId); hideAbilityList(); };
        abilityList.appendChild(btn);
    });
    abilityList.classList.remove('hidden');
}
function hideAbilityList() {
    abilityList.classList.add('hidden');
}
// Update battle creature cards
function updateBattleCards(state) {
    // Player area
    playerArea.innerHTML = '';
    state.party.forEach((c, i) => {
        const tmpl = (0, creatures_1.getCreatureTemplate)(c.templateId);
        const card = document.createElement('div');
        card.className = 'creature-card';
        if (c.currentHp <= 0)
            card.style.opacity = '0.4';
        const hpRatio = c.maxHp > 0 ? c.currentHp / c.maxHp : 0;
        const hpColor = hpRatio > 0.5 ? '#4ade80' : hpRatio > 0.25 ? '#fbbf24' : '#ef4444';
        card.innerHTML = `
      <div class="name" style="color:${types_1.AFFINITY_COLORS[tmpl.affinities[0]]}">${tmpl.name}</div>
      <div class="level">Lv ${c.level} | HP: ${c.currentHp}/${c.maxHp}</div>
      <div class="hp-bar-container"><div class="hp-bar-fill" style="width:${hpRatio * 100}%;background:${hpColor}"></div></div>
    `;
        playerArea.appendChild(card);
    });
}
function updateEnemyCards(enemies) {
    enemyArea.innerHTML = '';
    enemies.forEach(e => {
        if (e.currentHp <= 0)
            return;
        const tmpl = (0, creatures_1.getCreatureTemplate)(e.templateId);
        const card = document.createElement('div');
        card.className = 'creature-card';
        const hpRatio = e.maxHp > 0 ? e.currentHp / e.maxHp : 0;
        const hpColor = hpRatio > 0.5 ? '#4ade80' : hpRatio > 0.25 ? '#fbbf24' : '#ef4444';
        card.innerHTML = `
      <div class="name" style="color:${types_1.AFFINITY_COLORS[tmpl.affinities[0]]}">${tmpl.name}</div>
      <div class="level">Lv ${e.level}</div>
      <div class="hp-bar-container"><div class="hp-bar-fill" style="width:${hpRatio * 100}%;background:${hpColor}"></div></div>
    `;
        enemyArea.appendChild(card);
    });
}
// === MODAL SYSTEM ===
let modalCallback = null;
function showModal(content, onClose) {
    modalOverlay.innerHTML = `<div class="modal">${content}</div>`;
    modalOverlay.classList.remove('hidden');
    modalCallback = onClose || null;
}
function hideModal() {
    modalOverlay.classList.add('hidden');
    const cb = modalCallback;
    modalCallback = null;
    if (cb)
        cb();
}
// === MENU SCREENS ===
function showMainMenu(state) {
    return `
    <h2>Menu</h2>
    <div style="display:flex;flex-direction:column;gap:8px;">
      <button class="action-btn-choice" data-menu="team">Team (${state.party.length}/6)</button>
      <button class="action-btn-choice" data-menu="bag">Bag</button>
      <button class="action-btn-choice" data-menu="encyclopedia">Bestiary</button>
      <button class="action-btn-choice" data-menu="map">Map</button>
      <button class="action-btn-choice" data-menu="save">Save Game</button>
      <button class="action-btn-choice" data-menu="close">Close</button>
    </div>`;
}
function showTeamScreen(state) {
    let html = '<h2>Team</h2><div style="display:flex;flex-direction:column;gap:8px;">';
    state.party.forEach((c, i) => {
        const tmpl = (0, creatures_1.getCreatureTemplate)(c.templateId);
        const canTr = (0, state_1.canTransform)(c);
        html += `
      <div class="creature-card" style="width:100%;display:block;">
        <div class="name" style="color:${types_1.AFFINITY_COLORS[tmpl.affinities[0]]}">${tmpl.name}</div>
        <div class="level">Lv ${c.level} | HP: ${c.currentHp}/${c.maxHp} | ATK: ${c.atk} DEF: ${c.def} SPD: ${c.spd}</div>
        <div style="font-size:0.75rem;color:${types_1.AFFINITY_COLORS[tmpl.affinities[0]]};margin-top:4px;">${tmpl.affinities.join(' / ')}</div>
        <div class="hp-bar-container"><div class="hp-bar-fill" style="width:${(c.currentHp / c.maxHp) * 100}%"></div></div>
        ${canTr ? `<button class="action-btn-choice transform-btn" data-tidx="${i}" style="margin-top:6px;font-size:0.8rem;">Transform</button>` : ''}
      </div>`;
    });
    html += '</div><button class="action-btn-choice back-btn" style="margin-top:12px;">Back</button>';
    return html;
}
function showBagScreen(state) {
    let html = '<h2>Bag</h2><div style="display:flex;flex-direction:column;gap:6px;">';
    for (const [itemId, count] of Object.entries(state.inventory)) {
        if (count <= 0)
            continue;
        const item = zones_1.ITEMS[itemId];
        if (!item)
            continue;
        html += `<div style="display:flex;justify-content:space-between;align-items:center;padding:6px;background:rgba(255,255,255,0.05);border-radius:6px;">
      <div><strong>${item.name}</strong> <span style="color:#a09cb5;font-size:0.8rem;">${item.description}</span></div>
      <span>x${count}</span>
    </div>`;
    }
    html += '</div><button class="action-btn-choice back-btn" style="margin-top:12px;">Back</button>';
    return html;
}
function showBestiaryScreen(state) {
    let html = '<h2>Bestiary</h2><div style="display:flex;flex-direction:column;gap:6px;max-height:50vh;overflow-y:auto;">';
    for (const tmpl of Object.values(creatures_1.CREATURES)) {
        const seen = state.collectedCreatures.has(tmpl.id);
        html += `<div style="padding:8px;background:${seen ? 'rgba(123,104,238,0.1)' : 'rgba(255,255,255,0.03)'};border-radius:6px;border-left:3px solid ${types_1.AFFINITY_COLORS[tmpl.affinities[0]]};">
      <strong style="color:${types_1.AFFINITY_COLORS[tmpl.affinities[0]]}">${seen ? tmpl.name : '???'}</strong>
      <span style="font-size:0.75rem;color:#a09cb5;margin-left:8px;">${tmpl.affinities.join('/')}</span>
      ${seen ? `<div style="font-size:0.75rem;color:#a09cb5;margin-top:4px;">${tmpl.lore}</div>` : ''}
    </div>`;
    }
    html += '</div><button class="action-btn-choice back-btn" style="margin-top:12px;">Back</button>';
    return html;
}
function showMapScreen(state) {
    let html = '<h2>World Map</h2><div style="display:flex;flex-direction:column;gap:6px;">';
    const zoneOrder = ['ember_pass', 'deep_trench', 'stoneheart_cavern', 'prism_spire', 'shadow_veil', 'the_between'];
    for (const zid of zoneOrder) {
        const zone = zones_1.ITEMS['aether-net'] ? null : null; // dummy access
        html += `<div style="padding:8px;background:${state.unlockedZones.includes(zid) ? 'rgba(123,104,238,0.15)' : 'rgba(255,255,255,0.03)'};border-radius:6px;border-left:3px solid ${state.unlockedZones.includes(zid) ? '#7b68ee' : '#333'};">
      <strong>${zid.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())}</strong>
      ${state.defeatedGuardians.includes(zid) ? '<span style="color:#4ade80;margin-left:8px;">✓ Cleared</span>' : ''}
    </div>`;
    }
    html += '</div><button class="action-btn-choice back-btn" style="margin-top:12px;">Back</button>';
    return html;
}
function showShrineMenu(state) {
    return `
    <h2>✦ Aether Shrine ✦</h2>
    <p style="color:#a09cb5;margin-bottom:16px;">Rest and restore your bond with the creatures.</p>
    <div style="display:flex;flex-direction:column;gap:8px;">
      <button class="action-btn-choice" data-shrine="heal">Heal Party</button>
      <button class="action-btn-choice" data-shrine="save">Save Game</button>
      <button class="action-btn-choice" data-shrine="shop">Shop</button>
      <button class="action-btn-choice" data-shrine="close">Leave</button>
    </div>`;
}
function showShopScreen(state) {
    let html = '<h2>✦ Shrine Shop ✦</h2><div style="display:flex;flex-direction:column;gap:6px;">';
    const shopItems = [
        { id: 'aether-net', price: 0 },
        { id: 'resonance-cage', price: 0 },
        { id: 'prism-binder', price: 0 },
        { id: 'aether-draught', price: 0 },
        { id: 'prism-elixir', price: 0 }
    ];
    for (const si of shopItems) {
        const item = zones_1.ITEMS[si.id];
        if (!item)
            continue;
        html += `<div style="display:flex;justify-content:space-between;align-items:center;padding:6px;background:rgba(255,255,255,0.05);border-radius:6px;">
      <div><strong>${item.name}</strong> <span style="color:#a09cb5;font-size:0.8rem;">${item.description}</span></div>
      <button class="action-btn-choice buy-btn" data-buy="${si.id}" style="padding:4px 12px;font-size:0.8rem;">Free</button>
    </div>`;
    }
    html += '</div><button class="action-btn-choice back-btn" style="margin-top:12px;">Back</button>';
    return html;
}
function showCaptureMiniGame() {
    return `
    <h2>Capture Attempt</h2>
    <p style="color:#a09cb5;margin-bottom:16px;">Tap rapidly to stabilize the creature!</p>
    <div id="capture-bar" style="width:100%;height:30px;background:rgba(255,255,255,0.1);border-radius:8px;overflow:hidden;margin-bottom:16px;">
      <div id="capture-fill" style="width:0%;height:100%;background:linear-gradient(to right,#7b68ee,#4ade80);transition:width 0.1s;"></div>
    </div>
    <button class="action-btn-choice capture-tap-btn" style="padding:12px 32px;font-size:1.1rem;">TAP!</button>`;
}
function showTutorialStep(step) {
    const steps = [
        'Welcome to Aetheris! Use WASD or arrow keys to move around the overworld.',
        'Walk into tall grass areas to encounter wild creatures in battle!',
        'In battle, choose abilities to weaken creatures, then attempt to capture them with an Aether Net.',
        'Visit shrines (glowing diamonds) to heal your party and save your progress.',
        'Defeat zone Guardians (red icons) to unlock new areas. Good luck, Binder!'
    ];
    return `
    <h2>✦ Tutorial ✦</h2>
    <p style="color:#a09cb5;margin-bottom:16px;line-height:1.6;">${steps[step] || steps[steps.length - 1]}</p>
    <div style="display:flex;gap:8px;">
      ${step > 0 ? '<button class="action-btn-choice" data-tut="back">Back</button>' : ''}
      ${step < steps.length - 1 ? '<button class="action-btn-choice" data-tut="next">Next</button>' : '<button class="action-btn-choice" data-tut="done">Begin Journey!</button>'}
    </div>`;
}
function showStarterChoice() {
    const starters = [
        { id: 'emberling', desc: 'Pyra affinity. High attack, moderate speed.' },
        { id: 'tidepup', desc: 'Aqua affinity. Balanced stats with good defense.' },
        { id: 'mossback', desc: 'Terra affinity. Tanky with high HP and defense.' }
    ];
    let html = '<h2>Choose Your First Companion</h2><div style="display:flex;flex-direction:column;gap:8px;">';
    starters.forEach(s => {
        const tmpl = (0, creatures_1.getCreatureTemplate)(s.id);
        html += `<button class="action-btn-choice starter-btn" data-starter="${s.id}" style="text-align:left;padding:12px;">
      <strong style="color:${types_1.AFFINITY_COLORS[tmpl.affinities[0]]}">${tmpl.name}</strong> — ${s.desc}<br>
      <span style="font-size:0.8rem;color:#a09cb5;">${tmpl.lore}</span>
    </button>`;
    });
    html += '</div>';
    return html;
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"typescript","sessionId":"08f5d7c6-9b4c-46cf-a814-cf6069aa2a40","tokensIn":5240287,"tokensOut":74337,"tokensTotal":5314624,"turns":84,"toolCalls":83,"failedToolCalls":2,"timestamp":"2026-07-30T18:32:47.766Z"} -->