← Creature Collection results

Creature Collection

Qwen 3.6 35b a3b · composer

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 123,776 bytes · SHA-256 a1601cbf7253
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aetherials: The Fractured Realm</title>
<style>
/* ── css: palette ── */
:root {
  --bg-dark: #0a0a1a;
  --bg-card: rgba(15, 15, 35, 0.85);
  --glass: rgba(255, 255, 255, 0.08);
  --glass-border: rgba(255, 255, 255, 0.15);
  --text: #e8e8f0;
  --text-dim: #8888aa;
  --accent: #6ee7b7;
  --accent2: #818cf8;
  --danger: #f87171;
  --warn: #fbbf24;
  --type-virel: #4ade80;
  --type-krell: #a78bfa;
  --type-thorne: #fb923c;
  --type-dusk: #8b5cf6;
  --type-flux: #06b6d4;
  --type-prism: #fde68a;
  --type-aegis: #60a5fa;
  --hp-green: #4ade80;
  --hp-yellow: #fbbf24;
  --hp-red: #f87171;
}
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: var(--bg-dark); color: var(--text); font-family: 'Segoe UI', system-ui, sans-serif; }
#app { width: 100%; height: 100%; position: relative; }
canvas { display: block; width: 100%; height: 100%; }

/* Title Screen */
#title-screen { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #0a0a2e 0%, #1a0a3e 50%, #0a1a2e 100%); z-index: 100; }
#title-screen h1 { font-size: 3.5em; background: linear-gradient(90deg, #6ee7b7, #818cf8, #fde68a); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: none; margin-bottom: 0.2em; letter-spacing: 0.05em; }
#title-screen .subtitle { color: var(--text-dim); font-size: 1.1em; margin-bottom: 2em; font-style: italic; }
.title-btn { padding: 14px 40px; margin: 8px; font-size: 1.1em; border: 1px solid var(--glass-border); border-radius: 12px; background: var(--glass); color: var(--text); cursor: pointer; backdrop-filter: blur(10px); transition: all 0.3s; }
.title-btn:hover { background: rgba(255,255,255,0.15); transform: scale(1.05); border-color: var(--accent); }
.title-btn:active { transform: scale(0.98); }

/* Overworld HUD */
#overworld-hud { position: absolute; top: 0; left: 0; right: 0; padding: 10px 15px; display: flex; justify-content: space-between; align-items: flex-start; pointer-events: none; z-index: 10; }
#overworld-hud > * { pointer-events: auto; }
.hud-zone { background: var(--glass); backdrop-filter: blur(8px); border: 1px solid var(--glass-border); border-radius: 10px; padding: 8px 16px; font-size: 0.9em; }
.hud-party { display: flex; gap: 6px; }
.party-dot { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--glass-border); display: flex; align-items: center; justify-content: center; font-size: 0.7em; font-weight: bold; }

/* Touch Controls */
#touch-controls { position: absolute; bottom: 15px; left: 15px; z-index: 20; display: none; }
.touch-btn { width: 50px; height: 50px; border-radius: 12px; border: 1px solid var(--glass-border); background: var(--glass); backdrop-filter: blur(4px); color: var(--text); font-size: 1.3em; display: flex; align-items: center; justify-content: center; cursor: pointer; user-select: none; -webkit-user-select: none; }
.touch-btn:active { background: rgba(255,255,255,0.2); }
.dpad { display: grid; grid-template-columns: repeat(3, 50px); grid-template-rows: repeat(3, 50px); gap: 4px; }
.dpad .empty { visibility: hidden; }
#touch-actions { position: absolute; bottom: 15px; right: 15px; display: none; gap: 8px; z-index: 20; }
.action-btn { width: 56px; height: 56px; border-radius: 50%; border: 2px solid var(--glass-border); background: var(--glass); backdrop-filter: blur(4px); color: var(--text); font-size: 0.8em; font-weight: bold; display: flex; align-items: center; justify-content: center; cursor: pointer; user-select: none; }
.action-btn:active { background: rgba(255,255,255,0.2); }

/* Battle Scene */
#battle-scene { position: absolute; inset: 0; display: none; z-index: 50; }
#battle-canvas { width: 100%; height: 100%; }
#battle-ui { position: absolute; bottom: 0; left: 0; right: 0; background: var(--bg-card); border-top: 1px solid var(--glass-border); backdrop-filter: blur(12px); padding: 12px 16px; min-height: 200px; }
#battle-text { font-size: 1em; margin-bottom: 8px; min-height: 1.4em; line-height: 1.4; }
#battle-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.battle-btn { padding: 8px 16px; border: 1px solid var(--glass-border); border-radius: 8px; background: var(--glass); color: var(--text); cursor: pointer; font-size: 0.9em; transition: all 0.2s; }
.battle-btn:hover { background: rgba(255,255,255,0.15); }
.battle-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.battle-btn.ability-btn { min-width: 100px; }
.battle-btn.ability-btn.type-virel { border-color: var(--type-virel); color: var(--type-virel); }
.battle-btn.ability-btn.type-krell { border-color: var(--type-krell); color: var(--type-krell); }
.battle-btn.ability-btn.type-thorne { border-color: var(--type-thorne); color: var(--type-thorne); }
.battle-btn.ability-btn.type-dusk { border-color: var(--type-dusk); color: var(--type-dusk); }
.battle-btn.ability-btn.type-flux { border-color: var(--type-flux); color: var(--type-flux); }
.battle-btn.ability-btn.type-prism { border-color: var(--type-prism); color: var(--type-prism); }
.battle-btn.ability-btn.type-aegis { border-color: var(--type-aegis); color: var(--type-aegis); }

/* Battle creature display */
#battle-enemies { position: absolute; top: 20px; right: 40px; display: flex; gap: 30px; }
.battle-creature-display { background: var(--glass); backdrop-filter: blur(8px); border: 1px solid var(--glass-border); border-radius: 12px; padding: 10px 14px; min-width: 160px; }
.battle-creature-display .name { font-size: 0.85em; font-weight: bold; margin-bottom: 4px; }
.battle-creature-display .level { font-size: 0.7em; color: var(--text-dim); }
.hp-bar-container { width: 100%; height: 8px; background: rgba(0,0,0,0.4); border-radius: 4px; overflow: hidden; margin-top: 4px; }
.hp-bar { height: 100%; border-radius: 4px; transition: width 0.5s ease; }
.hp-bar.green { background: var(--hp-green); }
.hp-bar.yellow { background: var(--hp-yellow); }
.hp-bar.red { background: var(--hp-red); }
.hp-text { font-size: 0.7em; color: var(--text-dim); margin-top: 2px; }
.status-icons { display: flex; gap: 3px; margin-top: 3px; }
.status-icon { font-size: 0.65em; padding: 1px 4px; border-radius: 3px; background: rgba(255,255,255,0.1); }

/* Floating damage numbers */
.damage-float { position: absolute; font-weight: bold; font-size: 1.4em; pointer-events: none; animation: floatUp 1s ease-out forwards; z-index: 60; }
.damage-float.crit { font-size: 1.8em; }
@keyframes floatUp { 0% { opacity: 1; transform: translateY(0) scale(1); } 100% { opacity: 0; transform: translateY(-60px) scale(0.7); } }

/* Modals */
.modal-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 80; backdrop-filter: blur(4px); }
.modal { background: var(--bg-card); border: 1px solid var(--glass-border); border-radius: 16px; padding: 24px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; backdrop-filter: blur(16px); }
.modal h2 { font-size: 1.4em; margin-bottom: 12px; background: linear-gradient(90deg, var(--accent), var(--accent2)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.modal-close { position: absolute; top: 12px; right: 16px; background: none; border: none; color: var(--text-dim); font-size: 1.5em; cursor: pointer; }
.modal-close:hover { color: var(--text); }

/* Encyclopedia */
.encyc-entry { display: flex; gap: 12px; padding: 10px; border-bottom: 1px solid var(--glass-border); align-items: center; }
.encyc-entry .icon { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1.2em; }
.encyc-entry .info { flex: 1; }
.encyc-entry .info .name { font-weight: bold; font-size: 0.95em; }
.encyc-entry .info .detail { font-size: 0.75em; color: var(--text-dim); }

/* Story vignette */
#story-vignette { position: absolute; inset: 0; display: none; z-index: 90; flex-direction: column; align-items: center; justify-content: center; padding: 40px; }
#story-vignette .vignette-text { font-size: 1.3em; line-height: 1.6; text-align: center; max-width: 600px; opacity: 0; animation: vignetteFade 2s ease forwards; }
#story-vignette .vignette-skip { margin-top: 30px; color: var(--text-dim); font-size: 0.85em; opacity: 0; animation: vignetteFade 1s ease 3s forwards; }
@keyframes vignetteFade { 0% { opacity: 0; } 100% { opacity: 1; } }

/* Screen shake */
.shake { animation: screenShake 0.3s ease; }
@keyframes screenShake { 0%, 100% { transform: translate(0); } 20% { transform: translate(-4px, 2px); } 40% { transform: translate(4px, -2px); } 60% { transform: translate(-2px, 4px); } 80% { transform: translate(2px, -4px); } }

/* Capture mini-game */
#capture-game { position: absolute; inset: 0; display: none; z-index: 70; background: rgba(0,0,0,0.7); flex-direction: column; align-items: center; justify-content: center; }
.capture-ring { width: 120px; height: 120px; border-radius: 50%; border: 4px solid var(--glass-border); position: relative; display: flex; align-items: center; justify-content: center; }
.capture-indicator { position: absolute; width: 100%; height: 100%; border-radius: 50%; border: 4px solid transparent; border-top-color: var(--accent); animation: captureSpin 1s linear infinite; }
.capture-zone { position: absolute; width: 100%; height: 100%; border-radius: 50%; border: 4px solid transparent; }
@keyframes captureSpin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.capture-btn { padding: 16px 48px; font-size: 1.2em; border-radius: 12px; border: 2px solid var(--accent); background: var(--glass); color: var(--accent); cursor: pointer; margin-top: 20px; }
.capture-btn:hover { background: rgba(110, 231, 183, 0.2); }

/* Shrine overlay */
#shrine-overlay { position: absolute; inset: 0; display: none; z-index: 75; background: rgba(0,0,0,0.5); flex-direction: column; align-items: center; justify-content: center; }
.shrine-panel { background: var(--bg-card); border: 1px solid var(--glass-border); border-radius: 16px; padding: 24px; text-align: center; backdrop-filter: blur(16px); }
.shrine-panel h2 { font-size: 1.6em; margin-bottom: 16px; color: var(--warn); }
.shrine-btn { padding: 10px 24px; margin: 6px; border: 1px solid var(--glass-border); border-radius: 8px; background: var(--glass); color: var(--text); cursor: pointer; font-size: 0.95em; }
.shrine-btn:hover { background: rgba(255,255,255,0.15); }

/* Pause menu */
#pause-menu { position: absolute; inset: 0; display: none; z-index: 85; background: rgba(0,0,0,0.7); flex-direction: column; align-items: center; justify-content: center; }
#pause-menu .modal { text-align: center; }
#pause-menu .pause-btn { padding: 12px 32px; margin: 8px; border: 1px solid var(--glass-border); border-radius: 10px; background: var(--glass); color: var(--text); cursor: pointer; font-size: 1em; }
#pause-menu .pause-btn:hover { background: rgba(255,255,255,0.15); }

/* Inventory */
.inv-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border-bottom: 1px solid var(--glass-border); }
.inv-item .item-name { font-weight: bold; font-size: 0.9em; }
.inv-item .item-count { color: var(--text-dim); font-size: 0.8em; }
.inv-item .item-btn { padding: 4px 12px; border-radius: 6px; border: 1px solid var(--glass-border); background: var(--glass); color: var(--text); cursor: pointer; font-size: 0.8em; }
.inv-item .item-btn:hover { background: rgba(255,255,255,0.15); }

/* Tab navigation */
.tab-bar { display: flex; gap: 4px; margin-bottom: 12px; border-bottom: 1px solid var(--glass-border); padding-bottom: 8px; }
.tab-btn { padding: 6px 14px; border: none; border-radius: 6px; background: var(--glass); color: var(--text-dim); cursor: pointer; font-size: 0.85em; }
.tab-btn.active { background: rgba(255,255,255,0.15); color: var(--text); }

/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--glass-border); border-radius: 3px; }

/* Responsive */
@media (max-width: 600px) {
  #title-screen h1 { font-size: 2.2em; }
  .modal { padding: 16px; width: 95%; }
  #battle-ui { min-height: 180px; padding: 8px 10px; }
  .battle-btn { padding: 6px 10px; font-size: 0.8em; }
}
/* ── css: animations ── */
@keyframes pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }
@keyframes glow { 0%, 100% { box-shadow: 0 0 8px rgba(110, 231, 183, 0.3); } 50% { box-shadow: 0 0 20px rgba(110, 231, 183, 0.6); } }
@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
@keyframes enemyLunge { 0% { transform: scale(1); } 30% { transform: scale(1.1) translateX(-10px); } 100% { transform: scale(1); } }
@keyframes enemyHit { 0% { transform: translateX(0); filter: brightness(1); } 25% { transform: translateX(8px); filter: brightness(2); } 50% { transform: translateX(-4px); filter: brightness(1.5); } 100% { transform: translateX(0); filter: brightness(1); } }
@keyframes playerLunge { 0% { transform: scale(1); } 30% { transform: scale(1.1) translateX(10px); } 100% { transform: scale(1); } }
@keyframes playerHit { 0% { transform: translateX(0); filter: brightness(1); } 25% { transform: translateX(-8px); filter: brightness(2); } 50% { transform: translateX(4px); filter: brightness(1.5); } 100% { transform: translateX(0); filter: brightness(1); } }
@keyframes captureFlash { 0% { opacity: 0; transform: scale(0.5); } 50% { opacity: 1; transform: scale(1.2); } 100% { opacity: 0; transform: scale(1.5); } }
@keyframes particleFade { 0% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-30px); } }
.pulse { animation: pulse 2s ease infinite; }
.glow { animation: glow 3s ease infinite; }
/* ── css: capture_anim ── */
@keyframes captureSpin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
@keyframes vignetteFade { 0% { opacity: 0; transform: translateY(10px); } 100% { opacity: 1; transform: translateY(0); } }
@keyframes screenShake { 0%, 100% { transform: translate(0) rotate(0); } 10% { transform: translate(-4px, 2px) rotate(-0.5deg); } 20% { transform: translate(4px, -2px) rotate(0.5deg); } 30% { transform: translate(-2px, 4px) rotate(-0.3deg); } 40% { transform: translate(2px, -4px) rotate(0.3deg); } 50% { transform: translate(-3px, 1px) rotate(-0.2deg); } 60% { transform: translate(3px, -1px) rotate(0.2deg); } 70% { transform: translate(-1px, 3px) rotate(-0.1deg); } 80% { transform: translate(1px, -3px) rotate(0.1deg); } 90% { transform: translate(0, 0); } }
.shake { animation: screenShake 0.3s ease; }
.capture-indicator { animation: captureSpin 1s linear infinite; transform-origin: center; }
.vignette-text { animation: vignetteFade 2s ease forwards; }
.vignette-skip { animation: vignetteFade 1s ease 3s forwards; }

#title-screen { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #0a0a2e 0%, #1a0a3e 50%, #0a1a2e 100%); z-index: 100; }
#title-screen h1 { font-size: 3.5em; background: linear-gradient(90deg, #6ee7b7, #818cf8, #fde68a); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 0.2em; letter-spacing: 0.05em; }
#title-screen .subtitle { color: #8888aa; font-size: 1.1em; margin-bottom: 2em; font-style: italic; }
.title-btn { padding: 14px 40px; margin: 8px; font-size: 1.1em; border: 1px solid rgba(255,255,255,0.15); border-radius: 12px; background: rgba(255,255,255,0.08); color: #e8e8f0; cursor: pointer; backdrop-filter: blur(10px); transition: all 0.3s; }
.title-btn:hover { background: rgba(255,255,255,0.15); transform: scale(1.05); border-color: #6ee7b7; }
.title-btn:active { transform: scale(0.98); }

#overworld-hud { position: absolute; top: 0; left: 0; right: 0; padding: 10px 15px; display: flex; justify-content: space-between; align-items: flex-start; pointer-events: none; z-index: 10; }
#overworld-hud > * { pointer-events: auto; }
.hud-zone { background: rgba(15,15,35,0.85); backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.15); border-radius: 10px; padding: 8px 16px; font-size: 0.9em; }
.hud-party { display: flex; gap: 6px; }
.party-dot { width: 28px; height: 28px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.15); display: flex; align-items: center; justify-content: center; font-size: 0.7em; font-weight: bold; }

.battle-btn { padding: 8px 16px; border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; background: rgba(255,255,255,0.08); color: #e8e8f0; cursor: pointer; font-size: 0.9em; transition: all 0.2s; }
.battle-btn:hover { background: rgba(255,255,255,0.15); }
.battle-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.battle-btn.ability-btn { min-width: 100px; }
.battle-btn.ability-btn.type-virel { border-color: #4ade80; color: #4ade80; }
.battle-btn.ability-btn.type-krell { border-color: #a78bfa; color: #a78bfa; }
.battle-btn.ability-btn.type-thorne { border-color: #fb923c; color: #fb923c; }
.battle-btn.ability-btn.type-dusk { border-color: #8b5cf6; color: #8b5cf6; }
.battle-btn.ability-btn.type-flux { border-color: #06b6d4; color: #06b6d4; }
.battle-btn.ability-btn.type-prism { border-color: #fde68a; color: #fde68a; }
.battle-btn.ability-btn.type-aegis { border-color: #60a5fa; color: #60a5fa; }

.modal-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 80; backdrop-filter: blur(4px); }
.modal { background: rgba(15,15,35,0.95); border: 1px solid rgba(255,255,255,0.15); border-radius: 16px; padding: 24px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; backdrop-filter: blur(16px); }
.modal h2 { font-size: 1.4em; margin-bottom: 12px; background: linear-gradient(90deg, #6ee7b7, #818cf8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.pause-btn { padding: 12px 32px; margin: 8px; border: 1px solid rgba(255,255,255,0.15); border-radius: 10px; background: rgba(255,255,255,0.08); color: #e8e8f0; cursor: pointer; font-size: 1em; }
.pause-btn:hover { background: rgba(255,255,255,0.15); }
.shrine-btn { padding: 10px 24px; margin: 6px; border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; background: rgba(255,255,255,0.08); color: #e8e8f0; cursor: pointer; font-size: 0.95em; }
.shrine-btn:hover { background: rgba(255,255,255,0.15); }
.inv-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.1); }
.inv-item .item-name { font-weight: bold; font-size: 0.9em; }
.inv-item .item-count { color: #8888aa; font-size: 0.8em; }
.inv-item .item-btn { padding: 4px 12px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.08); color: #e8e8f0; cursor: pointer; font-size: 0.8em; }
.inv-item .item-btn:hover { background: rgba(255,255,255,0.15); }
.encyc-entry { display: flex; gap: 12px; padding: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); align-items: center; }
.encyc-entry .icon { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1.2em; }
.encyc-entry .info { flex: 1; }
.encyc-entry .info .name { font-weight: bold; font-size: 0.95em; }
.encyc-entry .info .detail { font-size: 0.75em; color: #8888aa; }

.capture-ring { width: 120px; height: 120px; border-radius: 50%; border: 4px solid rgba(255,255,255,0.15); position: relative; display: flex; align-items: center; justify-content: center; }
.capture-zone { position: absolute; width: 100%; height: 100%; border-radius: 50%; border: 4px solid transparent; }
.capture-btn { padding: 16px 48px; font-size: 1.2em; border-radius: 12px; border: 2px solid #6ee7b7; background: rgba(255,255,255,0.08); color: #6ee7b7; cursor: pointer; margin-top: 20px; }
.capture-btn:hover { background: rgba(110,231,183,0.2); }

.dpad { display: grid; grid-template-columns: repeat(3, 50px); grid-template-rows: repeat(3, 50px); gap: 4px; }
.dpad .empty { visibility: hidden; }
.touch-btn { width: 50px; height: 50px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.08); color: #e8e8f0; font-size: 1.3em; display: flex; align-items: center; justify-content: center; cursor: pointer; user-select: none; -webkit-user-select: none; }
.touch-btn:active { background: rgba(255,255,255,0.2); }
.action-btn { width: 56px; height: 56px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.08); color: #e8e8f0; font-size: 0.8em; font-weight: bold; display: flex; align-items: center; justify-content: center; cursor: pointer; user-select: none; }
.action-btn:active { background: rgba(255,255,255,0.2); }

::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 3px; }

@media (max-width: 600px) {
  #title-screen h1 { font-size: 2.2em; }
  .modal { padding: 16px; width: 95%; }
  #battle-ui { min-height: 180px; padding: 8px 10px; }
  .battle-btn { padding: 6px 10px; font-size: 0.8em; }
}
</style>
</head>
<body>
<!-- Title Screen -->
<div id="title-screen">
  <canvas id="title-canvas" style="position:absolute;inset:0;z-index:0;"></canvas>
  <div style="position:relative;z-index:1;text-align:center;">
    <h1>AETHERIALS</h1>
    <div class="subtitle">The Fractured Realm</div>
    <button id="new-binding" class="title-btn">New Binding</button>
    <button id="continue-btn" class="title-btn" style="display:none;">Continue</button>
  </div>
</div>

<!-- Overworld -->
<div id="overworld-hud" style="display:none;">
  <div class="hud-zone" id="hud-zone-name">Verdant Hollow</div>
  <div class="hud-party" id="hud-party"></div>
  <div style="display:flex;gap:4px;">
    <button class="battle-btn" onclick="showPauseMenu()" style="padding:4px 10px;font-size:0.8em;">⏸</button>
    <button class="battle-btn" onclick="showEncyclopedia()" style="padding:4px 10px;font-size:0.8em;">📖</button>
    <button class="battle-btn" onclick="toggleMute()" style="padding:4px 10px;font-size:0.8em;" id="mute-btn">🔊</button>
  </div>
</div>
<canvas id="overworld-canvas" style="display:none;"></canvas>

<!-- Touch Controls -->
<div id="touch-controls" style="display:none;">
  <div class="dpad">
    <div class="empty"></div>
    <button class="touch-btn" id="btn-up">▲</button>
    <div class="empty"></div>
    <button class="touch-btn" id="btn-left">◀</button>
    <div class="empty"></div>
    <button class="touch-btn" id="btn-right">▶</button>
    <div class="empty"></div>
    <button class="touch-btn" id="btn-down">▼</button>
    <div class="empty"></div>
  </div>
</div>
<div id="touch-actions" style="display:none;">
  <button class="action-btn" onclick="showPauseMenu()">⏸</button>
  <button class="action-btn" onclick="showEncyclopedia()">📖</button>
</div>

<!-- Battle Scene -->
<div id="battle-scene" style="display:none;">
  <canvas id="battle-canvas" style="width:100%;height:100%;"></canvas>
  <div id="battle-ui">
    <div id="battle-text" style="font-size:1em;margin-bottom:8px;min-height:1.4em;line-height:1.4;color:#e8e8f0;"></div>
    <div id="battle-actions" style="display:flex;gap:8px;flex-wrap:wrap;"></div>
  </div>
</div>

<!-- Capture Mini-game -->
<div id="capture-game" style="display:none;position:absolute;inset:0;z-index:70;background:rgba(0,0,0,0.7);flex-direction:column;align-items:center;justify-content:center;">
  <div style="color:#e8e8f0;font-size:1.2em;margin-bottom:20px;">Tap when the ring aligns!</div>
  <div class="capture-ring">
    <div class="capture-indicator" id="capture-indicator"></div>
    <div class="capture-zone" id="capture-zone" style="border-color:transparent;"></div>
    <div style="font-size:2em;" id="capture-creature-emoji">🐾</div>
  </div>
  <button class="capture-btn" id="capture-tap-btn" onclick="tapCapture()">TAP!</button>
  <div id="capture-result" style="margin-top:16px;font-size:1em;color:#e8e8f0;"></div>
</div>

<!-- Shrine Overlay -->
<div id="shrine-overlay" style="display:none;position:absolute;inset:0;z-index:75;background:rgba(0,0,0,0.5);flex-direction:column;align-items:center;justify-content:center;">
  <div class="shrine-panel" id="shrine-panel" style="text-align:center;">
    <h2>⛩️ Aether Shrine</h2>
    <p style="color:var(--text-dim);margin-bottom:16px;">The shrine hums with Aetherium energy.</p>
    <button class="shrine-btn" onclick="shrineHeal()">💚 Heal Party</button>
    <button class="shrine-btn" onclick="shrineSave()">💾 Save</button>
    <button class="shrine-btn" onclick="shrineShop()">🛒 Shop</button>
    <button class="shrine-btn" onclick="shrineClose()">🚪 Leave</button>
  </div>
</div>

<!-- Story Vignette -->
<div id="story-vignette" style="display:none;position:absolute;inset:0;z-index:90;flex-direction:column;align-items:center;justify-content:center;padding:40px;">
  <div class="vignette-text" id="vignette-text" style="font-size:1.3em;line-height:1.6;text-align:center;max-width:600px;opacity:0;animation:vignetteFade 2s ease forwards;"></div>
  <div class="vignette-skip" style="margin-top:30px;color:var(--text-dim);font-size:0.85em;opacity:0;animation:vignetteFade 1s ease 3s forwards;">Tap to skip</div>
</div>

<!-- Pause Menu -->
<div id="pause-menu" style="display:none;position:absolute;inset:0;z-index:85;background:rgba(0,0,0,0.7);flex-direction:column;align-items:center;justify-content:center;">
  <div class="modal" style="text-align:center;">
    <h2>⏸️ Paused</h2>
    <button class="pause-btn" onclick="hideModal();game.screen='overworld';showScreen('overworld');">▶️ Resume</button>
    <button class="pause-btn" onclick="showEncyclopedia();">📖 Codex</button>
    <button class="pause-btn" onclick="showPartyScreen();">👥 Party</button>
    <button class="pause-btn" onclick="showInventoryScreen();">🎒 Inventory</button>
    <button class="pause-btn" onclick="saveGame(1);hideModal();">💾 Save</button>
    <button class="pause-btn" onclick="toggleMute();showPauseMenu();">🔊 Audio</button>
    <button class="pause-btn" onclick="hideModal();showScreen('title');">🏠 Title</button>
  </div>
</div>
<script>
'use strict';

// ── const: TYPES ──
// ===== WORLD TYPE SYSTEM =====
const TYPES = ['virel','krell','thorne','dusk','flux','prism','aegis'];

// ── const: TYPE_COLORS ──
const TYPE_COLORS = { virel:'#4ade80', krell:'#a78bfa', thorne:'#fb923c', dusk:'#8b5cf6', flux:'#06b6d4', prism:'#fde68a', aegis:'#60a5fa' };

// ── const: TYPE_NAMES ──
const TYPE_NAMES = { virel:'Virel', krell:'Krell', thorne:'Thorne', dusk:'Dusk', flux:'Flux', prism:'Prism', aegis:'Aegis' };

// ── const: TYPE_EMOJI ──
const TYPE_EMOJI = { virel:'🌿', krell:'🪨', thorne:'🌵', dusk:'🌑', flux:'⚡', prism:'💎', aegis:'🛡️' };

// ── const: TYPE_CHART ──
// Type effectiveness chart
// virel > krell > thorne > virel
// dusk > virel & thorne
// flux > dusk & krell
// prism > all slightly
// aegis: resists all, weak to prism
const TYPE_CHART = {
  virel: { krell: 1.5, thorne: 0.5, virel: 1.0, dusk: 1.0, flux: 1.0, prism: 0.8, aegis: 1.0 },
  krell: { thorne: 1.5, virel: 0.5, krell: 1.0, dusk: 1.0, flux: 0.5, prism: 0.8, aegis: 1.0 },
  thorne: { virel: 1.5, krell: 0.5, thorne: 1.0, dusk: 0.5, flux: 1.0, prism: 0.8, aegis: 1.0 },
  dusk: { virel: 1.5, thorne: 1.5, dusk: 1.0, krell: 1.0, flux: 0.5, prism: 0.8, aegis: 1.0 },
  flux: { dusk: 1.5, krell: 1.5, flux: 1.0, virel: 1.0, thorne: 1.0, prism: 0.8, aegis: 1.0 },
  prism: { virel: 1.3, krell: 1.3, thorne: 1.3, dusk: 1.3, flux: 1.3, prism: 1.0, aegis: 1.3 },
  aegis: { virel: 0.7, krell: 0.7, thorne: 0.7, dusk: 0.7, flux: 0.7, prism: 1.5, aegis: 1.0 }
};

// ── const: CREATURES ──
// ===== CREATURE DATABASE =====
const CREATURES = {
  emberlit: {
    name: 'Emberlit', emoji: '🔥', affinity: 'virel',
    baseStats: { hp: 45, atk: 38, def: 22, spd: 35 },
    lore: 'A small flame spirit born from the first warmth of the Aetherium.',
    growth: { hp: 8, atk: 7, def: 4, spd: 5 },
    abilities: ['scorch', 'ember_blast', 'flame_wisp', 'blaze_fury'],
    evolve: { to: 'pyraxis', atLevel: 20, catalyst: 'ember_crystal' },
    captureRate: 180, encounterLevel: [1, 4]
  },
  stonewisp: {
    name: 'Stonewisp', emoji: '🪨', affinity: 'krell',
    baseStats: { hp: 50, atk: 30, def: 35, spd: 20 },
    lore: 'A patient rock sprite that shapes itself over centuries of quiet thought.',
    growth: { hp: 9, atk: 5, def: 6, spd: 3 },
    abilities: ['rock_throw', 'stone_wall', 'quarry_strike', 'earth_shield'],
    evolve: { to: 'lithos', atLevel: 20, catalyst: 'stone_fragment' },
    captureRate: 170, encounterLevel: [2, 5]
  },
  thornling: {
    name: 'Thornling', emoji: '🌵', affinity: 'thorne',
    baseStats: { hp: 42, atk: 40, def: 20, spd: 38 },
    lore: 'A restless vine creature that lashes out when its territory is threatened.',
    growth: { hp: 7, atk: 8, def: 3, spd: 6 },
    abilities: ['vine_whip', 'thorn_barrage', 'suffocate', 'bloom_strike'],
    evolve: { to: 'briarthorn', atLevel: 20, catalyst: 'thorn_seed' },
    captureRate: 160, encounterLevel: [3, 6]
  },
  pyraxis: {
    name: 'Pyraxis', emoji: '🔥', affinity: 'virel',
    baseStats: { hp: 65, atk: 55, def: 35, spd: 48 },
    lore: 'The fully awakened flame spirit, its body burns with the memory of a thousand suns.',
    growth: { hp: 10, atk: 9, def: 5, spd: 6 },
    abilities: ['scorch', 'ember_blast', 'flame_wisp', 'blaze_fury'],
    evolve: null, captureRate: 60, encounterLevel: [25, 35]
  },
  lithos: {
    name: 'Lithos', emoji: '🏔️', affinity: 'krell',
    baseStats: { hp: 80, atk: 48, def: 55, spd: 30 },
    lore: 'A living mountain that walks the Aetherium\'s ridges with ancient purpose.',
    growth: { hp: 12, atk: 7, def: 8, spd: 4 },
    abilities: ['rock_throw', 'stone_wall', 'quarry_strike', 'earth_shield'],
    evolve: null, captureRate: 55, encounterLevel: [25, 35]
  },
  briarthorn: {
    name: 'Briarthorn', emoji: '🌹', affinity: 'thorne',
    baseStats: { hp: 60, atk: 62, def: 32, spd: 52 },
    lore: 'A magnificent but deadly thorned bloom that feeds on the Aetherium\'s energy.',
    growth: { hp: 9, atk: 10, def: 4, spd: 7 },
    abilities: ['vine_whip', 'thorn_barrage', 'suffocate', 'bloom_strike'],
    evolve: null, captureRate: 50, encounterLevel: [25, 35]
  },
  dusk_moth: {
    name: 'Dusk Moth', emoji: '🦋', affinity: 'dusk',
    baseStats: { hp: 40, atk: 35, def: 18, spd: 42 },
    lore: 'A shadow-winged moth that feeds on the fading light between zones.',
    growth: { hp: 7, atk: 6, def: 3, spd: 6 },
    abilities: ['shadow_wing', 'void_dust', 'nightmare', 'gloom_burst'],
    evolve: { to: 'umbrafly', atLevel: 22, catalyst: 'shadow_wing' },
    captureRate: 150, encounterLevel: [5, 10]
  },
  umbrafly: {
    name: 'Umbrafly', emoji: '🌑', affinity: 'dusk',
    baseStats: { hp: 58, atk: 52, def: 28, spd: 58 },
    lore: 'The evolved shadow-fly whose wings can swallow light itself.',
    growth: { hp: 9, atk: 8, def: 4, spd: 7 },
    abilities: ['shadow_wing', 'void_dust', 'nightmare', 'gloom_burst'],
    evolve: null, captureRate: 55, encounterLevel: [28, 38]
  },
  flux_spark: {
    name: 'Flux Spark', emoji: '⚡', affinity: 'flux',
    baseStats: { hp: 38, atk: 42, def: 15, spd: 45 },
    lore: 'A crackling energy sprite that dances between the Aetherium\'s ley lines.',
    growth: { hp: 6, atk: 7, def: 3, spd: 6 },
    abilities: ['spark_bolt', 'overload', 'chain_lightning', 'surge'],
    evolve: { to: 'voltex', atLevel: 22, catalyst: 'lightning_core' },
    captureRate: 140, encounterLevel: [6, 12]
  },
  voltex: {
    name: 'Voltex', emoji: '🌩️', affinity: 'flux',
    baseStats: { hp: 55, atk: 60, def: 25, spd: 62 },
    lore: 'A living storm that commands the Aetherium\'s electrical currents.',
    growth: { hp: 8, atk: 9, def: 4, spd: 8 },
    abilities: ['spark_bolt', 'overload', 'chain_lightning', 'surge'],
    evolve: null, captureRate: 50, encounterLevel: [30, 40]
  },
  prism_fae: {
    name: 'Prism Fae', emoji: '💎', affinity: 'prism',
    baseStats: { hp: 44, atk: 36, def: 24, spd: 40 },
    lore: 'A crystalline fairy that refracts the Aetherium\'s light into living color.',
    growth: { hp: 7, atk: 6, def: 4, spd: 5 },
    abilities: ['prism_ray', 'refraction', 'luster_burst', 'prismatic_heal'],
    evolve: { to: 'crystalla', atLevel: 24, catalyst: 'prism_shard' },
    captureRate: 130, encounterLevel: [8, 15]
  },
  crystalla: {
    name: 'Crystalla', emoji: '✨', affinity: 'prism',
    baseStats: { hp: 62, atk: 52, def: 38, spd: 55 },
    lore: 'The apex crystal fairy whose body becomes a living prism of pure Aetherium.',
    growth: { hp: 9, atk: 8, def: 5, spd: 7 },
    abilities: ['prism_ray', 'refraction', 'luster_burst', 'prismatic_heal'],
    evolve: null, captureRate: 45, encounterLevel: [32, 42]
  },
  aegis_knight: {
    name: 'Aegis Knight', emoji: '🛡️', affinity: 'aegis',
    baseStats: { hp: 55, atk: 32, def: 42, spd: 25 },
    lore: 'An ancient guardian construct that patrols the boundaries of the Aetherium.',
    growth: { hp: 9, atk: 5, def: 6, spd: 3 },
    abilities: ['shield_bash', 'iron_wall', 'guardian_strike', 'fortify'],
    evolve: { to: 'sentinel', atLevel: 24, catalyst: 'aegis_core' },
    captureRate: 120, encounterLevel: [10, 18]
  },
  sentinel: {
    name: 'Sentinel', emoji: '⚔️', affinity: 'aegis',
    baseStats: { hp: 78, atk: 48, def: 62, spd: 38 },
    lore: 'The ultimate guardian, its shield can deflect even the Aetherium\'s raw power.',
    growth: { hp: 11, atk: 7, def: 8, spd: 5 },
    abilities: ['shield_bash', 'iron_wall', 'guardian_strike', 'fortify'],
    evolve: null, captureRate: 40, encounterLevel: [35, 45]
  },
  aether_worm: {
    name: 'Aether Worm', emoji: '🐛', affinity: 'virel',
    baseStats: { hp: 48, atk: 28, def: 30, spd: 22 },
    lore: 'A burrowing creature that feeds on the Aetherium\'s energy deposits.',
    growth: { hp: 8, atk: 5, def: 5, spd: 3 },
    abilities: ['burrow', 'energy_drain', 'aether_bite', 'dig_down'],
    evolve: { to: 'aether_drake', atLevel: 26, catalyst: 'aether_heart' },
    captureRate: 160, encounterLevel: [10, 20]
  },
  aether_drake: {
    name: 'Aether Drake', emoji: '🐉', affinity: 'virel',
    baseStats: { hp: 72, atk: 50, def: 45, spd: 35 },
    lore: 'A majestic drake born from the Aetherium\'s deepest energy currents.',
    growth: { hp: 10, atk: 8, def: 6, spd: 5 },
    abilities: ['burrow', 'energy_drain', 'aether_bite', 'dig_down'],
    evolve: null, captureRate: 40, encounterLevel: [38, 48]
  },
  glow_moss: {
    name: 'Glow Moss', emoji: '🌱', affinity: 'virel',
    baseStats: { hp: 52, atk: 25, def: 28, spd: 18 },
    lore: 'A sentient moss that glows with the Aetherium\'s gentle warmth.',
    growth: { hp: 8, atk: 4, def: 4, spd: 2 },
    abilities: ['spore_cloud', 'regenerate', 'moss_bond', 'photosynthesis'],
    evolve: { to: 'moss_giant', atLevel: 26, catalyst: 'glow_cap' },
    captureRate: 150, encounterLevel: [12, 22]
  },
  moss_giant: {
    name: 'Moss Giant', emoji: '🌳', affinity: 'virel',
    baseStats: { hp: 85, atk: 42, def: 48, spd: 28 },
    lore: 'A towering forest spirit whose roots reach the Aetherium\'s core.',
    growth: { hp: 11, atk: 6, def: 7, spd: 4 },
    abilities: ['spore_cloud', 'regenerate', 'moss_bond', 'photosynthesis'],
    evolve: null, captureRate: 40, encounterLevel: [40, 50]
  },
  void_fox: {
    name: 'Void Fox', emoji: '🦊', affinity: 'dusk',
    baseStats: { hp: 42, atk: 44, def: 16, spd: 48 },
    lore: 'A sly shadow-fox that slips between the Aetherium\'s zones unnoticed.',
    growth: { hp: 7, atk: 7, def: 3, spd: 6 },
    abilities: ['shadow_pounce', 'void_step', 'dark_bite', 'phantom_fade'],
    evolve: { to: 'nether_wolf', atLevel: 28, catalyst: 'void_fang' },
    captureRate: 130, encounterLevel: [14, 24]
  },
  nether_wolf: {
    name: 'Nether Wolf', emoji: '🐺', affinity: 'dusk',
    baseStats: { hp: 62, atk: 62, def: 28, spd: 62 },
    lore: 'A pack hunter of the shadows, its howl echoes through the Aetherium.',
    growth: { hp: 9, atk: 9, def: 4, spd: 8 },
    abilities: ['shadow_pounce', 'void_step', 'dark_bite', 'phantom_fade'],
    evolve: null, captureRate: 35, encounterLevel: [42, 50]
  },
  crystal_slug: {
    name: 'Crystal Slug', emoji: '🐌', affinity: 'krell',
    baseStats: { hp: 46, atk: 22, def: 38, spd: 12 },
    lore: 'A slow-moving slug that secretes crystalline armor from Aetherium minerals.',
    growth: { hp: 8, atk: 3, def: 5, spd: 2 },
    abilities: ['crystal_shell', 'acid_trail', 'mineral_bite', 'harden'],
    evolve: { to: 'gem_shield', atLevel: 28, catalyst: 'crystal_gel' },
    captureRate: 140, encounterLevel: [15, 25]
  },
  gem_shield: {
    name: 'Gem Shield', emoji: '💠', affinity: 'krell',
    baseStats: { hp: 70, atk: 38, def: 58, spd: 22 },
    lore: 'A living gem that hardens its shell with layers of Aetherium crystals.',
    growth: { hp: 10, atk: 5, def: 7, spd: 3 },
    abilities: ['crystal_shell', 'acid_trail', 'mineral_bite', 'harden'],
    evolve: null, captureRate: 35, encounterLevel: [44, 50]
  },
  storm_jelly: {
    name: 'Storm Jelly', emoji: '🪼', affinity: 'flux',
    baseStats: { hp: 36, atk: 38, def: 14, spd: 44 },
    lore: 'A translucent jellyfish that conducts Aetherium electricity through its tentacles.',
    growth: { hp: 6, atk: 6, def: 3, spd: 6 },
    abilities: ['electro_shock', 'tentacle_whip', 'static_burst', 'jelly_wrap'],
    evolve: { to: 'tempest_ray', atLevel: 30, catalyst: 'storm_heart' },
    captureRate: 120, encounterLevel: [18, 28]
  },
  tempest_ray: {
    name: 'Tempest Ray', emoji: '🐟', affinity: 'flux',
    baseStats: { hp: 58, atk: 56, def: 24, spd: 58 },
    lore: 'A floating ray that channels storms across the Aetherium\'s skies.',
    growth: { hp: 8, atk: 8, def: 4, spd: 7 },
    abilities: ['electro_shock', 'tentacle_whip', 'static_burst', 'jelly_wrap'],
    evolve: null, captureRate: 30, encounterLevel: [45, 50]
  },
  aura_bloom: {
    name: 'Aura Bloom', emoji: '🌸', affinity: 'prism',
    baseStats: { hp: 48, atk: 30, def: 26, spd: 32 },
    lore: 'A flower that radiates healing light from the Aetherium\'s purest crystals.',
    growth: { hp: 8, atk: 5, def: 4, spd: 4 },
    abilities: ['aura_heal', 'petal_storm', 'bloom_burst', 'light_bond'],
    evolve: { to: 'luminaria', atLevel: 30, catalyst: 'aura_petals' },
    captureRate: 110, encounterLevel: [20, 30]
  },
  luminaria: {
    name: 'Luminaria', emoji: '🌟', affinity: 'prism',
    baseStats: { hp: 68, atk: 46, def: 40, spd: 48 },
    lore: 'The radiant bloom whose light can mend even the deepest Aetherium wounds.',
    growth: { hp: 10, atk: 7, def: 5, spd: 6 },
    abilities: ['aura_heal', 'petal_storm', 'bloom_burst', 'light_bond'],
    evolve: null, captureRate: 30, encounterLevel: [46, 50]
  },
  guardian_ward: {
    name: 'Guardian Ward', emoji: '🗿', affinity: 'aegis',
    baseStats: { hp: 60, atk: 35, def: 45, spd: 20 },
    lore: 'A stone guardian animated by the Aetherium\'s protective will.',
    growth: { hp: 10, atk: 5, def: 6, spd: 3 },
    abilities: ['ward_strike', 'stone_guard', 'aegis_wall', 'protective_roar'],
    evolve: { to: 'ward_lord', atLevel: 32, catalyst: 'ward_stone' },
    captureRate: 100, encounterLevel: [22, 35]
  },
  ward_lord: {
    name: 'Ward Lord', emoji: '👑', affinity: 'aegis',
    baseStats: { hp: 88, atk: 52, def: 65, spd: 32 },
    lore: 'The sovereign of guardians, its authority commands the Aetherium\'s defenses.',
    growth: { hp: 12, atk: 7, def: 8, spd: 5 },
    abilities: ['ward_strike', 'stone_guard', 'aegis_wall', 'protective_roar'],
    evolve: null, captureRate: 25, encounterLevel: [48, 50]
  }
};

// ── const: ABILITIES ──
// ===== ABILITY DATABASE (40+ abilities) =====
const ABILITIES = {
  // Virel abilities
  scorch: { name: 'Scorch', type: 'virel', power: 40, accuracy: 100, pp: 20, effect: 'burn', effectChance: 0.3 },
  ember_blast: { name: 'Ember Blast', type: 'virel', power: 65, accuracy: 95, pp: 15, effect: 'burn', effectChance: 0.25 },
  flame_wisp: { name: 'Flame Wisp', type: 'virel', power: 30, accuracy: 100, pp: 25, effect: 'heal_self', effectChance: 0.4 },
  blaze_fury: { name: 'Blaze Fury', type: 'virel', power: 90, accuracy: 85, pp: 10, effect: 'recoil', effectChance: 0.3 },
  energy_drain: { name: 'Energy Drain', type: 'virel', power: 50, accuracy: 95, pp: 15, effect: 'steal_hp', effectChance: 0.5 },
  aether_bite: { name: 'Aether Bite', type: 'virel', power: 55, accuracy: 90, pp: 18, effect: 'energy_boost', effectChance: 0.2 },
  photosynthesis: { name: 'Photosynthesis', type: 'virel', power: 0, accuracy: 100, pp: 10, effect: 'self_heal', effectChance: 0.6 },
  moss_bond: { name: 'Moss Bond', type: 'virel', power: 35, accuracy: 100, pp: 15, effect: 'bind', effectChance: 0.35 },

  // Krell abilities
  rock_throw: { name: 'Rock Throw', type: 'krell', power: 45, accuracy: 100, pp: 20, effect: 'frost', effectChance: 0.15 },
  stone_wall: { name: 'Stone Wall', type: 'krell', power: 0, accuracy: 100, pp: 15, effect: 'barrier', effectChance: 0.7 },
  quarry_strike: { name: 'Quarry Strike', type: 'krell', power: 80, accuracy: 85, pp: 10, effect: 'def_down', effectChance: 0.3 },
  earth_shield: { name: 'Earth Shield', type: 'krell', power: 0, accuracy: 100, pp: 15, effect: 'def_up', effectChance: 0.5 },
  crystal_shell: { name: 'Crystal Shell', type: 'krell', power: 0, accuracy: 100, pp: 12, effect: 'barrier', effectChance: 0.6 },
  acid_trail: { name: 'Acid Trail', type: 'krell', power: 40, accuracy: 95, pp: 15, effect: 'def_down', effectChance: 0.4 },
  mineral_bite: { name: 'Mineral Bite', type: 'krell', power: 50, accuracy: 90, pp: 18, effect: 'steal_hp', effectChance: 0.3 },
  harden: { name: 'Harden', type: 'krell', power: 0, accuracy: 100, pp: 20, effect: 'def_up', effectChance: 0.5 },

  // Thorne abilities
  vine_whip: { name: 'Vine Whip', type: 'thorne', power: 40, accuracy: 100, pp: 20, effect: 'bind', effectChance: 0.3 },
  thorn_barrage: { name: 'Thorn Barrage', type: 'thorne', power: 30, accuracy: 95, pp: 15, effect: 'multi_hit', effectChance: 0.3 },
  suffocate: { name: 'Suffocate', type: 'thorne', power: 60, accuracy: 85, pp: 12, effect: 'frost', effectChance: 0.35 },
  bloom_strike: { name: 'Bloom Strike', type: 'thorne', power: 75, accuracy: 90, pp: 10, effect: 'heal_self', effectChance: 0.3 },
  burrow: { name: 'Burrow', type: 'thorne', power: 0, accuracy: 100, pp: 10, effect: 'evasion_up', effectChance: 0.7 },
  dig_down: { name: 'Dig Down', type: 'thorne', power: 70, accuracy: 90, pp: 10, effect: 'multi_hit', effectChance: 0.3 },

  // Dusk abilities
  shadow_wing: { name: 'Shadow Wing', type: 'dusk', power: 50, accuracy: 95, pp: 15, effect: 'spd_down', effectChance: 0.3 },
  void_dust: { name: 'Void Dust', type: 'dusk', power: 45, accuracy: 90, pp: 18, effect: 'shock', effectChance: 0.25 },
  nightmare: { name: 'Nightmare', type: 'dusk', power: 70, accuracy: 80, pp: 10, effect: 'shock', effectChance: 0.4 },
  gloom_burst: { name: 'Gloom Burst', type: 'dusk', power: 85, accuracy: 75, pp: 8, effect: 'all_down', effectChance: 0.2 },
  shadow_pounce: { name: 'Shadow Pounce', type: 'dusk', power: 55, accuracy: 100, pp: 15, effect: 'priority', effectChance: 0 },
  void_step: { name: 'Void Step', type: 'dusk', power: 0, accuracy: 100, pp: 10, effect: 'spd_up', effectChance: 0.6 },
  dark_bite: { name: 'Dark Bite', type: 'dusk', power: 60, accuracy: 90, pp: 15, effect: 'steal_hp', effectChance: 0.3 },
  phantom_fade: { name: 'Phantom Fade', type: 'dusk', power: 0, accuracy: 100, pp: 12, effect: 'evasion_up', effectChance: 0.7 },

  // Flux abilities
  spark_bolt: { name: 'Spark Bolt', type: 'flux', power: 50, accuracy: 100, pp: 20, effect: 'shock', effectChance: 0.3 },
  overload: { name: 'Overload', type: 'flux', power: 80, accuracy: 80, pp: 10, effect: 'burn', effectChance: 0.4 },
  chain_lightning: { name: 'Chain Lightning', type: 'flux', power: 35, accuracy: 95, pp: 10, effect: 'multi_hit', effectChance: 0.3 },
  surge: { name: 'Surge', type: 'flux', power: 0, accuracy: 100, pp: 15, effect: 'spd_up', effectChance: 0.5 },
  electro_shock: { name: 'Electro Shock', type: 'flux', power: 45, accuracy: 95, pp: 18, effect: 'shock', effectChance: 0.35 },
  tentacle_whip: { name: 'Tentacle Whip', type: 'flux', power: 40, accuracy: 100, pp: 20, effect: 'bind', effectChance: 0.25 },
  static_burst: { name: 'Static Burst', type: 'flux', power: 65, accuracy: 85, pp: 12, effect: 'shock', effectChance: 0.4 },
  jelly_wrap: { name: 'Jelly Wrap', type: 'flux', power: 55, accuracy: 90, pp: 14, effect: 'bind', effectChance: 0.45 },

  // Prism abilities
  prism_ray: { name: 'Prism Ray', type: 'prism', power: 55, accuracy: 95, pp: 15, effect: 'burn', effectChance: 0.2 },
  refraction: { name: 'Refraction', type: 'prism', power: 0, accuracy: 100, pp: 12, effect: 'evasion_up', effectChance: 0.6 },
  luster_burst: { name: 'Luster Burst', type: 'prism', power: 75, accuracy: 85, pp: 10, effect: 'all_up', effectChance: 0.3 },
  prismatic_heal: { name: 'Prismatic Heal', type: 'prism', power: 0, accuracy: 100, pp: 15, effect: 'heal_self', effectChance: 0.7 },
  aura_heal: { name: 'Aura Heal', type: 'prism', power: 0, accuracy: 100, pp: 20, effect: 'regen', effectChance: 0.6 },
  petal_storm: { name: 'Petal Storm', type: 'prism', power: 45, accuracy: 95, pp: 18, effect: 'multi_hit', effectChance: 0.3 },
  bloom_burst: { name: 'Bloom Burst', type: 'prism', power: 70, accuracy: 85, pp: 10, effect: 'heal_self', effectChance: 0.35 },
  light_bond: { name: 'Light Bond', type: 'prism', power: 0, accuracy: 100, pp: 12, effect: 'barrier', effectChance: 0.5 },

  // Aegis abilities
  shield_bash: { name: 'Shield Bash', type: 'aegis', power: 50, accuracy: 95, pp: 18, effect: 'def_down', effectChance: 0.3 },
  iron_wall: { name: 'Iron Wall', type: 'aegis', power: 0, accuracy: 100, pp: 15, effect: 'barrier', effectChance: 0.65 },
  guardian_strike: { name: 'Guardian Strike', type: 'aegis', power: 70, accuracy: 85, pp: 10, effect: 'def_up', effectChance: 0.3 },
  fortify: { name: 'Fortify', type: 'aegis', power: 0, accuracy: 100, pp: 15, effect: 'def_up', effectChance: 0.55 },
  ward_strike: { name: 'Ward Strike', type: 'aegis', power: 55, accuracy: 90, pp: 15, effect: 'def_down', effectChance: 0.35 },
  stone_guard: { name: 'Stone Guard', type: 'aegis', power: 0, accuracy: 100, pp: 12, effect: 'barrier', effectChance: 0.6 },
  aegis_wall: { name: 'Aegis Wall', type: 'aegis', power: 0, accuracy: 100, pp: 10, effect: 'all_up', effectChance: 0.25 },
  protective_roar: { name: 'Protective Roar', type: 'aegis', power: 0, accuracy: 100, pp: 12, effect: 'def_up', effectChance: 0.5 }
};

// ── const: ITEMS ──
// ===== ITEMS =====
const ITEMS = {
  // Collection items
  aether_ball: { name: 'Aether Ball', type: 'capture', tier: 1, rate: 1, price: 100, desc: 'Basic collection sphere.' },
  void_orb: { name: 'Void Orb', type: 'capture', tier: 2, rate: 2, price: 300, desc: 'Enhanced collection orb.' },
  prism_crystal: { name: 'Prism Crystal', type: 'capture', tier: 3, rate: 4, price: 800, desc: 'Powerful collection crystal.' },
  // Restoration
  aether_draught: { name: 'Aether Draught', type: 'restore', heal: 30, price: 50, desc: 'Restores 30 HP.' },
  great_aether: { name: 'Great Aether', type: 'restore', heal: 80, price: 150, desc: 'Restores 80 HP.' },
  full_restoration: { name: 'Full Restoration', type: 'restore', full: true, price: 500, desc: 'Fully restores HP.' },
  // Catalysts for transformation
  ember_crystal: { name: 'Ember Crystal', type: 'catalyst', desc: 'Evolve Emberlit → Pyraxis.' },
  stone_fragment: { name: 'Stone Fragment', type: 'catalyst', desc: 'Evolve Stonewisp → Lithos.' },
  thorn_seed: { name: 'Thorn Seed', type: 'catalyst', desc: 'Evolve Thornling → Briarthorn.' },
  shadow_wing: { name: 'Shadow Wing', type: 'catalyst', desc: 'Evolve Dusk Moth → Umbrafly.' },
  lightning_core: { name: 'Lightning Core', type: 'catalyst', desc: 'Evolve Flux Spark → Voltex.' },
  prism_shard: { name: 'Prism Shard', type: 'catalyst', desc: 'Evolve Prism Fae → Crystalla.' },
  aegis_core: { name: 'Aegis Core', type: 'catalyst', desc: 'Evolve Aegis Knight → Sentinel.' },
  aether_heart: { name: 'Aether Heart', type: 'catalyst', desc: 'Evolve Aether Worm → Aether Drake.' },
  glow_cap: { name: 'Glow Cap', type: 'catalyst', desc: 'Evolve Glow Moss → Moss Giant.' },
  void_fang: { name: 'Void Fang', type: 'catalyst', desc: 'Evolve Void Fox → Nether Wolf.' },
  crystal_gel: { name: 'Crystal Gel', type: 'catalyst', desc: 'Evolve Crystal Slug → Gem Shield.' },
  storm_heart: { name: 'Storm Heart', type: 'catalyst', desc: 'Evolve Storm Jelly → Tempest Ray.' },
  aura_petals: { name: 'Aura Petals', type: 'catalyst', desc: 'Evolve Aura Bloom → Luminaria.' },
  ward_stone: { name: 'Ward Stone', type: 'catalyst', desc: 'Evolve Guardian Ward → Ward Lord.' },
  // Field tools
  aether_compass: { name: 'Aether Compass', type: 'field', price: 200, desc: 'Reveals wild encounters on the overworld.' },
  energy_drink: { name: 'Energy Drink', type: 'field', price: 75, desc: 'Boosts SPD by 1 level for 3 turns.' }
};

// ── const: ZONES ──
// ===== ZONES =====
const ZONES = [
  {
    id: 0, name: 'Verdant Hollow', name_display: 'Verdant Hollow',
    gradient: 'linear-gradient(180deg, #1a3a1a 0%, #0a2a0a 100%)',
    particles: { color: '#4ade80', count: 30, size: 3, speed: 0.5 },
    encounterRate: 0.15,
    wildCreatures: ['emberlit', 'stonewisp', 'thornling'],
    encounterLevels: [1, 5],
    shrine: true,
    guardian: { name: 'Verdant Warden', type: 'virel', level: 8, emoji: '🌳', abilities: ['photosynthesis', 'moss_bond', 'energy_drain', 'regenerate'] },
    story: 'The first hollow opens before you — a realm of living green where Aetherium energy seeps from the earth itself.',
    storyColor: '#4ade80',
    unlocked: true
  },
  {
    id: 1, name: 'Krell Depths', name_display: 'Krell Depths',
    gradient: 'linear-gradient(180deg, #2a1a3a 0%, #1a0a2a 100%)',
    particles: { color: '#a78bfa', count: 25, size: 4, speed: 0.3 },
    encounterRate: 0.2,
    wildCreatures: ['stonewisp', 'crystal_slug', 'guardian_ward'],
    encounterLevels: [4, 10],
    shrine: true,
    guardian: { name: 'Deep Stone Guardian', type: 'krell', level: 15, emoji: '🗿', abilities: ['earth_shield', 'quarry_strike', 'stone_wall', 'harden'] },
    story: 'Beneath the surface lies the Krell Depths — ancient stone corridors humming with crystalline resonance.',
    storyColor: '#a78bfa',
    unlocked: false
  },
  {
    id: 2, name: 'Thornwood Expanse', name_display: 'Thornwood Expanse',
    gradient: 'linear-gradient(180deg, #3a2a1a 0%, #2a1a0a 100%)',
    particles: { color: '#fb923c', count: 20, size: 3, speed: 0.6 },
    encounterRate: 0.25,
    wildCreatures: ['thornling', 'dusk_moth', 'glow_moss'],
    encounterLevels: [8, 16],
    shrine: true,
    guardian: { name: 'Thornwood Sovereign', type: 'thorne', level: 22, emoji: '🌹', abilities: ['suffocate', 'bloom_strike', 'vine_whip', 'thorn_barrage'] },
    story: 'The Thornwood stretches endlessly — a forest of living thorns where the Aetherium bleeds amber light.',
    storyColor: '#fb923c',
    unlocked: false
  },
  {
    id: 3, name: 'Duskreach Abyss', name_display: 'Duskreach Abyss',
    gradient: 'linear-gradient(180deg, #1a0a2a 0%, #0a001a 100%)',
    particles: { color: '#8b5cf6', count: 35, size: 2, speed: 0.8 },
    encounterRate: 0.3,
    wildCreatures: ['dusk_moth', 'void_fox', 'flux_spark'],
    encounterLevels: [14, 24],
    shrine: true,
    guardian: { name: 'Abyssal Watcher', type: 'dusk', level: 30, emoji: '🌑', abilities: ['nightmare', 'gloom_burst', 'void_dust', 'phantom_fade'] },
    story: 'The Duskreach — where light dies and the Aetherium\'s shadow-self takes form.',
    storyColor: '#8b5cf6',
    unlocked: false
  },
  {
    id: 4, name: 'Fluxstorm Peaks', name_display: 'Fluxstorm Peaks',
    gradient: 'linear-gradient(180deg, #0a2a3a 0%, #001a2a 100%)',
    particles: { color: '#06b6d4', count: 40, size: 3, speed: 1.0 },
    encounterRate: 0.35,
    wildCreatures: ['flux_spark', 'storm_jelly', 'prism_fae'],
    encounterLevels: [20, 32],
    shrine: true,
    guardian: { name: 'Stormheart Titan', type: 'flux', level: 38, emoji: '🌩️', abilities: ['overload', 'chain_lightning', 'surge', 'static_burst'] },
    story: 'The Fluxstorm Peaks — mountains of living electricity where the Aetherium\'s raw power crashes like thunder.',
    storyColor: '#06b6d4',
    unlocked: false
  },
  {
    id: 5, name: 'Prism Core', name_display: 'Prism Core',
    gradient: 'linear-gradient(180deg, #2a2a1a 0%, #1a1a0a 100%)',
    particles: { color: '#fde68a', count: 50, size: 4, speed: 0.7 },
    encounterRate: 0.4,
    wildCreatures: ['prism_fae', 'aegis_knight', 'aether_worm'],
    encounterLevels: [28, 42],
    shrine: true,
    guardian: { name: 'The Final Anchor', type: 'prism', level: 45, emoji: '💎', abilities: ['luster_burst', 'prismatic_heal', 'prism_ray', 'refraction'] },
    story: 'The Prism Core — the heart of the Aetherium. Here, all energies converge. The final Anchor awaits.',
    storyColor: '#fde68a',
    unlocked: false
  }
];

// ── const: ANCHORS ──
// ===== BOSS ANCHORS (4 zone anchors) =====
const ANCHORS = [
  { name: 'Verdant Anchor', zone: 0, level: 10, type: 'virel', emoji: '🌿', bonus: 'atk', bonusVal: 5 },
  { name: 'Krell Anchor', zone: 1, level: 18, type: 'krell', emoji: '🪨', bonus: 'def', bonusVal: 5 },
  { name: 'Thornwood Anchor', zone: 2, level: 28, type: 'thorne', emoji: '🌵', bonus: 'spd', bonusVal: 5 },
  { name: 'Duskreach Anchor', zone: 3, level: 38, type: 'dusk', emoji: '🌑', bonus: 'hp', bonusVal: 10 }
];

// ── const: STATUS_EFFECTS ──
// ===== STATUS EFFECTS =====
const STATUS_EFFECTS = {
  burn: { name: 'Burn', emoji: '🔥', damage: 0.05, desc: 'Takes damage each turn.' },
  frost: { name: 'Frost', emoji: '❄️', spdMult: 0.5, desc: 'Speed halved.' },
  shock: { name: 'Shock', emoji: '⚡', skipChance: 0.3, desc: 'May skip turn.' },
  barrier: { name: 'Barrier', emoji: '🛡️', hits: 1, desc: 'One-hit deflect.' },
  regen: { name: 'Regen', emoji: '💚', healPerTurn: 0.03, desc: 'Heals each turn.' },
  bind: { name: 'Bind', emoji: '🌿', spdMult: 0.7, desc: 'Speed reduced.' },
  burn_self: { name: 'Burn', emoji: '🔥', damage: 0.05, desc: 'Takes damage each turn.' },
  def_down: { name: 'DEF Down', emoji: '📉', defMult: 0.75, desc: 'Defense reduced.' },
  spd_down: { name: 'SPD Down', emoji: '📉', spdMult: 0.75, desc: 'Speed reduced.' },
  all_down: { name: 'Stats Down', emoji: '📉', allMult: 0.8, desc: 'All stats reduced.' }
};

// ── let: game ──
// ===== GAME STATE =====
let game = {
  screen: 'title', // title, overworld, battle, capture, shrine, vignette, modal, pause
  zone: 0,
  playerX: 5, playerY: 5,
  party: [], // up to 6 creatures
  inventory: {}, // item_id -> count
  collected: [], // creature_ids collected
  anchorsDefeated: [], // anchor indices
  storyStage: 0,
  totalBattles: 0,
  totalCaptures: 0,
  shrineSave: null,
  audioMuted: false,
  postGame: false,
  tutorialPhase: 0,
  moveCount: 0,
  hasMoved: false,
  firstBattle: false,
  firstCapture: false
};

// ── function: saveGame ──
// ===== SAVE / LOAD =====
function saveGame(slot) {
  const data = {
    zone: game.zone, playerX: game.playerX, playerY: game.playerY,
    party: game.party.map(c => ({ ...c, currentHp: c.currentHp })),
    inventory: { ...game.inventory },
    collected: [...game.collected],
    anchorsDefeated: [...game.anchorsDefeated],
    storyStage: game.storyStage,
    shrineSave: game.shrineSave
  };
  localStorage.setItem('aetherials_save_' + slot, JSON.stringify(data));
}

// ── function: loadGame ──
function loadGame(slot) {
  const raw = localStorage.getItem('aetherials_save_' + slot);
  if (!raw) return null;
  const data = JSON.parse(raw);
  game.zone = data.zone;
  game.playerX = data.playerX;
  game.playerY = data.playerY;
  game.party = data.party;
  game.inventory = data.inventory;
  game.collected = data.collected;
  game.anchorsDefeated = data.anchorsDefeated;
  game.storyStage = data.storyStage;
  game.shrineSave = data.shrineSave;
  game.postGame = game.anchorsDefeated.length >= 4;
  return true;
}

// ── function: hasSave ──
function hasSave(slot) {
  return localStorage.getItem('aetherials_save_' + slot) !== null;
}

// ── function: getStat ──
// ===== STAT CALCULATIONS =====
function getStat(creature, stat) {
  const base = creature.baseStats[stat];
  const growth = creature.growth[stat];
  const bonus = creature.affinityBonus ? creature.affinityBonus[stat] || 0 : 0;
  return Math.floor(base + (growth * (creature.level - 1)) + bonus);
}

// ── function: getEffectiveStat ──
function getEffectiveStat(creature, stat, multiplier) {
  return Math.floor(getStat(creature, stat) * (multiplier || 1));
}

// ── function: getMaxHp ──
function getMaxHp(creature) {
  return getStat(creature, 'hp');
}

// ── function: calcDamage ──
// ===== DAMAGE FORMULA =====
function calcDamage(atkCreature, defCreature, ability) {
  const atkStat = getEffectiveStat(atkCreature, 'atk', 1);
  const defStat = getEffectiveStat(defCreature, 'def', 1);
  const level = atkCreature.level;

  let multiplier = 1;
  const typeChart = TYPE_CHART[ability.type];
  if (typeChart && typeChart[defCreature.affinity]) {
    multiplier = typeChart[defCreature.affinity];
  }

  // STAB
  if (ability.type === atkCreature.affinity) {
    multiplier *= 1.25;
  }

  let base = (0.4 * level * atkStat / defStat) * multiplier;
  let random = 0.85 + Math.random() * 0.3;
  let damage = Math.floor(base * random);
  damage = Math.max(1, damage);

  // Critical hit (6% chance)
  let crit = false;
  if (Math.random() < 0.06) {
    damage = Math.floor(damage * 1.5);
    crit = true;
  }

  return { damage, multiplier, crit };
}

// ── function: getTypeEffectiveness ──
// ===== TYPE EFFECTIVENESS DISPLAY =====
function getTypeEffectiveness(abilityType, targetType) {
  const chart = TYPE_CHART[abilityType];
  if (!chart) return { text: 'Normal', color: '#aaa', mult: 1 };
  const mult = chart[targetType] || 1;
  if (mult > 1.2) return { text: 'Super Effective!', color: '#4ade80', mult };
  if (mult < 0.8) return { text: 'Not Very Effective', color: '#f87171', mult };
  return { text: 'Normal', color: '#aaa', mult };
}

// ── function: addItem ──
// ===== INVENTORY HELPERS =====
function addItem(itemId, count) {
  game.inventory[itemId] = (game.inventory[itemId] || 0) + count;
}

// ── function: removeItem ──
function removeItem(itemId, count) {
  game.inventory[itemId] = (game.inventory[itemId] || 0) - count;
  if (game.inventory[itemId] <= 0) delete game.inventory[itemId];
}

// ── function: hasItem ──
function hasItem(itemId, count) {
  return (game.inventory[itemId] || 0) >= count;
}

// ── function: createCreature ──
function createCreature(templateId, level) {
  const template = CREATURES[templateId];
  if (!template) return null;
  const creature = {
    id: templateId,
    name: template.name,
    emoji: template.emoji,
    affinity: template.affinity,
    level: level,
    currentHp: Math.floor(template.baseStats.hp + template.growth.hp * (level - 1)),
    baseStats: { ...template.baseStats },
    growth: { ...template.growth },
    abilities: [...template.abilities],
    evolved: false,
    exp: 0,
    expToNext: level * 20,
    statusEffects: [],
    affinityBonus: null
  };
  return creature;
}

// ── function: createBossCreature ──
function createBossCreature(templateId, level) {
  const c = createCreature(templateId, level);
  c.currentHp = getMaxHp(c);
  return c;
}

// ── function: checkLevelUp ──
// ===== LEVEL UP =====
function checkLevelUp(creature) {
  while (creature.exp >= creature.expToNext && creature.level < 50) {
    creature.level++;
    creature.exp -= creature.expToNext;
    creature.expToNext = creature.level * 20;
    // Recalculate HP
    const maxHp = getMaxHp(creature);
    creature.currentHp = Math.min(creature.currentHp + 10, maxHp);
  }
  return creature.level >= 50 ? false : true;
}

// ── function: calcExp ──
// ===== EXP CALCULATION =====
function calcExp(creature, defeatedLevel, isBoss) {
  const base = Math.floor(creature.level * 10);
  const levelDiff = Math.abs(defeatedLevel - creature.level);
  const diffBonus = Math.floor(levelDiff * 2);
  const bossMult = isBoss ? 2 : 1;
  return Math.floor((base + diffBonus) * bossMult);
}

// ── function: generateWildEncounter ──
// ===== WILD ENCOUNTER =====
function generateWildEncounter() {
  const zone = ZONES[game.zone];
  const levelRange = game.postGame
    ? [zone.encounterLevels[0] + 10, zone.encounterLevels[1] + 10]
    : zone.encounterLevels;
  const creatureId = zone.wildCreatures[Math.floor(Math.random() * zone.wildCreatures.length)];
  const level = levelRange[0] + Math.floor(Math.random() * (levelRange[1] - levelRange[0] + 1));
  return { creature: createCreature(creatureId, level), isBoss: false };
}

// ── function: generateBossEncounter ──
function generateBossEncounter(anchorIndex) {
  const anchor = ANCHORS[anchorIndex];
  const templateId = ['emberlit', 'stonewisp', 'thornling', 'dusk_moth'][anchorIndex];
  return { creature: createBossCreature(templateId, anchor.level), isBoss: true, anchor };
}

// ── let: battle ──
// ===== BATTLE INITIALIZATION =====
let battle = {
  playerParty: [],
  enemies: [],
  turnOrder: [],
  currentTurnIndex: 0,
  phase: 'select', // select, animating, capture, victory, defeat
  selectedAbility: null,
  selectedTarget: null,
  animating: false,
  floatingNumbers: [],
  screenShake: false,
  particleBursts: [],
  captured: null,
  capturedCreature: null
};

// ── function: calcTurnOrder ──
// ===== BATTLE TURN ORDER =====
function calcTurnOrder() {
  const all = [...battle.playerParty.map((c, i) => ({ type: 'player', creature: c, index: i })),
               ...battle.enemies.map((c, i) => ({ type: 'enemy', creature: c, index: i }))];
  all.sort((a, b) => {
    const spdA = getEffectiveStat(a.creature, 'spd', 1);
    const spdB = getEffectiveStat(b.creature, 'spd', 1);
    return spdB - spdA;
  });
  return all;
}

// ── let: audioCtx ──
// ===== AUDIO SYSTEM =====
let audioCtx = null;

// ── let: audioNodes ──
let audioNodes = [];

// ── function: initAudio ──
// ===== AUDIO =====
function initAudio() {
  if (audioCtx) return;
  audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}

// ── function: playTone ──
function playTone(freq, duration, type, volume) {
  if (!audioCtx || game.audioMuted) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.type = type || 'sine';
  osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
  gain.gain.setValueAtTime(volume || 0.1, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
  osc.connect(gain);
  gain.connect(audioCtx.destination);
  osc.start();
  osc.stop(audioCtx.currentTime + duration);
}

// ── function: playDamageSound ──
function playDamageSound() { playTone(200, 0.15, 'sawtooth', 0.08); setTimeout(() => playTone(150, 0.1, 'sawtooth', 0.06), 50); }

// ── function: playCritSound ──
function playCritSound() { playTone(400, 0.1, 'square', 0.1); setTimeout(() => playTone(600, 0.15, 'square', 0.1), 50); setTimeout(() => playTone(800, 0.2, 'square', 0.08), 100); }

// ── function: playSuperEffectiveSound ──
function playSuperEffectiveSound() { playTone(300, 0.2, 'sine', 0.1); setTimeout(() => playTone(500, 0.2, 'sine', 0.1), 100); setTimeout(() => playTone(700, 0.3, 'sine', 0.08), 200); }

// ── function: playCaptureSound ──
function playCaptureSound() { playTone(440, 0.15, 'sine', 0.08); setTimeout(() => playTone(550, 0.15, 'sine', 0.08), 150); setTimeout(() => playTone(660, 0.2, 'sine', 0.08), 300); setTimeout(() => playTone(880, 0.3, 'sine', 0.06), 500); }

// ── function: playVictorySound ──
function playVictorySound() { [440, 550, 660, 880].forEach((f, i) => { setTimeout(() => playTone(f, 0.3, 'sine', 0.08), i * 150); }); }

// ── function: playDefeatSound ──
function playDefeatSound() { [440, 350, 280, 200].forEach((f, i) => { setTimeout(() => playTone(f, 0.4, 'sawtooth', 0.06), i * 200); }); }

// ── function: playAmbientPad ──
function playAmbientPad() {
  if (!audioCtx || game.audioMuted) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.type = 'sine';
  osc.frequency.setValueAtTime(80, audioCtx.currentTime);
  gain.gain.setValueAtTime(0.02, audioCtx.currentTime);
  osc.connect(gain);
  gain.connect(audioCtx.destination);
  osc.start();
  audioNodes.push(osc);
}

// ── function: playZoneAmbient ──
function playZoneAmbient(zoneId) {
  if (game.audioMuted) return;
  const freqs = [[120, 180], [100, 150], [140, 200], [90, 130], [160, 220], [200, 280]];
  const f = freqs[zoneId] || freqs[0];
  playTone(f[0], 2, 'sine', 0.03);
  playTone(f[1], 2, 'sine', 0.02);
}

// ── function: toggleMute ──
function toggleMute() { game.audioMuted = !game.audioMuted; }

// ── let: particles ──
let particles = [];

// ── function: spawnParticle ──
function spawnParticle(x, y, color, size, vx, vy, life) {
  particles.push({ x, y, color, size, vx: vx || (Math.random() - 0.5) * 2, vy: vy || -Math.random() * 2, life: life || 60, maxLife: life || 60 });
}

// ── function: updateParticles ──
function updateParticles() {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.x += p.vx; p.y += p.vy; p.life--;
    if (p.life <= 0) particles.splice(i, 1);
  }
}

// ── function: drawParticles ──
function drawParticles(ctx) {
  for (const p of particles) {
    const alpha = p.life / p.maxLife;
    ctx.globalAlpha = alpha;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
}

// ── let: floatingNumbers ──
let floatingNumbers = [];

// ── function: spawnFloatingNumber ──
function spawnFloatingNumber(x, y, text, color, isCrit) {
  floatingNumbers.push({ x, y, text, color, isCrit: isCrit || false, life: 60, maxLife: 60 });
}

// ── function: updateFloatingNumbers ──
function updateFloatingNumbers() {
  for (let i = floatingNumbers.length - 1; i >= 0; i--) {
    const n = floatingNumbers[i];
    n.y -= 1; n.life--;
    if (n.life <= 0) floatingNumbers.splice(i, 1);
  }
}

// ── function: drawFloatingNumbers ──
function drawFloatingNumbers(ctx) {
  for (const n of floatingNumbers) {
    const alpha = n.life / n.maxLife;
    ctx.globalAlpha = alpha;
    ctx.font = n.isCrit ? 'bold 24px sans-serif' : 'bold 18px sans-serif';
    ctx.fillStyle = n.color;
    ctx.strokeStyle = 'rgba(0,0,0,0.5)';
    ctx.lineWidth = 2;
    ctx.strokeText(n.text, n.x, n.y);
    ctx.fillText(n.text, n.x, n.y);
  }
  ctx.globalAlpha = 1;
}

// ── let: battleAnim ──
// ===== BATTLE ANIMATIONS =====
let battleAnim = { active: false, type: '', target: null, progress: 0, duration: 30, callback: null };

// ── function: triggerBattleAnim ──
function triggerBattleAnim(type, target, duration, callback) {
  battleAnim = { active: true, type, target, progress: 0, duration: duration || 30, callback };
}

// ── function: updateBattleAnim ──
function updateBattleAnim() {
  if (!battleAnim.active) return;
  battleAnim.progress++;
  if (battleAnim.progress >= battleAnim.duration) {
    battleAnim.active = false;
    if (battleAnim.callback) battleAnim.callback();
  }
}

// ── const: TILE_SIZE ──
// ===== OVERWORLD RENDERING =====
const TILE_SIZE = 48;

// ── const: MAP_W ──
const MAP_W = 20;

// ── const: MAP_H ──
const MAP_H = 20;

// ── function: generateMap ──
function generateMap(zoneId) {
  const map = [];
  for (let y = 0; y < MAP_H; y++) {
    map[y] = [];
    for (let x = 0; x < MAP_W; x++) {
      if (x === 0 || x === MAP_W - 1 || y === 0 || y === MAP_H - 1) {
        map[y][x] = 1;
      } else {
        const r = Math.random();
        if (r < 0.08) map[y][x] = 2;
        else if (r < 0.12) map[y][x] = 3;
        else if (r < 0.15) map[y][x] = 4;
        else map[y][x] = 0;
      }
    }
  }
  map[9][9] = 0; map[9][10] = 0; map[10][9] = 0; map[10][10] = 0;
  map[10][10] = 3;
  map[3][17] = 5;
  for (let x = 10; x < 17; x++) map[3][x] = 0;
  for (let y = 10; y < 3; y++) map[y][10] = 0;
  return map;
}

// ── let: overworldMap ──
let overworldMap = generateMap(0);

// ── let: overworldCanvas ──
let overworldCanvas = null;

// ── let: overworldCtx ──
let overworldCtx = null;

// ── let: zoneParticles ──
let zoneParticles = [];

// ── function: initOverworldCanvas ──
function initOverworldCanvas() {
  overworldCanvas = document.getElementById('overworld-canvas');
  if (overworldCanvas) {
    const dpr = window.devicePixelRatio || 1;
    overworldCanvas.width = overworldCanvas.clientWidth * dpr;
    overworldCanvas.height = overworldCanvas.clientHeight * dpr;
    overworldCtx = overworldCanvas.getContext('2d');
    overworldCtx.scale(dpr, dpr);
  }
}

// ── function: resizeOverworldCanvas ──
function resizeOverworldCanvas() {
  if (!overworldCanvas) return;
  const dpr = window.devicePixelRatio || 1;
  overworldCanvas.width = overworldCanvas.clientWidth * dpr;
  overworldCanvas.height = overworldCanvas.clientHeight * dpr;
  if (overworldCtx) overworldCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
}

// ── function: initZoneParticles ──
function initZoneParticles(zoneId) {
  zoneParticles = [];
  const zone = ZONES[zoneId];
  for (let i = 0; i < zone.particles.count; i++) {
    zoneParticles.push({
      x: Math.random() * (overworldCanvas?.clientWidth || 800),
      y: Math.random() * (overworldCanvas?.clientHeight || 600),
      vx: (Math.random() - 0.5) * zone.particles.speed,
      vy: -Math.random() * zone.particles.speed * 0.5,
      size: zone.particles.size * (0.5 + Math.random()),
      alpha: 0.3 + Math.random() * 0.5,
      color: zone.particles.color,
      pulse: Math.random() * Math.PI * 2
    });
  }
}

// ── function: updateZoneParticles ──
function updateZoneParticles() {
  for (const p of zoneParticles) {
    p.x += p.vx; p.y += p.vy; p.pulse += 0.02;
    if (p.y < -10) { p.y = (overworldCanvas?.clientHeight || 600) + 10; p.x = Math.random() * (overworldCanvas?.clientWidth || 800); }
    if (p.x < -10) p.x = (overworldCanvas?.clientWidth || 800) + 10;
    if (p.x > (overworldCanvas?.clientWidth || 800) + 10) p.x = -10;
  }
}

// ── function: drawZoneParticles ──
function drawZoneParticles(ctx) {
  for (const p of zoneParticles) {
    const alpha = p.alpha * (0.7 + 0.3 * Math.sin(p.pulse));
    ctx.globalAlpha = alpha;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
}

// ── function: drawOverworld ──
function drawOverworld() {
  if (!overworldCtx || !overworldCanvas) return;
  const w = overworldCanvas.clientWidth;
  const h = overworldCanvas.clientHeight;
  const zone = ZONES[game.zone];

  const grad = overworldCtx.createLinearGradient(0, 0, 0, h);
  grad.addColorStop(0, '#1a1a2e');
  grad.addColorStop(1, '#0a0a1a');
  overworldCtx.fillStyle = grad;
  overworldCtx.fillRect(0, 0, w, h);

  const camX = game.playerX * TILE_SIZE - w / 2 + TILE_SIZE / 2;
  const camY = game.playerY * TILE_SIZE - h / 2 + TILE_SIZE / 2;

  for (let y = 0; y < MAP_H; y++) {
    for (let x = 0; x < MAP_W; x++) {
      const sx = x * TILE_SIZE - camX;
      const sy = y * TILE_SIZE - camY;
      if (sx < -TILE_SIZE || sx > w + TILE_SIZE || sy < -TILE_SIZE || sy > h + TILE_SIZE) continue;
      const tile = overworldMap[y][x];
      if (tile === 0) {
        overworldCtx.fillStyle = 'rgba(255,255,255,0.03)';
        overworldCtx.fillRect(sx, sy, TILE_SIZE, TILE_SIZE);
      } else if (tile === 1) {
        overworldCtx.fillStyle = 'rgba(255,255,255,0.08)';
        overworldCtx.fillRect(sx, sy, TILE_SIZE, TILE_SIZE);
        overworldCtx.strokeStyle = 'rgba(255,255,255,0.15)';
        overworldCtx.lineWidth = 1;
        overworldCtx.strokeRect(sx, sy, TILE_SIZE, TILE_SIZE);
      } else if (tile === 2) {
        overworldCtx.fillStyle = 'rgba(255,255,255,0.12)';
        overworldCtx.fillRect(sx, sy, TILE_SIZE, TILE_SIZE);
        overworldCtx.fillStyle = 'rgba(255,255,255,0.2)';
        overworldCtx.fillRect(sx + 4, sy + 4, TILE_SIZE - 8, TILE_SIZE - 8);
      } else if (tile === 3) {
        overworldCtx.fillStyle = 'rgba(251, 191, 36, 0.3)';
        overworldCtx.fillRect(sx, sy, TILE_SIZE, TILE_SIZE);
        overworldCtx.fillStyle = '#fbbf24';
        overworldCtx.font = '24px sans-serif';
        overworldCtx.textAlign = 'center';
        overworldCtx.textBaseline = 'middle';
        overworldCtx.fillText('⛩️', sx + TILE_SIZE / 2, sy + TILE_SIZE / 2);
      } else if (tile === 4) {
        overworldCtx.fillStyle = 'rgba(96, 165, 250, 0.3)';
        overworldCtx.fillRect(sx, sy, TILE_SIZE, TILE_SIZE);
      } else if (tile === 5) {
        overworldCtx.fillStyle = 'rgba(251, 191, 36, 0.4)';
        overworldCtx.fillRect(sx, sy, TILE_SIZE, TILE_SIZE);
        overworldCtx.fillStyle = '#fbbf24';
        overworldCtx.font = '24px sans-serif';
        overworldCtx.textAlign = 'center';
        overworldCtx.textBaseline = 'middle';
        overworldCtx.fillText('👑', sx + TILE_SIZE / 2, sy + TILE_SIZE / 2);
      }
    }
  }

  const playerSx = game.playerX * TILE_SIZE - camX;
  const playerSy = game.playerY * TILE_SIZE - camY;
  overworldCtx.fillStyle = '#6ee7b7';
  overworldCtx.beginPath();
  overworldCtx.arc(playerSx + TILE_SIZE / 2, playerSy + TILE_SIZE / 2, 12, 0, Math.PI * 2);
  overworldCtx.fill();
  overworldCtx.fillStyle = '#fff';
  overworldCtx.font = '16px sans-serif';
  overworldCtx.textAlign = 'center';
  overworldCtx.textBaseline = 'middle';
  overworldCtx.fillText('🧙', playerSx + TILE_SIZE / 2, playerSy + TILE_SIZE / 2);

  drawZoneParticles(overworldCtx);
  drawParticles(overworldCtx);

  overworldCtx.fillStyle = 'rgba(0,0,0,0.3)';
  overworldCtx.fillRect(0, 0, w, 40);
  overworldCtx.fillStyle = '#e8e8f0';
  overworldCtx.font = '14px sans-serif';
  overworldCtx.textAlign = 'left';
  overworldCtx.fillText('📍 ' + zone.name, 15, 25);
}

// ── let: battleCanvas ──
// ===== BATTLE RENDERING =====
let battleCanvas = null;

// ── let: battleCtx ──
let battleCtx = null;

// ── function: initBattleCanvas ──
function initBattleCanvas() {
  battleCanvas = document.getElementById('battle-canvas');
  if (battleCanvas) {
    const dpr = window.devicePixelRatio || 1;
    battleCanvas.width = battleCanvas.clientWidth * dpr;
    battleCanvas.height = battleCanvas.clientHeight * dpr;
    battleCtx = battleCanvas.getContext('2d');
    battleCtx.scale(dpr, dpr);
  }
}

// ── function: resizeBattleCanvas ──
function resizeBattleCanvas() {
  if (!battleCanvas) return;
  const dpr = window.devicePixelRatio || 1;
  battleCanvas.width = battleCanvas.clientWidth * dpr;
  battleCanvas.height = battleCanvas.clientHeight * dpr;
  if (battleCtx) battleCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
}

// ── function: drawBattleScene ──
function drawBattleScene() {
  if (!battleCtx || !battleCanvas) return;
  const w = battleCanvas.clientWidth;
  const h = battleCanvas.clientHeight;

  const grad = battleCtx.createLinearGradient(0, 0, 0, h);
  grad.addColorStop(0, '#1a1a2e');
  grad.addColorStop(0.5, '#16213e');
  grad.addColorStop(1, '#0f3460');
  battleCtx.fillStyle = grad;
  battleCtx.fillRect(0, 0, w, h);

  battleCtx.strokeStyle = 'rgba(255,255,255,0.03)';
  battleCtx.lineWidth = 1;
  for (let x = 0; x < w; x += 40) {
    battleCtx.beginPath(); battleCtx.moveTo(x, 0); battleCtx.lineTo(x, h); battleCtx.stroke();
  }
  for (let y = 0; y < h; y += 40) {
    battleCtx.beginPath(); battleCtx.moveTo(0, y); battleCtx.lineTo(w, y); battleCtx.stroke();
  }

  const enemyStartX = w * 0.55;
  const enemyY = h * 0.25;
  battle.enemies.forEach((enemy, i) => {
    const ex = enemyStartX + i * 120;
    const ey = enemyY;
    const hpPercent = enemy.currentHp / getMaxHp(enemy);

    battleCtx.fillStyle = 'rgba(255,255,255,0.05)';
    battleCtx.beginPath();
    battleCtx.arc(ex, ey, 35, 0, Math.PI * 2);
    battleCtx.fill();

    battleCtx.fillStyle = '#e8e8f0';
    battleCtx.font = '32px sans-serif';
    battleCtx.textAlign = 'center';
    battleCtx.textBaseline = 'middle';
    battleCtx.fillText(enemy.emoji, ex, ey);

    battleCtx.fillStyle = '#e8e8f0';
    battleCtx.font = 'bold 12px sans-serif';
    battleCtx.textAlign = 'left';
    battleCtx.fillText(enemy.name, ex - 30, ey + 50);
    battleCtx.fillStyle = '#8888aa';
    battleCtx.font = '10px sans-serif';
    battleCtx.fillText('Lv.' + enemy.level, ex - 30, ey + 65);

    const barW = 60;
    const barH = 6;
    const barX = ex - 30;
    const barY = ey + 75;
    battleCtx.fillStyle = 'rgba(0,0,0,0.4)';
    battleCtx.fillRect(barX, barY, barW, barH);
    const hpColor = hpPercent > 0.5 ? '#4ade80' : hpPercent > 0.2 ? '#fbbf24' : '#f87171';
    battleCtx.fillStyle = hpColor;
    battleCtx.fillRect(barX, barY, barW * hpPercent, barH);
    battleCtx.strokeStyle = 'rgba(255,255,255,0.2)';
    battleCtx.lineWidth = 1;
    battleCtx.strokeRect(barX, barY, barW, barH);

    let statusX = ex - 30;
    let statusY = ey + 88;
    enemy.statusEffects.forEach(se => {
      battleCtx.font = '10px sans-serif';
      battleCtx.textAlign = 'left';
      battleCtx.fillText(STATUS_EFFECTS[se] ? STATUS_EFFECTS[se].emoji : '?', statusX, statusY);
      statusX += 14;
    });
  });

  const activeCreature = battle.activePlayerCreature;
  if (activeCreature) {
    const px = w * 0.2;
    const py = h * 0.65;
    const hpPercent = activeCreature.currentHp / getMaxHp(activeCreature);

    battleCtx.fillStyle = 'rgba(110, 231, 183, 0.1)';
    battleCtx.beginPath();
    battleCtx.arc(px, py, 45, 0, Math.PI * 2);
    battleCtx.fill();

    battleCtx.fillStyle = '#e8e8f0';
    battleCtx.font = '40px sans-serif';
    battleCtx.textAlign = 'center';
    battleCtx.textBaseline = 'middle';
    battleCtx.fillText(activeCreature.emoji, px, py);

    battleCtx.fillStyle = '#e8e8f0';
    battleCtx.font = 'bold 13px sans-serif';
    battleCtx.textAlign = 'left';
    battleCtx.fillText(activeCreature.name, px + 55, py - 30);
    battleCtx.fillStyle = '#8888aa';
    battleCtx.font = '11px sans-serif';
    battleCtx.fillText('Lv.' + activeCreature.level, px + 55, py - 15);

    const barW = 100;
    const barH = 8;
    const barX = px + 55;
    const barY = py;
    battleCtx.fillStyle = 'rgba(0,0,0,0.4)';
    battleCtx.fillRect(barX, barY, barW, barH);
    const hpColor = hpPercent > 0.5 ? '#4ade80' : hpPercent > 0.2 ? '#fbbf24' : '#f87171';
    battleCtx.fillStyle = hpColor;
    battleCtx.fillRect(barX, barY, barW * hpPercent, barH);
    battleCtx.strokeStyle = 'rgba(255,255,255,0.2)';
    battleCtx.lineWidth = 1;
    battleCtx.strokeRect(barX, barY, barW, barH);

    battleCtx.fillStyle = '#8888aa';
    battleCtx.font = '10px sans-serif';
    battleCtx.textAlign = 'left';
    battleCtx.fillText(activeCreature.currentHp + '/' + getMaxHp(activeCreature), barX, barY + 14);

    battleCtx.fillStyle = TYPE_COLORS[activeCreature.affinity];
    battleCtx.font = '10px sans-serif';
    battleCtx.textAlign = 'left';
    battleCtx.fillText(TYPE_EMOJI[activeCreature.affinity] + ' ' + TYPE_NAMES[activeCreature.affinity], barX, barY + 26);

    let statusX = px + 55;
    let statusY = py + 36;
    activeCreature.statusEffects.forEach(se => {
      battleCtx.font = '10px sans-serif';
      battleCtx.fillText(STATUS_EFFECTS[se] ? STATUS_EFFECTS[se].emoji : '?', statusX, statusY);
      statusX += 14;
    });
  }

  drawParticles(battleCtx);
  drawFloatingNumbers(battleCtx);
}

// ── function: showScreen ──
// ===== UI HELPERS =====
function showScreen(screenId) {
  document.querySelectorAll('#title-screen, #overworld-hud, #battle-scene, #capture-game, #shrine-overlay, #story-vignette, #pause-menu').forEach(el => {
    if (el) el.style.display = 'none';
  });
  document.querySelectorAll('.modal-overlay').forEach(el => el.remove());

  switch (screenId) {
    case 'title':
      document.getElementById('title-screen').style.display = 'flex';
      break;
    case 'overworld':
      document.getElementById('overworld-hud').style.display = 'flex';
      document.getElementById('overworld-canvas').style.display = 'block';
      break;
    case 'battle':
      document.getElementById('battle-scene').style.display = 'block';
      break;
    case 'capture':
      document.getElementById('capture-game').style.display = 'flex';
      break;
    case 'shrine':
      document.getElementById('shrine-overlay').style.display = 'flex';
      break;
    case 'vignette':
      document.getElementById('story-vignette').style.display = 'flex';
      break;
    case 'pause':
      document.getElementById('pause-menu').style.display = 'flex';
      break;
  }
  game.screen = screenId;
}

// ── function: showModal ──
function showModal(content) {
  hideModal();
  const overlay = document.createElement('div');
  overlay.className = 'modal-overlay';
  overlay.innerHTML = '<div class="modal" style="position:relative;">' + content + '</div>';
  document.getElementById('app').appendChild(overlay);
  game.modalOverlay = overlay;
}

// ── function: hideModal ──
function hideModal() {
  if (game.modalOverlay) {
    game.modalOverlay.remove();
    game.modalOverlay = null;
  }
}

// ── function: shakeScreen ──
function shakeScreen() {
  const app = document.getElementById('app');
  app.classList.add('shake');
  setTimeout(() => app.classList.remove('shake'), 300);
}

// ── function: renderBattleUI ──
// ===== BATTLE UI RENDERING =====
function renderBattleUI() {
  const activeCreature = battle.activePlayerCreature;
  if (!activeCreature) return;

  const textEl = document.getElementById('battle-text');
  const actionsEl = document.getElementById('battle-actions');

  if (battle.phase === 'select') {
    textEl.textContent = 'What will ' + activeCreature.name + ' do?';
    textEl.style.color = '#e8e8f0';

    let html = '';
    activeCreature.abilities.forEach((abilityId, i) => {
      const ability = ABILITIES[abilityId];
      if (!ability) return;
      const ppLeft = (activeCreature.pp && activeCreature.pp[i]) || ability.pp;
      const typeClass = 'type-' + ability.type;
      html += '<button class="battle-btn ability-btn ' + typeClass + '" onclick="useAbility(' + i + ')" ' + (ppLeft <= 0 ? 'disabled' : '') + '>' +
        TYPE_EMOJI[ability.type] + ' ' + ability.name + ' <span style="font-size:0.7em;color:#8888aa">PP:' + ppLeft + '</span></button>';
    });

    html += '<button class="battle-btn" onclick="showSwitchMenu()">🔄 Switch</button>';
    html += '<button class="battle-btn" onclick="showItemMenu()">🎒 Item</button>';

    if (!battle.isBossBattle) {
      const hasBall = hasItem('aether_ball', 1) || hasItem('void_orb', 1) || hasItem('prism_crystal', 1);
      html += '<button class="battle-btn" onclick="startCapture()" ' + (!hasBall ? 'disabled' : '') + '>⭕ Capture</button>';
    }

    html += '<button class="battle-btn" onclick="tryRun()">🏃 Run</button>';
    actionsEl.innerHTML = html;
  } else if (battle.phase === 'animating') {
    textEl.textContent = battle.animText || '...';
    textEl.style.color = '#fbbf24';
    actionsEl.innerHTML = '';
  } else if (battle.phase === 'victory') {
    textEl.textContent = 'Victory!';
    textEl.style.color = '#4ade80';
    actionsEl.innerHTML = '<button class="battle-btn" onclick="endBattle()">Continue</button>';
  } else if (battle.phase === 'defeat') {
    textEl.textContent = 'Your party has fallen...';
    textEl.style.color = '#f87171';
    actionsEl.innerHTML = '<button class="battle-btn" onclick="retreatFromBattle()">Retreat</button>';
  }
}

// ── function: startBattle ──
// ===== BATTLE LOGIC =====
function startBattle(encounter, isBossBattle) {
  initAudio();
  battle = {
    playerParty: game.party.map(c => ({ ...c, currentHp: c.currentHp, pp: c.abilities.map(a => ABILITIES[a] ? ABILITIES[a].pp : 0) })),
    enemies: [encounter.creature],
    turnOrder: [],
    currentTurnIndex: 0,
    phase: 'select',
    selectedAbility: null,
    selectedTarget: null,
    animating: false,
    floatingNumbers: [],
    screenShake: false,
    particleBursts: [],
    captured: null,
    capturedCreature: null,
    isBossBattle: isBossBattle || false,
    activePlayerCreature: null,
    animText: '',
    encounter: encounter
  };

  updateActiveCreature();
  battle.turnOrder = calcTurnOrder();
  battle.currentTurnIndex = 0;

  showScreen('battle');
  initBattleCanvas();
  resizeBattleCanvas();
  renderBattleUI();

  game.totalBattles++;
}

// ── function: updateActiveCreature ──
function updateActiveCreature() {
  const alive = battle.playerParty.filter(c => c.currentHp > 0);
  if (alive.length === 0) {
    battle.phase = 'defeat';
    return;
  }
  if (!battle.activePlayerCreature || battle.activePlayerCreature.currentHp <= 0) {
    battle.activePlayerCreature = alive[0];
  }
}

// ── function: useAbility ──
function useAbility(abilityIndex) {
  if (battle.phase !== 'select' || battle.animating) return;
  const creature = battle.activePlayerCreature;
  if (!creature || creature.currentHp <= 0) return;

  const abilityId = creature.abilities[abilityIndex];
  const ability = ABILITIES[abilityId];
  if (!ability) return;

  const ppIndex = creature.abilities.indexOf(abilityId);
  const currentPp = (creature.pp && creature.pp[ppIndex]) || ability.pp;
  if (currentPp <= 0) return;

  battle.phase = 'animating';
  battle.selectedAbility = ability;
  battle.selectedAbilityIndex = abilityIndex;

  if (!creature.pp) creature.pp = creature.abilities.map(() => ability.pp);
  if (creature.pp[ppIndex] > 0) creature.pp[ppIndex]--;

  let target, targetIndex;
  if (battle.enemies.length === 1) {
    target = battle.enemies[0];
    targetIndex = 0;
  } else {
    let minHp = Infinity;
    battle.enemies.forEach((e, i) => {
      if (e.currentHp > 0 && e.currentHp < minHp) {
        minHp = e.currentHp;
        targetIndex = i;
      }
    });
    target = battle.enemies[targetIndex];
  }

  executeMove(creature, ability, target, targetIndex, 'player');
}

// ── function: executeMove ──
function executeMove(creature, ability, target, targetIndex, side) {
  const isPlayer = side === 'player';
  battle.animText = creature.name + ' used ' + ability.name + '!';
  renderBattleUI();

  if (isPlayer) {
    triggerBattleAnim('lunge_player', targetIndex, 15, () => {
      resolveHit(creature, ability, target, targetIndex, side);
    });
  } else {
    triggerBattleAnim('lunge_enemy', targetIndex, 15, () => {
      resolveHit(creature, ability, target, targetIndex, side);
    });
  }
}

// ── function: resolveHit ──
function resolveHit(creature, ability, target, targetIndex, side) {
  const isPlayer = side === 'player';

  if (Math.random() * 100 > ability.accuracy) {
    battle.animText = creature.name + "'s attack missed!";
    renderBattleUI();
    setTimeout(() => nextTurn(), 800);
    return;
  }

  const barrierIndex = target.statusEffects.indexOf('barrier');
  if (barrierIndex >= 0) {
    target.statusEffects.splice(barrierIndex, 1);
    battle.animText = target.name + "'s barrier absorbed the attack!";
    renderBattleUI();
    spawnFloatingNumber(
      isPlayer ? window.innerWidth * 0.7 : window.innerWidth * 0.3,
      window.innerHeight * 0.3,
      'Blocked!', '#60a5fa', false
    );
    shakeScreen();
    playTone(300, 0.2, 'sine', 0.08);
    setTimeout(() => nextTurn(), 800);
    return;
  }

  if (ability.power > 0) {
    const result = calcDamage(creature, target, ability);
    target.currentHp = Math.max(0, target.currentHp - result.damage);

    const fx = isPlayer ? window.innerWidth * 0.7 : window.innerWidth * 0.3;
    const fy = window.innerHeight * 0.3;

    if (result.crit) {
      playCritSound();
      spawnFloatingNumber(fx, fy, result.damage + '!', '#fde68a', true);
    } else {
      playDamageSound();
      spawnFloatingNumber(fx, fy, result.damage + '!', '#e8e8f0', false);
    }

    const eff = getTypeEffectiveness(ability.type, target.affinity);
    if (eff.mult > 1.2) {
      playSuperEffectiveSound();
      battle.animText += '\nIt\'s super effective!';
      shakeScreen();
      for (let i = 0; i < 20; i++) {
        spawnParticle(fx, fy, TYPE_COLORS[ability.type], 3 + Math.random() * 3, (Math.random() - 0.5) * 8, (Math.random() - 0.5) * 8, 30);
      }
    } else if (eff.mult < 0.8) {
      battle.animText += '\nIt\'s not very effective...';
    }

    shakeScreen();
    triggerBattleAnim('hit_enemy', targetIndex, 10, () => {
      checkFaint(target, targetIndex, side);
    });
  } else {
    applyAbilityEffect(creature, ability, target, targetIndex, side);
  }

  renderBattleUI();
}

// ── function: applyAbilityEffect ──
function applyAbilityEffect(creature, ability, target, targetIndex, side) {
  const isPlayer = side === 'player';

  switch (ability.effect) {
    case 'heal_self':
    case 'self_heal': {
      const healAmount = Math.floor(getMaxHp(creature) * 0.3);
      creature.currentHp = Math.min(getMaxHp(creature), creature.currentHp + healAmount);
      battle.animText = creature.name + ' restored ' + healAmount + ' HP!';
      spawnFloatingNumber(window.innerWidth * 0.2, window.innerHeight * 0.6, '+' + healAmount, '#4ade80', false);
      break;
    }
    case 'steal_hp': {
      const stealAmount = Math.floor(getMaxHp(creature) * 0.2);
      creature.currentHp = Math.min(getMaxHp(creature), creature.currentHp + stealAmount);
      target.currentHp = Math.max(0, target.currentHp - stealAmount);
      battle.animText = creature.name + ' drained ' + stealAmount + ' HP!';
      spawnFloatingNumber(window.innerWidth * 0.2, window.innerHeight * 0.6, '+' + stealAmount, '#4ade80', false);
      checkFaint(target, targetIndex, side);
      break;
    }
    case 'barrier': {
      target.statusEffects.push('barrier');
      battle.animText = target.name + ' created a barrier!';
      break;
    }
    case 'def_up': {
      if (!creature.statusEffects) creature.statusEffects = [];
      creature.statusEffects.push('def_up');
      battle.animText = creature.name + "'s defense rose!";
      break;
    }
    case 'spd_up': {
      if (!creature.statusEffects) creature.statusEffects = [];
      creature.statusEffects.push('spd_up');
      battle.animText = creature.name + "'s speed increased!";
      break;
    }
    case 'evasion_up': {
      if (!creature.statusEffects) creature.statusEffects = [];
      creature.statusEffects.push('evasion_up');
      battle.animText = creature.name + ' became harder to hit!';
      break;
    }
    case 'all_up': {
      if (!creature.statusEffects) creature.statusEffects = [];
      creature.statusEffects.push('all_up');
      battle.animText = creature.name + "'s stats rose!";
      break;
    }
    case 'regen': {
      target.statusEffects.push('regen');
      battle.animText = target.name + ' began regenerating!';
      break;
    }
    case 'bind': {
      target.statusEffects.push('bind');
      battle.animText = target.name + ' was bound!';
      break;
    }
    default:
      battle.animText = creature.name + ' used ' + ability.name + '!';
  }

  setTimeout(() => nextTurn(), 800);
}

// ── function: checkFaint ──
function checkFaint(creature, index, side) {
  if (creature.currentHp <= 0) {
    if (side === 'player') {
      battle.animText = creature.name + ' fainted!';
      checkEnemyFaint(index);
    } else {
      battle.animText = creature.name + ' fainted!';
      checkPlayerFaint(index);
    }
  } else {
    setTimeout(() => nextTurn(), 600);
  }
}

// ── function: checkEnemyFaint ──
function checkEnemyFaint(index) {
  if (battle.enemies[index] && battle.enemies[index].currentHp <= 0) {
    battle.enemies.splice(index, 1);
    if (battle.enemies.length === 0) {
      setTimeout(() => handleVictory(), 800);
      return;
    }
  }
  setTimeout(() => nextTurn(), 600);
}

// ── function: checkPlayerFaint ──
function checkPlayerFaint(index) {
  updateActiveCreature();
  if (battle.playerParty.filter(c => c.currentHp > 0).length === 0) {
    battle.phase = 'defeat';
    renderBattleUI();
  }
}

// ── function: nextTurn ──
function nextTurn() {
  battle.animating = false;
  battleAnim.active = false;

  processStatusEffects();

  if (battle.phase === 'victory' || battle.phase === 'defeat') return;

  const currentCreature = battle.turnOrder[battle.currentTurnIndex];
  if (!currentCreature) {
    battle.currentTurnIndex = 0;
    nextTurn();
    return;
  }

  if (currentCreature.creature.currentHp <= 0) {
    battle.currentTurnIndex = (battle.currentTurnIndex + 1) % battle.turnOrder.length;
    nextTurn();
    return;
  }

  if (currentCreature.creature.statusEffects && currentCreature.creature.statusEffects.includes('shock')) {
    if (Math.random() < 0.3) {
      battle.animText = currentCreature.creature.name + ' is shocked and can\'t move!';
      renderBattleUI();
      setTimeout(() => {
        battle.currentTurnIndex = (battle.currentTurnIndex + 1) % battle.turnOrder.length;
        nextTurn();
      }, 800);
      return;
    }
  }

  battle.activePlayerCreature = currentCreature.creature;

  if (currentCreature.type === 'player') {
    battle.phase = 'select';
    renderBattleUI();
  } else {
    battle.phase = 'animating';
    renderBattleUI();
    setTimeout(() => enemyTurn(currentCreature), 600);
  }

  battle.currentTurnIndex = (battle.currentTurnIndex + 1) % battle.turnOrder.length;
}

// ── function: processStatusEffects ──
function processStatusEffects() {
  battle.playerParty.forEach(c => {
    if (c.currentHp <= 0) return;
    c.statusEffects.forEach(se => {
      const effect = STATUS_EFFECTS[se];
      if (!effect) return;
      if (effect.damage) {
        const dmg = Math.floor(getMaxHp(c) * effect.damage);
        c.currentHp = Math.max(0, c.currentHp - dmg);
      }
      if (effect.healPerTurn) {
        c.currentHp = Math.min(getMaxHp(c), c.currentHp + Math.floor(getMaxHp(c) * effect.healPerTurn));
      }
    });
  });

  battle.enemies.forEach(e => {
    if (e.currentHp <= 0) return;
    e.statusEffects.forEach(se => {
      const effect = STATUS_EFFECTS[se];
      if (!effect || !effect.damage) return;
      const dmg = Math.floor(getMaxHp(e) * effect.damage);
      e.currentHp = Math.max(0, e.currentHp - dmg);
    });
  });

  battle.enemies = battle.enemies.filter(e => e.currentHp > 0);
  updateActiveCreature();
}

// ── function: enemyTurn ──
function enemyTurn(enemyEntry) {
  const enemy = enemyEntry.creature;
  const abilityId = enemy.abilities[Math.floor(Math.random() * enemy.abilities.length)];
  const ability = ABILITIES[abilityId];
  if (!ability) { nextTurn(); return; }

  const alivePlayers = battle.playerParty.filter(c => c.currentHp > 0);
  if (alivePlayers.length === 0) { nextTurn(); return; }
  const target = alivePlayers[Math.floor(Math.random() * alivePlayers.length)];
  const targetIndex = battle.playerParty.indexOf(target);

  if (target.statusEffects.includes('evasion_up') && Math.random() < 0.4) {
    battle.animText = target.name + ' evaded the attack!';
    renderBattleUI();
    setTimeout(() => nextTurn(), 800);
    return;
  }

  executeMove(enemy, ability, target, targetIndex, 'enemy');
}

// ── function: handleVictory ──
function handleVictory() {
  battle.phase = 'victory';

  battle.playerParty.forEach(c => {
    if (c.currentHp <= 0) return;
    const exp = calcExp(c, battle.encounter.creature.level, battle.isBossBattle);
    c.exp += exp;
    checkLevelUp(c);
  });

  battle.playerParty.forEach(c => {
    const template = CREATURES[c.id];
    if (template && template.evolve && c.level >= template.evolve.atLevel) {
      if (hasItem(template.evolve.catalyst, 1)) {
        c.id = template.evolve.to;
        c.name = CREATURES[c.id].name;
        c.emoji = CREATURES[c.id].emoji;
        c.affinity = CREATURES[c.id].affinity;
        c.baseStats = { ...CREATURES[c.id].baseStats };
        c.growth = { ...CREATURES[c.id].growth };
        c.abilities = [...CREATURES[c.id].abilities];
        c.evolved = true;
        c.currentHp = getMaxHp(c);
        removeItem(template.evolve.catalyst, 1);
        battle.animText = c.name + ' transformed!';
      }
    }
  });

  if (battle.isBossBattle && battle.encounter.anchor) {
    const anchorIdx = ANCHORS.indexOf(battle.encounter.anchor);
    if (anchorIdx >= 0 && !game.anchorsDefeated.includes(anchorIdx)) {
      game.anchorsDefeated.push(anchorIdx);
      const anchor = battle.encounter.anchor;
      battle.playerParty.forEach(c => {
        if (c.affinityBonus) {
          c.affinityBonus[anchor.bonus] = (c.affinityBonus[anchor.bonus] || 0) + anchor.bonusVal;
        } else {
          c.affinityBonus = { [anchor.bonus]: anchor.bonusVal };
        }
      });
      if (anchorIdx < 3) ZONES[anchorIdx + 1].unlocked = true;
      if (game.anchorsDefeated.length >= 4) game.postGame = true;
      saveGame(1);
    }
  }

  if (!battle.isBossBattle && Math.random() < 0.15) {
    addItem('aether_draught', 1);
  }

  renderBattleUI();
  playVictorySound();
}

// ── function: endBattle ──
function endBattle() {
  showScreen('overworld');
  resizeOverworldCanvas();
  initZoneParticles(game.zone);
  playZoneAmbient(game.zone);
}

// ── function: retreatFromBattle ──
function retreatFromBattle() {
  showScreen('overworld');
  resizeOverworldCanvas();
  initZoneParticles(game.zone);
  playZoneAmbient(game.zone);
}

// ── function: tryRun ──
function tryRun() {
  if (battle.isBossBattle) {
    battle.animText = "Can't run from a boss!";
    renderBattleUI();
    setTimeout(() => { battle.phase = 'select'; renderBattleUI(); }, 800);
    return;
  }
  if (Math.random() < 0.6) {
    battle.animText = 'Got away safely!';
    renderBattleUI();
    setTimeout(() => endBattle(), 800);
  } else {
    battle.animText = "Couldn't escape!";
    renderBattleUI();
    setTimeout(() => { battle.phase = 'select'; renderBattleUI(); }, 800);
  }
}

// ── let: captureState ──
// ===== CAPTURE SYSTEM =====
let captureState = { active: false, ringRotation: 0, targetAngle: 0, zoneWidth: 0, zoneStart: 0, captured: false, creature: null, timer: 0, maxTime: 180 };

// ── function: startCapture ──
function startCapture() {
  if (battle.phase !== 'select') return;
  const targetEnemy = battle.enemies[0];
  if (!targetEnemy || targetEnemy.currentHp <= 0) return;

  captureState = {
    active: true, ringRotation: 0,
    targetAngle: Math.random() * Math.PI * 2,
    zoneWidth: 0.3 + Math.random() * 0.4,
    zoneStart: Math.random() * Math.PI * 2,
    captured: false, creature: targetEnemy,
    timer: 0, maxTime: 180
  };

  showScreen('capture');
  const emojiEl = document.getElementById('capture-creature-emoji');
  if (emojiEl) emojiEl.textContent = targetEnemy.emoji;
}

// ── function: tapCapture ──
function tapCapture() {
  if (!captureState.active) return;

  const diff = Math.abs(captureState.ringRotation - captureState.targetAngle);
  const normalizedDiff = Math.min(diff, Math.PI * 2 - diff);

  if (normalizedDiff < captureState.zoneWidth / 2) {
    captureState.captured = true;
    captureState.active = false;

    const hpPercent = captureState.creature.currentHp / getMaxHp(captureState.creature);
    const captureRate = CREATURES[captureState.creature.id]?.captureRate || 150;
    let chance = (captureRate / 300) * (1 - hpPercent * 0.5);
    if (hasItem('prism_crystal', 1)) chance *= 4;
    else if (hasItem('void_orb', 1)) chance *= 2;

    if (Math.random() < chance) playCaptureSound();
    else playTone(200, 0.3, 'sine', 0.08);

    showCaptureResult();
  } else {
    playTone(150, 0.1, 'sine', 0.06);
    const zoneEl = document.getElementById('capture-zone');
    if (zoneEl) {
      zoneEl.style.borderColor = '#f87171';
      setTimeout(() => { zoneEl.style.borderColor = 'transparent'; }, 200);
    }
  }
}

// ── function: showCaptureResult ──
function showCaptureResult() {
  const resultEl = document.getElementById('capture-result');
  if (!resultEl) return;

  if (captureState.captured) {
    const newCreature = createCreature(captureState.creature.id, captureState.creature.level);
    if (game.party.length < 6) {
      game.party.push(newCreature);
      resultEl.innerHTML = '<div style="color:#4ade80;font-size:1.2em;margin-bottom:10px;">✨ ' + newCreature.name + ' captured!</div><button class="capture-btn" onclick="hideCaptureGame()">OK</button>';
      game.collected.push(captureState.creature.id);
      game.totalCaptures++;
    } else {
      resultEl.innerHTML = '<div style="color:#fbbf24;font-size:1.2em;margin-bottom:10px;">Party is full! ' + newCreature.name + ' added to storage.</div><button class="capture-btn" onclick="hideCaptureGame()">OK</button>';
      game.collected.push(captureState.creature.id);
      game.totalCaptures++;
    }
  } else {
    resultEl.innerHTML = '<div style="color:#f87171;font-size:1.2em;margin-bottom:10px;">💔 ' + captureState.creature.name + ' broke free!</div><button class="capture-btn" onclick="hideCaptureGame()">OK</button>';
  }
}

// ── function: hideCaptureGame ──
function hideCaptureGame() {
  document.getElementById('capture-game').style.display = 'none';
  captureState.active = false;
  if (captureState.captured) {
    const idx = battle.enemies.indexOf(captureState.creature);
    if (idx >= 0) battle.enemies.splice(idx, 1);
    if (battle.enemies.length === 0) handleVictory();
    else { battle.phase = 'select'; showScreen('battle'); renderBattleUI(); }
  } else {
    battle.phase = 'select';
    showScreen('battle');
    renderBattleUI();
  }
}

// ── function: openShrine ──
// ===== SHRINE SYSTEM =====
function openShrine() {
  showScreen('shrine');
  const panel = document.getElementById('shrine-panel');
  if (panel) {
    panel.innerHTML = '<h2>⛩️ Aether Shrine</h2><p style="color:var(--text-dim);margin-bottom:16px;">The shrine hums with Aetherium energy.</p>' +
      '<button class="shrine-btn" onclick="shrineHeal()">💚 Heal Party</button>' +
      '<button class="shrine-btn" onclick="shrineSave()">💾 Save</button>' +
      '<button class="shrine-btn" onclick="shrineShop()">🛒 Shop</button>' +
      '<button class="shrine-btn" onclick="shrineClose()">🚪 Leave</button>';
  }
}

// ── function: shrineHeal ──
function shrineHeal() {
  game.party.forEach(c => { c.currentHp = getMaxHp(c); c.statusEffects = []; });
  openShrine();
}

// ── function: shrineSave ──
function shrineSave() {
  saveGame(1);
  openShrine();
}

// ── function: shrineShop ──
function shrineShop() {
  hideModal();
  showModal('<h2>🛒 Shrine Shop</h2><div style="max-height:300px;overflow-y:auto;">' +
    '<div class="inv-item"><span class="item-name">Aether Draught (30HP)</span><span class="item-count">50g</span><button class="item-btn" onclick="buyItem(\'aether_draught\',50)">Buy</button></div>' +
    '<div class="inv-item"><span class="item-name">Great Aether (80HP)</span><span class="item-count">150g</span><button class="item-btn" onclick="buyItem(\'great_aether\',150)">Buy</button></div>' +
    '<div class="inv-item"><span class="item-name">Full Restoration</span><span class="item-count">500g</span><button class="item-btn" onclick="buyItem(\'full_restoration\',500)">Buy</button></div>' +
    '<div class="inv-item"><span class="item-name">Aether Ball</span><span class="item-count">100g</span><button class="item-btn" onclick="buyItem(\'aether_ball\',100)">Buy</button></div>' +
    '<div class="inv-item"><span class="item-name">Void Orb</span><span class="item-count">300g</span><button class="item-btn" onclick="buyItem(\'void_orb\',300)">Buy</button></div>' +
    '<div class="inv-item"><span class="item-name">Prism Crystal</span><span class="item-count">800g</span><button class="item-btn" onclick="buyItem(\'prism_crystal\',800)">Buy</button></div>' +
    '</div><button class="battle-btn" style="margin-top:12px;" onclick="hideModal()">Close</button>');
}

// ── function: buyItem ──
function buyItem(itemId, price) {
  addItem(itemId, 1);
  playTone(600, 0.1, 'sine', 0.08);
}

// ── function: shrineClose ──
function shrineClose() {
  showScreen('overworld');
  resizeOverworldCanvas();
  initZoneParticles(game.zone);
}

// ── function: showSwitchMenu ──
// ===== SWITCH / ITEM MENUS =====
function showSwitchMenu() {
  let html = '<h2>🔄 Switch Creature</h2><div style="max-height:250px;overflow-y:auto;">';
  game.party.forEach((c, i) => {
    const hpPct = c.currentHp / getMaxHp(c);
    const color = hpPct > 0.5 ? '#4ade80' : hpPct > 0.2 ? '#fbbf24' : '#f87171';
    const isCurrent = battle.activePlayerCreature && battle.activePlayerCreature.id === c.id && battle.activePlayerCreature.currentHp > 0;
    html += '<div class="inv-item" style="' + (isCurrent ? 'opacity:0.5;' : '') + '">' +
      '<span class="item-name">' + c.emoji + ' ' + c.name + ' Lv.' + c.level + ' <span style="color:' + color + '">' + c.currentHp + '/' + getMaxHp(c) + '</span></span>' +
      (isCurrent ? '<span style="color:var(--text-dim)">Active</span>' : '<button class="item-btn" onclick="switchCreature(' + i + ')">Switch</button>') +
      '</div>';
  });
  html += '</div><button class="battle-btn" style="margin-top:12px;" onclick="hideModal()">Cancel</button>';
  showModal(html);
}

// ── function: switchCreature ──
function switchCreature(index) {
  const target = game.party[index];
  if (target.currentHp <= 0) { hideModal(); return; }
  battle.activePlayerCreature = target;
  hideModal();
  battle.phase = 'select';
  renderBattleUI();
  playTone(440, 0.1, 'sine', 0.08);
}

// ── function: showItemMenu ──
function showItemMenu() {
  let html = '<h2>🎒 Use Item</h2><div style="max-height:250px;overflow-y:auto;">';
  ['aether_draught', 'great_aether', 'full_restoration'].forEach(itemId => {
    if ((game.inventory[itemId] || 0) > 0) {
      const item = ITEMS[itemId];
      html += '<div class="inv-item"><span class="item-name">' + item.name + ' (' + game.inventory[itemId] + ')</span><button class="item-btn" onclick="useItem(\'' + itemId + '\')">Use</button></div>';
    }
  });
  html += '</div><button class="battle-btn" style="margin-top:12px;" onclick="hideModal()">Cancel</button>';
  showModal(html);
}

// ── function: useItem ──
function useItem(itemId) {
  if (!hasItem(itemId, 1)) return;
  const item = ITEMS[itemId];
  if (item.type === 'restore') {
    if (item.full) {
      game.party.forEach(c => { c.currentHp = getMaxHp(c); c.statusEffects = []; });
      battle.activePlayerCreature = game.party.find(c => c.currentHp > 0) || battle.activePlayerCreature;
    } else {
      battle.activePlayerCreature.currentHp = Math.min(getMaxHp(battle.activePlayerCreature), battle.activePlayerCreature.currentHp + item.heal);
    }
    removeItem(itemId, 1);
    playTone(500, 0.15, 'sine', 0.08);
  }
  hideModal();
  battle.phase = 'select';
  renderBattleUI();
}

// ── function: showEncyclopedia ──
// ===== ENCYCLOPEDIA / PARTY / INVENTORY =====
function showEncyclopedia() {
  let html = '<h2>📖 Aetherial Codex</h2><div style="max-height:350px;overflow-y:auto;">';
  game.collected.forEach(cid => {
    const c = CREATURES[cid];
    if (!c) return;
    html += '<div class="encyc-entry"><div class="icon" style="background:' + TYPE_COLORS[c.affinity] + '22;">' + c.emoji + '</div>' +
      '<div class="info"><div class="name" style="color:' + TYPE_COLORS[c.affinity] + '">' + c.name + '</div>' +
      '<div class="detail">' + TYPE_NAMES[c.affinity] + ' | ' + c.lore + '</div></div></div>';
  });
  Object.keys(CREATURES).forEach(cid => {
    if (game.collected.includes(cid)) return;
    const c = CREATURES[cid];
    html += '<div class="encyc-entry" style="opacity:0.4;"><div class="icon" style="background:' + TYPE_COLORS[c.affinity] + '22;">❓</div>' +
      '<div class="info"><div class="name">???</div><div class="detail">Not yet discovered</div></div></div>';
  });
  html += '</div><button class="battle-btn" style="margin-top:12px;" onclick="hideModal()">Close</button>';
  showModal(html);
}

// ── function: showPartyScreen ──
function showPartyScreen() {
  let html = '<h2>👥 Party</h2><div style="max-height:300px;overflow-y:auto;">';
  game.party.forEach(c => {
    const hpPct = c.currentHp / getMaxHp(c);
    const color = hpPct > 0.5 ? '#4ade80' : hpPct > 0.2 ? '#fbbf24' : '#f87171';
    html += '<div class="encyc-entry"><div class="icon" style="background:' + TYPE_COLORS[c.affinity] + '22;">' + c.emoji + '</div>' +
      '<div class="info"><div class="name" style="color:' + TYPE_COLORS[c.affinity] + '">' + c.name + ' Lv.' + c.level + '</div>' +
      '<div class="detail">HP: <span style="color:' + color + '">' + c.currentHp + '/' + getMaxHp(c) + '</span> | ATK:' + getStat(c, 'atk') + ' DEF:' + getStat(c, 'def') + ' SPD:' + getStat(c, 'spd') + '</div>' +
      '<div class="detail">Abilities: ' + c.abilities.map(a => ABILITIES[a] ? ABILITIES[a].name : '?').join(', ') + '</div></div></div>';
  });
  html += '</div><button class="battle-btn" style="margin-top:12px;" onclick="hideModal()">Close</button>';
  showModal(html);
}

// ── function: showInventoryScreen ──
function showInventoryScreen() {
  let html = '<h2>🎒 Inventory</h2><div style="max-height:300px;overflow-y:auto;">';
  Object.keys(game.inventory).forEach(itemId => {
    const item = ITEMS[itemId];
    if (!item) return;
    html += '<div class="inv-item"><span class="item-name">' + item.name + ' (' + game.inventory[itemId] + ')</span><span class="item-count">' + item.desc + '</span></div>';
  });
  if (Object.keys(game.inventory).length === 0) html += '<p style="color:var(--text-dim);text-align:center;padding:20px;">Empty</p>';
  html += '</div><button class="battle-btn" style="margin-top:12px;" onclick="hideModal()">Close</button>';
  showModal(html);
}

// ── function: showStoryVignette ──
// ===== STORY VIGNETTE =====
function showStoryVignette(text, color, duration, callback) {
  const vignette = document.getElementById('story-vignette');
  const textEl = document.getElementById('vignette-text');
  vignette.style.background = 'radial-gradient(ellipse at center, ' + color + '22 0%, rgba(0,0,0,0.9) 70%)';
  textEl.textContent = text;
  textEl.style.color = color;
  showScreen('vignette');

  const timer = setTimeout(() => { if (callback) callback(); }, duration || 4000);
  vignette.onclick = () => { clearTimeout(timer); if (callback) callback(); };
}

// ── function: showPauseMenu ──
// ===== PAUSE MENU =====
function showPauseMenu() {
  showModal('<h2>⏸️ Paused</h2>' +
    '<button class="pause-btn" onclick="hideModal();game.screen=\'overworld\';showScreen(\'overworld\');">▶️ Resume</button>' +
    '<button class="pause-btn" onclick="showEncyclopedia();">📖 Codex (' + game.collected.length + '/27)</button>' +
    '<button class="pause-btn" onclick="showPartyScreen();">👥 Party</button>' +
    '<button class="pause-btn" onclick="showInventoryScreen();">🎒 Inventory</button>' +
    '<button class="pause-btn" onclick="saveGame(1);hideModal();">💾 Save</button>' +
    '<button class="pause-btn" onclick="toggleMute();showPauseMenu();">' + (game.audioMuted ? '🔊' : '🔇') + ' Audio</button>' +
    '<button class="pause-btn" onclick="hideModal();showScreen(\'title\');">🏠 Title</button>');
}

// ── const: keys ──
// ===== OVERWORLD MOVEMENT =====
const keys = {};

// ── let: moveCooldown ──
let moveCooldown = 0;

// ── function: handleOverworldMovement ──
function handleOverworldMovement() {
  if (game.screen !== 'overworld') return;
  if (moveCooldown > 0) { moveCooldown--; return; }

  let dx = 0, dy = 0;
  if (keys['ArrowUp'] || keys['w'] || keys['W'] || keys['TouchUp']) dy = -1;
  if (keys['ArrowDown'] || keys['s'] || keys['S'] || keys['TouchDown']) dy = 1;
  if (keys['ArrowLeft'] || keys['a'] || keys['A'] || keys['TouchLeft']) dx = -1;
  if (keys['ArrowRight'] || keys['d'] || keys['D'] || keys['TouchRight']) dx = 1;

  if (dx !== 0 || dy !== 0) {
    const newX = game.playerX + dx;
    const newY = game.playerY + dy;
    if (newX >= 0 && newX < MAP_W && newY >= 0 && newY < MAP_H) {
      const tile = overworldMap[newY][newX];
      if (tile !== 1 && tile !== 2 && tile !== 4) {
        game.playerX = newX;
        game.playerY = newY;
        game.moveCount++;
        moveCooldown = 8;

        const zone = ZONES[game.zone];
        const encounterChance = game.postGame ? zone.encounterRate * 1.5 : zone.encounterRate;
        if (Math.random() < encounterChance) {
          startBattle(generateWildEncounter(), false);
        }

        if (tile === 3) setTimeout(() => openShrine(), 200);
        if (tile === 5) {
          const anchorIdx = game.zone;
          if (anchorIdx < ANCHORS.length && !game.anchorsDefeated.includes(anchorIdx)) {
            const bossEncounter = generateBossEncounter(anchorIdx);
            startBattle(bossEncounter.creature, true);
          }
        }

        if (game.zone < ZONES.length - 1 && game.playerX === MAP_W - 3 && game.playerY === 3) {
          if (ZONES[game.zone + 1].unlocked) {
            transitionToZone(game.zone + 1);
          }
        }
      }
    }
  }
}

// ── function: transitionToZone ──
function transitionToZone(zoneId) {
  game.zone = zoneId;
  overworldMap = generateMap(zoneId);
  game.playerX = 5;
  game.playerY = 10;

  const zone = ZONES[zoneId];
  game.storyStage = zoneId;
  showStoryVignette(zone.story, zone.storyColor, 3500, () => {
    showScreen('overworld');
    resizeOverworldCanvas();
    initZoneParticles(zoneId);
    playZoneAmbient(zoneId);
  });
}

// ── function: showTutorial ──
// ===== TUTORIAL =====
function showTutorial() {
  const phases = [
    { text: 'Welcome to the Aetherium, Traveler. The realm\'s energy is fracturing.', color: '#6ee7b7' },
    { text: 'Use WASD or Arrow Keys to move. Explore the zones to find Aetherials.', color: '#818cf8' },
    { text: 'Wild Aetherials roam the zones. Battle them and capture with Aether Balls!', color: '#fb923c' },
    { text: 'Defeat the zone Guardians to unlock new realms. Good luck!', color: '#fde68a' }
  ];

  let phase = 0;
  function nextPhase() {
    if (phase < phases.length) {
      showStoryVignette(phases[phase].text, phases[phase].color, 3000, () => { phase++; nextPhase(); });
    } else {
      game.party = [createCreature('emberlit', 5), createCreature('stonewisp', 5), createCreature('thornling', 5)];
      addItem('aether_ball', 5);
      addItem('aether_draught', 3);
      game.tutorialPhase = 1;
      showScreen('overworld');
      resizeOverworldCanvas();
      initZoneParticles(0);
      playZoneAmbient(0);
    }
  }
  nextPhase();
}

// ── function: initTitleScreen ──
// ===== TITLE SCREEN =====
function initTitleScreen() {
  const newBtn = document.getElementById('new-binding');
  if (newBtn) newBtn.onclick = startNewGame;

  const continueBtn = document.getElementById('continue-btn');
  if (hasSave(1)) {
    continueBtn.style.display = 'block';
    continueBtn.onclick = () => {
      loadGame(1);
      game.screen = 'overworld';
      showScreen('overworld');
      resizeOverworldCanvas();
      initZoneParticles(game.zone);
      playZoneAmbient(game.zone);
    };
  } else {
    continueBtn.style.display = 'none';
  }
}

// ── let: titleParticles ──
// ===== TITLE PARTICLES =====
let titleParticles = [];

// ── function: initTitleParticles ──
function initTitleParticles() {
  titleParticles = [];
  for (let i = 0; i < 50; i++) {
    titleParticles.push({
      x: Math.random() * (window.innerWidth || 800),
      y: Math.random() * (window.innerHeight || 600),
      vx: (Math.random() - 0.5) * 0.5,
      vy: -Math.random() * 0.5,
      size: 1 + Math.random() * 3,
      alpha: 0.2 + Math.random() * 0.5,
      color: ['#6ee7b7', '#818cf8', '#fde68a', '#06b6d4'][Math.floor(Math.random() * 4)],
      pulse: Math.random() * Math.PI * 2
    });
  }
}

// ── function: drawTitleParticles ──
function drawTitleParticles() {
  const canvas = document.getElementById('title-canvas');
  if (!canvas) return;
  const ctx = canvas.getContext('2d');
  const w = canvas.width;
  const h = canvas.height;
  ctx.clearRect(0, 0, w, h);

  for (const p of titleParticles) {
    p.x += p.vx; p.y += p.vy; p.pulse += 0.02;
    if (p.y < -10) { p.y = h + 10; p.x = Math.random() * w; }
    if (p.x < -10) p.x = w + 10;
    if (p.x > w + 10) p.x = -10;

    const alpha = p.alpha * (0.7 + 0.3 * Math.sin(p.pulse));
    ctx.globalAlpha = alpha;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
}

// ── function: startNewGame ──
// ===== NEW GAME =====
function startNewGame() {
  initAudio();
  game = {
    screen: 'overworld', zone: 0, playerX: 5, playerY: 5,
    party: [], inventory: {}, collected: [], anchorsDefeated: [],
    storyStage: 0, totalBattles: 0, totalCaptures: 0,
    shrineSave: null, audioMuted: false, postGame: false,
    tutorialPhase: 0, moveCount: 0, hasMoved: false,
    firstBattle: false, firstCapture: false
  };
  overworldMap = generateMap(0);
  ZONES.forEach((z, i) => { z.unlocked = i === 0; });

  showScreen('overworld');
  resizeOverworldCanvas();
  initZoneParticles(0);
  playZoneAmbient(0);

  setTimeout(() => showTutorial(), 500);
}

// ── function: setupKeyboard ──
// ===== KEYBOARD INPUT =====
function setupKeyboard() {
  document.addEventListener('keydown', (e) => {
    keys[e.key] = true;
    if (game.screen === 'overworld' && (e.key === 'Escape' || e.key === 'p')) showPauseMenu();
    if (game.screen === 'title' && e.key === 'Enter') startNewGame();
  });
  document.addEventListener('keyup', (e) => { keys[e.key] = false; });
}

// ── function: setupTouchControls ──
// ===== TOUCH CONTROLS =====
function setupTouchControls() {
  const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
  if (isMobile) {
    document.getElementById('touch-controls').style.display = 'block';
    document.getElementById('touch-actions').style.display = 'flex';

    ['up', 'down', 'left', 'right'].forEach(dir => {
      const btn = document.getElementById('btn-' + dir);
      if (btn) {
        btn.addEventListener('touchstart', (e) => { e.preventDefault(); keys['Touch' + dir.charAt(0).toUpperCase() + dir.slice(1)] = true; });
        btn.addEventListener('touchend', (e) => { e.preventDefault(); keys['Touch' + dir.charAt(0).toUpperCase() + dir.slice(1)] = false; });
        btn.addEventListener('mousedown', (e) => { keys['Touch' + dir.charAt(0).toUpperCase() + dir.slice(1)] = true; });
        btn.addEventListener('mouseup', (e) => { keys['Touch' + dir.charAt(0).toUpperCase() + dir.slice(1)] = false; });
      }
    });

    document.getElementById('touch-actions').querySelectorAll('.action-btn').forEach(btn => {
      btn.addEventListener('touchstart', (e) => { e.preventDefault(); btn.click(); });
    });
  }
}

// ── function: updateHUD ──
// ===== HUD UPDATE =====
function updateHUD() {
  const zoneName = document.getElementById('hud-zone-name');
  if (zoneName && game.screen === 'overworld') zoneName.textContent = ZONES[game.zone].name;

  const partyEl = document.getElementById('hud-party');
  if (partyEl && game.screen === 'overworld') {
    let html = '';
    game.party.forEach((c, i) => {
      const hpPct = c.currentHp / getMaxHp(c);
      const color = hpPct > 0.5 ? '#4ade80' : hpPct > 0.2 ? '#fbbf24' : '#f87171';
      html += '<div class="party-dot" style="background:' + color + '33;border-color:' + color + ';">' + c.emoji + '</div>';
    });
    partyEl.innerHTML = html;
  }

  const muteBtn = document.getElementById('mute-btn');
  if (muteBtn) muteBtn.textContent = game.audioMuted ? '🔇' : '🔊';
}

// ── let: lastTime ──
// ===== MAIN GAME LOOP =====
let lastTime = 0;

// ── let: frameCount ──
let frameCount = 0;

// ── function: gameLoop ──
function gameLoop(timestamp) {
  const dt = timestamp - lastTime;
  lastTime = timestamp;
  frameCount++;

  if (game.screen === 'overworld') {
    handleOverworldMovement();
    updateZoneParticles();
    updateParticles();
    updateFloatingNumbers();
    drawOverworld();
    updateHUD();
  } else if (game.screen === 'battle') {
    updateBattleAnim();
    updateParticles();
    updateFloatingNumbers();
    drawBattleScene();
    updateHUD();

    // Update capture ring
    if (captureState.active) {
      captureState.ringRotation = (captureState.ringRotation + 0.05) % (Math.PI * 2);
      captureState.timer++;
      const indicator = document.getElementById('capture-indicator');
      if (indicator) indicator.style.transform = 'rotate(' + (captureState.ringRotation * 180 / Math.PI) + 'deg)';
      if (captureState.timer >= captureState.maxTime) {
        const hpPercent = captureState.creature.currentHp / getMaxHp(captureState.creature);
        const captureRate = CREATURES[captureState.creature.id]?.captureRate || 150;
        let chance = (captureRate / 300) * (1 - hpPercent * 0.5);
        if (hasItem('prism_crystal', 1)) chance *= 4;
        else if (hasItem('void_orb', 1)) chance *= 2;
        captureState.captured = Math.random() < chance;
        captureState.active = false;
        showCaptureResult();
      }
    }
  } else if (game.screen === 'title') {
    drawTitleParticles();
  }

  requestAnimationFrame(gameLoop);
}

// ── main ──
// ===== MAIN INITIALIZATION =====
function init() {
  initTitleScreen();
  initTitleParticles();
  setupKeyboard();
  setupTouchControls();
  updateHUD();
  requestAnimationFrame(gameLoop);
}

window.addEventListener('load', init);
window.addEventListener('resize', () => {
  if (game.screen === 'overworld') resizeOverworldCanvas();
  if (game.screen === 'battle') resizeBattleCanvas();
  if (game.screen === 'title') initTitleParticles();
});
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-35b-a3b","provider":"lmstudio","persona":"composer","sessionId":"25fb5ad4-77ab-4acb-8d64-23401b45b246","tokensIn":3505178,"tokensOut":72528,"tokensTotal":3577706,"turns":52,"toolCalls":51,"failedToolCalls":3,"timestamp":"2026-07-20T23:18:01.308Z"} -->