← Creature Collection results

Creature Collection

thinkingcap qwen3.6 27b · 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 82,518 bytes · SHA-256 92252b4e6485
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Orynthia: Resonance of the Luminari</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a12; font-family: 'Segoe UI', system-ui, sans-serif; color: #e8e6f0; user-select: none; }
#app { position: relative; width: 100vw; height: 100vh; overflow: hidden; }
canvas { display: block; width: 100%; height: 100%; image-rendering: pixelated; }
.overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; z-index: 10; pointer-events: none; }
.overlay > * { pointer-events: auto; }
.hidden { display: none !important; }

/* Title Screen */
.title-content { text-align: center; animation: titleFloat 3s ease-in-out infinite alternate; }
.game-title { font-size: clamp(2.5rem, 8vw, 5rem); font-weight: 300; letter-spacing: 0.3em; color: #c4b5fd; text-shadow: 0 0 40px rgba(167,139,250,0.5), 0 0 80px rgba(167,139,250,0.2); margin-bottom: 0.2em; }
.subtitle { font-size: clamp(0.8rem, 2vw, 1.2rem); letter-spacing: 0.5em; color: #a78bfa; opacity: 0.7; margin-bottom: 3em; text-transform: uppercase; }

@keyframes titleFloat {
  from { transform: translateY(0); }
  to { transform: translateY(-10px); }
}

/* Buttons */
.menu-btn { padding: 12px 32px; margin: 6px; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; transition: all 0.2s; letter-spacing: 0.05em; }
.menu-btn.primary { background: linear-gradient(135deg, #7c3aed, #a78bfa); color: white; box-shadow: 0 4px 15px rgba(124,58,237,0.3); }
.menu-btn.secondary { background: rgba(255,255,255,0.1); color: #e8e6f0; border: 1px solid rgba(255,255,255,0.15); }
.menu-btn.small { padding: 8px 16px; font-size: 0.85rem; background: rgba(255,255,255,0.08); color: #d4d0e8; border: 1px solid rgba(255,255,255,0.1); }
.menu-btn:hover { transform: translateY(-2px); filter: brightness(1.15); }
.menu-btn:active { transform: translateY(0); }

/* Glass Panels */
.glass-panel { background: rgba(15, 12, 30, 0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-radius: 16px; padding: 24px; max-width: 90vw; max-height: 85vh; overflow-y: auto; border: 1px solid rgba(167,139,250,0.2); box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
.glass-panel h3 { color: #c4b5fd; margin-bottom: 16px; font-weight: 400; letter-spacing: 0.1em; text-align: center; }

/* Dialog Boxes */
.dialog-box { background: rgba(15, 12, 30, 0.9); backdrop-filter: blur(8px); border-radius: 16px; padding: 32px; max-width: 600px; width: 90%; text-align: center; border: 1px solid rgba(167,139,250,0.2); }
.dialog-box p { font-size: 1rem; line-height: 1.6; margin-bottom: 24px; color: #d4d0e8; }

/* Battle UI */
#battleUI { position: absolute; bottom: 0; left: 0; width: 100%; z-index: 5; pointer-events: none; }
#enemyArea, #playerParty { padding: 10px; }
#battleMenu { display: flex; justify-content: center; gap: 8px; padding: 10px; pointer-events: auto; }
#battleLog { background: rgba(0,0,0,0.6); color: #b4b0c8; font-size: 0.8rem; padding: 8px 12px; max-height: 60px; overflow-y: auto; pointer-events: auto; }

/* Battle creature cards */
.creature-card { display: inline-block; background: rgba(30,25,50,0.9); border-radius: 10px; padding: 8px 14px; margin: 4px; min-width: 160px; border: 1px solid rgba(167,139,250,0.15); }
.creature-card .name { font-weight: 600; color: #e8e6f0; font-size: 0.9rem; }
.creature-card .level { color: #a78bfa; font-size: 0.75rem; }
.hp-bar-bg { width: 100%; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; margin-top: 4px; overflow: hidden; }
.hp-bar-fill { height: 100%; border-radius: 3px; transition: width 0.5s ease; background: linear-gradient(90deg, #22c55e, #4ade80); }
.hp-bar-fill.low { background: linear-gradient(90deg, #ef4444, #f97316); }
.hp-bar-fill.mid { background: linear-gradient(90deg, #eab308, #facc15); }

/* Ability list */
#abilityList { max-height: 50vh; overflow-y: auto; }
.ability-item { padding: 10px 14px; margin: 4px 0; background: rgba(255,255,255,0.05); border-radius: 8px; cursor: pointer; transition: all 0.15s; display: flex; justify-content: space-between; align-items: center; }
.ability-item:hover { background: rgba(167,139,250,0.2); }
.ability-item .ab-name { font-weight: 500; }
.ability-item .ab-info { font-size: 0.75rem; color: #a8a4c0; }

/* Bag/Team items */
.item-row, .team-row { padding: 10px; margin: 4px 0; background: rgba(255,255,255,0.05); border-radius: 8px; display: flex; justify-content: space-between; align-items: center; }
.item-row:hover, .team-row:hover { background: rgba(167,139,250,0.15); cursor: pointer; }

/* Capture mini-game */
.capture-container { text-align: center; padding: 40px; }
#captureBar { width: 300px; height: 20px; background: rgba(255,255,255,0.1); border-radius: 10px; margin: 20px auto; overflow: hidden; position: relative; }
#captureFill { width: 0%; height: 100%; background: linear-gradient(90deg, #7c3aed, #a78bfa, #c4b5fd); border-radius: 10px; transition: width 0.1s; }

/* Notification */
#notification { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(15,12,30,0.9); padding: 12px 24px; border-radius: 8px; z-index: 100; color: #c4b5fd; font-size: 0.9rem; border: 1px solid rgba(167,139,250,0.3); animation: notifSlide 0.3s ease; }
@keyframes notifSlide { from { opacity: 0; transform: translateX(-50%) translateY(-20px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } }

/* Touch Controls */
#touchControls { position: absolute; bottom: 20px; left: 0; width: 100%; z-index: 8; display: flex; justify-content: space-between; padding: 0 30px; pointer-events: none; }
.dpad { display: grid; grid-template-columns: repeat(3,50px); grid-template-rows: repeat(2,50px); gap: 4px; pointer-events: auto; }
.dpad button { background: rgba(167,139,250,0.3); border: none; color: white; font-size: 1.2rem; border-radius: 8px; cursor: pointer; }
.action-btn { width: 60px; height: 60px; border-radius: 50%; background: rgba(124,58,237,0.5); border: none; color: white; font-size: 1.2rem; cursor: pointer; align-self: center; pointer-events: auto; }

/* Codex */
.codex-entry { padding: 12px; margin: 6px 0; background: rgba(255,255,255,0.04); border-radius: 8px; cursor: pointer; transition: all 0.15s; }
.codex-entry:hover { background: rgba(167,139,250,0.15); }
.codex-entry .ce-name { font-weight: 600; color: #e8e6f0; }
.codex-entry .ce-type { font-size: 0.75rem; color: #a78bfa; }
.codex-entry .ce-lore { font-size: 0.8rem; color: #9894b0; margin-top: 4px; font-style: italic; }

/* Zone map */
.zone-node { display: inline-block; padding: 12px 20px; margin: 6px; border-radius: 8px; background: rgba(255,255,255,0.05); cursor: pointer; transition: all 0.2s; }
.zone-node.unlocked { border: 1px solid rgba(167,139,250,0.3); }
.zone-node.locked { opacity: 0.4; cursor: not-allowed; }

/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(167,139,250,0.3); border-radius: 3px; }
/* ── css: animations ── */
@keyframes pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.03)} }
@keyframes fadeIn { from{opacity:0;transform:translateY(-10px)} to{opacity:1;transform:translateY(0)} }
@keyframes slideUp { from{opacity:0;transform:translateY(20px)} to{opacity:1;transform:translateY(0)} }
@keyframes glow { 0%,100%{box-shadow:0 0 5px rgba(167,139,250,0.3)} 50%{box-shadow:0 0 18px rgba(167,139,250,0.5)} }
@keyframes shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-4px)} 75%{transform:translateX(4px)} }

.creature-card { animation: slideUp 0.3s ease; transition: all 0.3s; }
.creature-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
.glass-panel { animation: fadeIn 0.25s ease; }
.dialog-box { animation: slideUp 0.3s ease; }
.menu-btn.primary { animation: glow 2.5s ease-in-out infinite; }
.capture-tap { animation: pulse 1s ease-in-out infinite; }

/* Type badges */
.type-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; margin-right: 4px; }

/* Battle log scroll */
#battleLog { animation: fadeIn 0.3s ease; }

/* Victory/Game Over effects */
.victory-box { border-color: rgba(250,204,21,0.3); }
.gameover-box { border-color: rgba(239,68,68,0.3); }

/* Zone transition flash */
.zone-flash { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: white; opacity: 0; pointer-events: none; z-index: 50; animation: zoneFlashAnim 0.5s ease-out; }
@keyframes zoneFlashAnim { from{opacity:0.8} to{opacity:0} }

/* HP bar animations */
.hp-bar-fill { transition: width 0.6s cubic-bezier(0.4,0,0.2,1); }

/* Notification bounce */
#notification { animation: notifSlide 0.3s ease, notifFade 0.3s ease 1.7s forwards; }
@keyframes notifFade { to{opacity:0} }

/* Starter selection */
.starter-option { display: inline-block; padding: 16px 24px; margin: 8px; background: rgba(255,255,255,0.05); border-radius: 12px; cursor: pointer; transition: all 0.2s; min-width: 140px; text-align: center; }
.starter-option:hover { background: rgba(167,139,250,0.2); transform: translateY(-3px); }
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="titleScreen" class="overlay">
  <div class="title-content">
    <h1 class="game-title">Orynthia</h1>
    <p class="subtitle">Resonance of the Luminari</p>
    <button id="btnNewGame" class="menu-btn primary">New Binding</button>
    <button id="btnContinue" class="menu-btn secondary">Continue</button>
  </div>
</div>
<div id="tutorialOverlay" class="overlay hidden">
  <div class="dialog-box tutorial-box">
    <p id="tutorialText"></p>
    <button id="btnTutorialNext" class="menu-btn primary">Continue</button>
  </div>
</div>
<div id="dialogueOverlay" class="overlay hidden">
  <div class="dialog-box story-box">
    <p id="dialogueText"></p>
    <button id="btnDialogueNext" class="menu-btn primary">Continue</button>
  </div>
</div>
<div id="battleUI" class="hidden">
  <div id="enemyArea"></div>
  <div id="playerParty"></div>
  <div id="battleMenu">
    <button id="btnFight" class="menu-btn small">Strike</button>
    <button id="btnBag" class="menu-btn small">Bag</button>
    <button id="btnTeam" class="menu-btn small">Team</button>
    <button id="btnCapture" class="menu-btn small">Bind</button>
  </div>
  <div id="battleLog"></div>
</div>
<div id="abilitySelect" class="overlay hidden glass-panel">
  <h3>Choose Strike</h3>
  <div id="abilityList"></div>
  <button id="btnAbilityBack" class="menu-btn small">Back</button>
</div>
<div id="bagMenu" class="overlay hidden glass-panel">
  <h3>Bag</h3>
  <div id="bagList"></div>
  <button id="btnBagClose" class="menu-btn small">Back</button>
</div>
<div id="teamMenu" class="overlay hidden glass-panel">
  <h3>Team</h3>
  <div id="teamList"></div>
  <button id="btnTeamClose" class="menu-btn small">Back</button>
</div>
<div id="encyclopediaMenu" class="overlay hidden glass-panel">
  <h3>Luminari Codex</h3>
  <div id="codexList"></div>
  <button id="btnCodexClose" class="menu-btn small">Back</button>
</div>
<div id="mapMenu" class="overlay hidden glass-panel">
  <h3>Zones of Orynthia</h3>
  <div id="zoneMap"></div>
  <button id="btnMapClose" class="menu-btn small">Back</button>
</div>
<div id="shrineMenu" class="overlay hidden glass-panel">
  <h3>Resonance Shrine</h3>
  <p>Rest and restore your Luminari.</p>
  <button id="btnShrineHeal" class="menu-btn primary">Restore All HP</button>
  <button id="btnShrineShop" class="menu-btn secondary">Trade</button>
  <button id="btnShrineSave" class="menu-btn secondary">Inscribe Binding</button>
  <button id="btnShrineLeave" class="menu-btn small">Depart</button>
</div>
<div id="shopMenu" class="overlay hidden glass-panel">
  <h3>Trade</h3>
  <div id="shopList"></div>
  <p id="shopGold">Resonance Shards: 0</p>
  <button id="btnShopClose" class="menu-btn small">Back</button>
</div>
<div id="captureOverlay" class="overlay hidden">
  <div class="capture-container">
    <h3>Binding Ritual</h3>
    <p id="captureInstruction"></p>
    <div id="captureBar"><div id="captureFill"></div></div>
    <button id="btnCaptureTap" class="menu-btn primary capture-tap">Resonate!</button>
  </div>
</div>
<div id="pauseMenu" class="overlay hidden glass-panel">
  <h3>Pause</h3>
  <button id="btnResume" class="menu-btn primary">Resume</button>
  <button id="btnCodex" class="menu-btn secondary">Luminari Codex</button>
  <button id="btnMap" class="menu-btn secondary">Zone Map</button>
  <button id="btnMute" class="menu-btn small">Toggle Sound</button>
</div>
<div id="victoryOverlay" class="overlay hidden">
  <div class="dialog-box victory-box">
    <h2 id="victoryTitle"></h2>
    <p id="victoryText"></p>
    <button id="btnVictoryContinue" class="menu-btn primary">Continue</button>
  </div>
</div>
<div id="gameOverOverlay" class="overlay hidden">
  <div class="dialog-box gameover-box">
    <h2>Your Luminari Faded...</h2>
    <p>You will awaken at the last shrine.</p>
    <button id="btnGameOverContinue" class="menu-btn primary">Awaken</button>
  </div>
</div>
<div id="touchControls" class="hidden">
  <div class="dpad">
    <button id="btnUp">▲</button>
    <button id="btnLeft">◄</button>
    <button id="btnRight">►</button>
    <button id="btnDown">▼</button>
  </div>
  <button id="btnAction" class="action-btn">A</button>
</div>
<div id="notification" class="hidden"></div>
<script>
'use strict';

// ── const: TYPES ──
const TYPES = ['Flux', 'Terra', 'Veil', 'Rune', 'Ember', 'Prism', 'Aegis'];

// ── const: TYPE_COLORS ──
const TYPE_COLORS = { Flux: '#60a5fa', Terra: '#4ade80', Veil: '#c084fc', Rune: '#f97316', Ember: '#ef4444', Prism: '#facc15', Aegis: '#94a3b8' };

// ── const: TYPE_CHART ──
const TYPE_CHART = {
  Flux:   { strong: ['Terra'], weak: ['Veil'] },
  Terra:  { strong: ['Veil'], weak: ['Flux'] },
  Veil:   { strong: ['Flux'], weak: ['Terra'] },
  Rune:   { strong: ['Flux', 'Veil'], weak: [] },
  Ember:  { strong: ['Rune', 'Terra'], weak: [] },
  Prism:  { strong: ['Flux','Terra','Veil','Rune','Ember','Aegis'], weak: [] },
  Aegis:  { strong: [], weak: ['Prism'] }
};

// ── const: MAX_LEVEL ──
const MAX_LEVEL = 50;

// ── const: TEAM_SIZE ──
const TEAM_SIZE = 6;

// ── const: TILE ──
const TILE = 48;

// ── const: CREATURES ──
const CREATURES = {
  sylphos: { name:'Sylphos', type:'Flux', lore:'A wisp of living current that dances between worlds, drawn to places where energy pools.', baseStats:{hp:45,atk:38,def:28,spd:35}, learnset:[{lv:1,'Lumen Rush':0},{lv:5,'Phase Shift':0},{lv:10,'Flux Surge':0},{lv:16,'Void Step':0},{lv:22,'Resonance Wave':0},{lv:30,'Cascade Break':0},{lv:40,'Omniflux':0}], transform:{name:'Sylphos Prime',level:35,catalyst:'Flux Core'} },
  terragon: { name:'Terragon', type:'Terra', lore:'Born from crystalline soil, its body shifts between stone and living root.', baseStats:{hp:55,atk:30,def:42,spd:22}, learnset:[{lv:1,'Stone Claw':0},{lv:5,'Root Bind':0},{lv:10,'Terra Slam':0},{lv:16,'Bark Shield':0},{lv:22,'Quake Pulse':0},{lv:30,'Mantle Crush':0},{lv:40,'Geolock':0}], transform:{name:'Terragon Prime',level:35,catalyst:'Terra Core'} },
  velaris: { name:'Velaris', type:'Veil', lore:'A shadow-weaver that folds light into patterns only the attuned can perceive.', baseStats:{hp:42,atk:36,def:25,spd:40}, learnset:[{lv:1,'Shadow Bite':0},{lv:5,'Mist Cloak':0},{lv:10,'Veil Slash':0},{lv:16,'Phantom Drain':0},{lv:22,'Eclipse Edge':0},{lv:30,'Null Rift':0},{lv:40,'Voidrend':0}], transform:{name:'Velaris Prime',level:35,catalyst:'Veil Core'} },
  aquorin: { name:'Aquorin', type:'Flux', lore:'Streams of liquid light coalesce into a being that flows through any barrier.', baseStats:{hp:40,atk:32,def:26,spd:38}, learnset:[{lv:1,'Lumen Rush':0},{lv:5,'Phase Shift':0},{lv:10,'Flux Surge':0},{lv:16,'Resonance Wave':0},{lv:24,'Cascade Break':0}], transform:null },
  lithara: { name:'Lithara', type:'Terra', lore:'Ancient stone given sentience, it remembers every footstep that has crossed its path.', baseStats:{hp:58,atk:28,def:45,spd:18}, learnset:[{lv:1,'Stone Claw':0},{lv:5,'Root Bind':0},{lv:10,'Terra Slam':0},{lv:16,'Bark Shield':0},{lv:24,'Quake Pulse':0}], transform:null },
  umbrax: { name:'Umbrax', type:'Veil', lore:'A creature of folded darkness that feeds on forgotten memories.', baseStats:{hp:38,atk:40,def:22,spd:42}, learnset:[{lv:1,'Shadow Bite':0},{lv:5,'Mist Cloak':0},{lv:10,'Veil Slash':0},{lv:16,'Phantom Drain':0},{lv:24,'Eclipse Edge':0}], transform:null },
  runeth: { name:'Runeth', type:'Rune', lore:'Inscribed with living glyphs, it channels ancient power through its crystalline form.', baseStats:{hp:50,atk:42,def:30,spd:28}, learnset:[{lv:1,'Glyph Strike':0},{lv:5,'Rune Ward':0},{lv:10,'Sigil Burst':0},{lv:16,'Inscription':0},{lv:22,'Codex Break':0},{lv:30,'Script Nova':0}], transform:null },
  pyralis: { name:'Pyralis', type:'Rune', lore:'A flame-scribe that writes its thoughts in burning symbols across the air.', baseStats:{hp:44,atk:45,def:26,spd:32}, learnset:[{lv:1,'Glyph Strike':0},{lv:5,'Ember Touch':0},{lv:10,'Sigil Burst':0},{lv:16,'Flame Script':0},{lv:22,'Codex Break':0}], transform:null },
  ignara: { name:'Ignara', type:'Ember', lore:'Born from the heart of a dying star, its warmth sustains nearby life while burning foes.', baseStats:{hp:48,atk:46,def:24,spd:30}, learnset:[{lv:1,'Ember Touch':0},{lv:5,'Flame Script':0},{lv:10,'Inferno Claw':0},{lv:16,'Solar Flare':0},{lv:22,'Cinder Storm':0},{lv:30,'Starfall':0}], transform:null },
  solvex: { name:'Solvex', type:'Ember', lore:'A solar fragment that learned to walk, carrying the fury of a newborn dawn.', baseStats:{hp:46,atk:44,def:28,spd:34}, learnset:[{lv:1,'Ember Touch':0},{lv:5,'Flame Script':0},{lv:10,'Inferno Claw':0},{lv:16,'Solar Flare':0},{lv:24,'Cinder Storm':0}], transform:null },
  prisma: { name:'Prisma', type:'Prism', lore:'Light refracted through a thousand facets, each one holding a different truth.', baseStats:{hp:52,atk:38,def:34,spd:36}, learnset:[{lv:1,'Prismatic Beam':0},{lv:5,'Spectrum Shift':0},{lv:10,'Light Fracture':0},{lv:16,'Rainbow Cascade':0},{lv:22,'Iris Burst':0},{lv:30,'Aurora Wave':0}], transform:null },
  bulwark: { name:'Bulwark', type:'Aegis', lore:'An impenetrable guardian forged from the silence between heartbeats.', baseStats:{hp:65,atk:24,def:50,spd:16}, learnset:[{lv:1,'Iron Guard':0},{lv:5,'Fortify':0},{lv:10,'Shield Bash':0},{lv:16,'Aegis Dome':0},{lv:22,'Impervious':0},{lv:30,'Last Bastion':0}], transform:null },
  nyxara: { name:'Nyxara', type:['Veil','Rune'], lore:'Where shadow meets inscription, a being of forbidden knowledge takes shape.', baseStats:{hp:46,atk:42,def:30,spd:34}, learnset:[{lv:1,'Shadow Bite':0},{lv:5,'Glyph Strike':0},{lv:10,'Veil Slash':0},{lv:16,'Sigil Burst':0},{lv:22,'Eclipse Edge':0},{lv:30,'Null Rift':0}], transform:null },
  aurion: { name:'Aurion', type:['Flux','Prism'], lore:'The first light of creation, still searching for its original purpose.', baseStats:{hp:50,atk:40,def:32,spd:38}, learnset:[{lv:1,'Lumen Rush':0},{lv:5,'Prismatic Beam':0},{lv:10,'Flux Surge':0},{lv:16,'Light Fracture':0},{lv:22,'Resonance Wave':0},{lv:30,'Aurora Wave':0}], transform:null },
  verdant: { name:'Verdant', type:['Terra','Ember'], lore:'Living fire and growing earth intertwined in an eternal dance of renewal.', baseStats:{hp:54,atk:36,def:38,spd:26}, learnset:[{lv:1,'Stone Claw':0},{lv:5,'Ember Touch':0},{lv:10,'Terra Slam':0},{lv:16,'Solar Flare':0},{lv:22,'Quake Pulse':0}], transform:null },
  wardenshade: { name:'Wardenshade', type:['Veil','Aegis'], lore:'The silent guardian of the first threshold, neither friend nor foe.', baseStats:{hp:80,atk:45,def:40,spd:25}, learnset:[{lv:1,'Shadow Bite':0},{lv:5,'Iron Guard':0},{lv:10,'Veil Slash':0},{lv:16,'Aegis Dome':0}], transform:null },
  flamerune: { name:'Flamerune', type:['Rune','Ember'], lore:'An ancient inscription given flame, it burns with the knowledge of ages.', baseStats:{hp:75,atk:52,def:35,spd:28}, learnset:[{lv:1,'Glyph Strike':0},{lv:5,'Flame Script':0},{lv:10,'Sigil Burst':0},{lv:16,'Solar Flare':0}], transform:null },
  prismguardian: { name:'Prism Guardian', type:['Prism','Aegis'], lore:'The final sentinel, its many facets reflect every path taken to reach it.', baseStats:{hp:90,atk:48,def:45,spd:32}, learnset:[{lv:1,'Prismatic Beam':0},{lv:5,'Iron Guard':0},{lv:10,'Light Fracture':0},{lv:16,'Aegis Dome':0}], transform:null },
  sylphos_prime: { name:'Sylphos Prime', type:'Flux', lore:'The current has become a storm — Sylphos now commands the flow of all energy.', baseStats:{hp:70,atk:58,def:42,spd:52}, learnset:[{lv:1,'Lumen Rush':0},{lv:5,'Phase Shift':0},{lv:10,'Flux Surge':0},{lv:16,'Void Step':0},{lv:22,'Resonance Wave':0},{lv:30,'Cascade Break':0},{lv:40,'Omniflux':0}], transform:null },
  terragon_prime: { name:'Terragon Prime', type:'Terra', lore:'The earth itself bows before this ancient force of stone and root.', baseStats:{hp:85,atk:52,def:62,spd:30}, learnset:[{lv:1,'Stone Claw':0},{lv:5,'Root Bind':0},{lv:10,'Terra Slam':0},{lv:16,'Bark Shield':0},{lv:22,'Quake Pulse':0},{lv:30,'Mantle Crush':0},{lv:40,'Geolock':0}], transform:null },
  velaris_prime: { name:'Velaris Prime', type:'Veil', lore:'Shadow and light collapse into a single point of infinite darkness.', baseStats:{hp:68,atk:56,def:38,spd:55}, learnset:[{lv:1,'Shadow Bite':0},{lv:5,'Mist Cloak':0},{lv:10,'Veil Slash':0},{lv:16,'Phantom Drain':0},{lv:22,'Eclipse Edge':0},{lv:30,'Null Rift':0},{lv:40,'Voidrend':0}], transform:null },
};

// ── const: ABILITIES ──
const ABILITIES = {
  'Lumen Rush':{power:40,type:'Flux',acc:95,pp:15,effect:null},
  'Phase Shift':{power:30,type:'Flux',acc:100,pp:20,effect:'speedBoost'},
  'Flux Surge':{power:60,type:'Flux',acc:90,pp:10,effect:null},
  'Void Step':{power:50,type:'Flux',acc:85,pp:8,effect:'priority'},
  'Resonance Wave':{power:70,type:'Flux',acc:88,pp:7,effect:'multiHit'},
  'Cascade Break':{power:90,type:'Flux',acc:82,pp:5,effect:null},
  'Omniflux':{power:110,type:'Flux',acc:75,pp:3,effect:'multiHit'},
  'Stone Claw':{power:40,type:'Terra',acc:95,pp:15,effect:null},
  'Root Bind':{power:25,type:'Terra',acc:100,pp:20,effect:'frost'},
  'Terra Slam':{power:60,type:'Terra',acc:90,pp:10,effect:null},
  'Bark Shield':{power:0,type:'Terra',acc:100,pp:15,effect:'barrier'},
  'Quake Pulse':{power:70,type:'Terra',acc:88,pp:7,effect:'multiHit'},
  'Mantle Crush':{power:90,type:'Terra',acc:82,pp:5,effect:null},
  'Geolock':{power:110,type:'Terra',acc:75,pp:3,effect:'frost'},
  'Shadow Bite':{power:40,type:'Veil',acc:95,pp:15,effect:null},
  'Mist Cloak':{power:25,type:'Veil',acc:100,pp:20,effect:'barrier'},
  'Veil Slash':{power:60,type:'Veil',acc:90,pp:10,effect:null},
  'Phantom Drain':{power:45,type:'Veil',acc:88,pp:12,effect:'regen'},
  'Eclipse Edge':{power:75,type:'Veil',acc:85,pp:7,effect:null},
  'Null Rift':{power:90,type:'Veil',acc:80,pp:5,effect:'shock'},
  'Voidrend':{power:110,type:'Veil',acc:72,pp:3,effect:'multiHit'},
  'Glyph Strike':{power:45,type:'Rune',acc:95,pp:15,effect:null},
  'Rune Ward':{power:0,type:'Rune',acc:100,pp:18,effect:'barrier'},
  'Sigil Burst':{power:65,type:'Rune',acc:90,pp:10,effect:null},
  'Inscription':{power:40,type:'Rune',acc:100,pp:12,effect:'speedBoost'},
  'Codex Break':{power:80,type:'Rune',acc:85,pp:7,effect:null},
  'Script Nova':{power:95,type:'Rune',acc:80,pp:5,effect:'burn'},
  'Ember Touch':{power:40,type:'Ember',acc:95,pp:15,effect:'burn'},
  'Flame Script':{power:55,type:'Ember',acc:92,pp:12,effect:'burn'},
  'Inferno Claw':{power:70,type:'Ember',acc:88,pp:8,effect:null},
  'Solar Flare':{power:85,type:'Ember',acc:85,pp:6,effect:'multiHit'},
  'Cinder Storm':{power:100,type:'Ember',acc:78,pp:4,effect:'burn'},
  'Starfall':{power:120,type:'Ember',acc:70,pp:3,effect:null},
  'Prismatic Beam':{power:50,type:'Prism',acc:95,pp:14,effect:null},
  'Spectrum Shift':{power:35,type:'Prism',acc:100,pp:18,effect:'speedBoost'},
  'Light Fracture':{power:65,type:'Prism',acc:92,pp:10,effect:null},
  'Rainbow Cascade':{power:75,type:'Prism',acc:88,pp:8,effect:'multiHit'},
  'Iris Burst':{power:90,type:'Prism',acc:84,pp:6,effect:null},
  'Aurora Wave':{power:105,type:'Prism',acc:78,pp:4,effect:'regen'},
  'Iron Guard':{power:35,type:'Aegis',acc:95,pp:15,effect:null},
  'Fortify':{power:0,type:'Aegis',acc:100,pp:20,effect:'barrier'},
  'Shield Bash':{power:55,type:'Aegis',acc:90,pp:10,effect:null},
  'Aegis Dome':{power:40,type:'Aegis',acc:100,pp:12,effect:'barrier'},
  'Impervious':{power:70,type:'Aegis',acc:85,pp:7,effect:null},
  'Last Bastion':{power:95,type:'Aegis',acc:80,pp:4,effect:'regen'},
};

// ── const: ITEMS ──
const ITEMS = {
  'Minor Bindstone':{tier:1,type:'capture',desc:'A faintly glowing stone for binding weaker Luminari.'},
  'Major Bindstone':{tier:2,type:'capture',desc:'Pulsing with resonance, effective against stronger foes.'},
  'Prime Bindstone':{tier:3,type:'capture',desc:'The ultimate binding tool, humming with pure energy.'},
  'Resonance Draught':{type:'heal',hpRestore:40,desc:'Restores moderate HP to a Luminari.'},
  'Lumen Elixir':{type:'heal',hpRestore:80,desc:'A potent restorative draught.'},
  'Flux Core':{type:'catalyst',forCreature:'sylphos',desc:'Catalyst for Sylphos transformation.'},
  'Terra Core':{type:'catalyst',forCreature:'terragon',desc:'Catalyst for Terragon transformation.'},
  'Veil Core':{type:'catalyst',forCreature:'velaris',desc:'Catalyst for Velaris transformation.'},
};

// ── const: SHOP_ITEMS ──
const SHOP_ITEMS = [
  {id:'Minor Bindstone',cost:30},{id:'Major Bindstone',cost:80},{id:'Prime Bindstone',cost:150},
  {id:'Resonance Draught',cost:25},{id:'Lumen Elixir',cost:60},
];

// ── const: STORY_NODES ──
const STORY_NODES = [
  'The resonance stirs. Luminari awaken across Orynthia, drawn to your presence.',
  'Deeper into the hollows, the shadows grow restless. Something ancient watches.',
  'The runes speak of a convergence — all affinities must unite or fade.',
  'At the vault\'s heart, the final guardian awaits. The resonance reaches its crescendo.'
];

// ── function: gainXp ──
function gainXp(creature, amount) {
  creature.xp += amount;
  const needed = creature.level * (creature.level + 1) * 3;
  while (creature.xp >= needed && creature.level < MAX_LEVEL) {
    creature.xp -= needed;
    creature.level++;
    // Stat growth
    const hpGrowth = Math.floor(creature.maxHp * 0.08);
    const atkGrowth = Math.floor(creature.atk * 0.05);
    const defGrowth = Math.floor(creature.def * 0.05);
    const spdGrowth = Math.floor(creature.spd * 0.04);
    creature.maxHp += hpGrowth;
    creature.hp = Math.min(creature.hp + hpGrowth, creature.maxHp);
    creature.atk += atkGrowth;
    creature.def += defGrowth;
    creature.spd += spdGrowth;
    learnAbilities(creature);
  }
}

// ── function: getTypeMultiplier ──
function getTypeMultiplier(atkType, defTypes) {
  let mult = 1;
  for (const dt of defTypes) {
    if (TYPE_CHART[atkType] && TYPE_CHART[atkType].strong.includes(dt)) mult *= 2;
    if (TYPE_CHART[dt] && TYPE_CHART[dt].weak.includes(atkType)) mult *= 0.5;
  }
  return Math.max(0.25, Math.min(mult, 4));
}

// ── function: calcDamage ──
function calcDamage(attacker, abilityName, defender) {
  const ab = ABILITIES[abilityName];
  if (!ab || ab.power === 0) return { damage:0, text:'' };
  let mult = getTypeMultiplier(ab.type, defender.types);
  // STAB
  if (attacker.types.includes(ab.type)) mult *= 1.25;
  const base = Math.max(1, Math.floor(0.4 * attacker.level * attacker.atk / defender.def * ab.power * mult));
  const random = Math.floor(base * 0.15);
  const crit = Math.random() < 0.06 ? true : false;
  let dmg = base + (Math.random() > 0.5 ? random : -random);
  if (crit) dmg = Math.floor(dmg * 1.5);
  dmg = Math.max(1, dmg);
  return { damage:dmg, crit, effective: mult >= 2, notVeryEffective: mult <= 0.5 };
}

// ── function: applyStatusEffect ──
function applyStatusEffect(target, effect) {
  target.status = { type:effect, turns:3 };
}

// ── function: processStatus ──
function processStatus(creature) {
  if (!creature.status) return '';
  let msg = '';
  switch (creature.status.type) {
    case 'burn': creature.hp -= Math.floor(creature.maxHp * 0.1); msg = `${creature.name} burns!`; break;
    case 'frost': /* spd halved handled in turn order */ msg = `${creature.name} is frozen solid.`; break;
    case 'shock': if (Math.random() < 0.3) { creature.status.turns--; return 'skip'; } msg = `${creature.name} is shocked!`; break;
    case 'barrier': /* handled in damage */ break;
    case 'regen': creature.hp = Math.min(creature.maxHp, creature.hp + Math.floor(creature.maxHp * 0.1)); msg = `${creature.name} regenerates.`; break;
  }
  if (creature.status.turns > 0) creature.status.turns--;
  else creature.status = null;
  return msg;
}

// ── class: ParticleSystem ──
class ParticleSystem {
  constructor() { this.particles = []; }
  emit(x, y, color, count, spread) {
    for (let i = 0; i < count; i++) {
      this.particles.push({ x, y, vx:(Math.random()-0.5)*spread, vy:(Math.random()-0.5)*spread-1, life:1, decay:0.02+Math.random()*0.03, color, size:2+Math.random()*4 });
    }
  }
  update() {
    for (let i = this.particles.length - 1; i >= 0; i--) {
      const p = this.particles[i];
      p.x += p.vx; p.y += p.vy; p.life -= p.decay;
      if (p.life <= 0) this.particles.splice(i, 1);
    }
  }
  draw(ctx) {
    for (const p of this.particles) {
      ctx.globalAlpha = p.life;
      ctx.fillStyle = p.color;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI*2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;
  }
}

// ── class: GameState ──
class GameState {
  constructor() {
    this.state = 'title'; // title, tutorial, overworld, battle, menu, shrine, shop, victory, gameover
    this.currentZone = 0;
    this.unlockedZones = [true,false,false,false,false,false];
    this.playerX = 2; this.playerY = 2;
    this.team = [];
    this.inventory = { 'Minor Bindstone':5, 'Resonance Draught':3 };
    this.shards = 50;
    this.lastShrineZone = 0;
    this.lastShrinePos = {x:2,y:2};
    this.storyProgress = 0;
    this.bossesDefeated = [];
    this.postGame = false;
    this.muted = false;
    this.moveCooldown = 0;
    this.zoneTransition = null;
    this.screenShake = {x:0,y:0,intensity:0};
  }

  addItem(id, qty=1) { this.inventory[id] = (this.inventory[id]||0) + qty; }
  removeItem(id, qty=1) { if(this.inventory[id]>=qty){this.inventory[id]-=qty;if(this.inventory[id]<=0)delete this.inventory[id];return true;} return false; }

  save(slot='default') {
    const data = JSON.stringify({currentZone:this.currentZone,unlockedZones:this.unlockedZones,playerX:this.playerX,playerY:this.playerY,team:this.team.map(t=>({...t})),inventory:this.inventory,shards:this.shards,lastShrineZone:this.lastShrineZone,lastShrinePos:this.lastShrinePos,storyProgress:this.storyProgress,bossesDefeated:this.bossesDefeated,postGame:this.postGame});
    localStorage.setItem('orynthia_'+slot, data);
  }

  load(slot='default') {
    const raw = localStorage.getItem('orynthia_'+slot);
    if (!raw) return false;
    try {
      const d = JSON.parse(raw);
      Object.assign(this, d);
      return true;
    } catch(e) { return false; }
  }

  healTeam() { for (const c of this.team) c.hp = c.maxHp; }
}

// ── class: AudioEngine ──
class AudioEngine {
  constructor() {
    this.ctx = null;
    this.muted = false;
    this.masterGain = null;
    this.initialized = false;
  }

  init() {
    if (this.initialized) return;
    try {
      this.ctx = new (window.AudioContext || window.webkitAudioContext)();
      this.masterGain = this.ctx.createGain();
      this.masterGain.gain.value = 0.3;
      this.masterGain.connect(this.ctx.destination);
      this.initialized = true;
    } catch(e) { console.warn('Audio unavailable'); }
  }

  toggleMute() {
    this.muted = !this.muted;
    if (this.masterGain) this.masterGain.gain.value = this.muted ? 0 : 0.3;
    return this.muted;
  }

  playTone(freq, duration, type, vol) {
    if (!this.initialized || this.muted) return;
    try {
      var osc = this.ctx.createOscillator();
      var gain = this.ctx.createGain();
      osc.type = type || 'sine';
      osc.frequency.value = freq;
      gain.gain.setValueAtTime(vol||0.15, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + duration);
      osc.connect(gain);
      gain.connect(this.masterGain);
      osc.start();
      osc.stop(this.ctx.currentTime + duration);
    } catch(e) {}
  }

  playHit() { this.playTone(220, 0.15, 'square', 0.12); }
  playCrit() { this.playTone(440, 0.2, 'sawtooth', 0.15); setTimeout(()=>this.playTone(660,0.15,'sawtooth',0.1),80); }
  playHeal() { this.playTone(523, 0.3, 'sine', 0.1); setTimeout(()=>this.playTone(659,0.2,'sine',0.08),100); }
  playCapture() { this.playTone(440,0.1,'sine',0.1);setTimeout(()=>this.playTone(554,0.1,'sine',0.1),100);setTimeout(()=>this.playTone(659,0.2,'sine',0.1),200); }
  playVictory() { [523,659,784,1047].forEach(function(f,i){setTimeout(()=>{if(window._audio)window._audio.playTone(f,0.3,'sine',0.12);},i*150);}); }
  playStep() { this.playTone(100+Math.random()*50, 0.05, 'triangle', 0.04); }
  playMenuSelect() { this.playTone(800, 0.08, 'sine', 0.06); }

  startAmbient(zoneIdx) {
    if (!this.initialized || this.muted) return;
    try {
      var freqs = [130,146,155,174,196];
      var f = freqs[zoneIdx % freqs.length];
      var osc = this.ctx.createOscillator();
      var gain = this.ctx.createGain();
      osc.type = 'sine';
      osc.frequency.value = f;
      gain.gain.value = 0.03;
      osc.connect(gain);
      gain.connect(this.masterGain);
      osc.start();
      // LFO for modulation
      var lfo = this.ctx.createOscillator();
      var lfoGain = this.ctx.createGain();
      lfo.frequency.value = 0.2;
      lfoGain.gain.value = f * 0.05;
      lfo.connect(lfoGain);
      lfoGain.connect(osc.frequency);
      lfo.start();
      if (this._ambientOsc) this._ambientOsc.stop();
      this._ambientOsc = osc;
      this._lfo = lfo;
    } catch(e) {}
  }

  stopAmbient() {
    try { if(this._ambientOsc){this._ambientOsc.stop();} if(this._lfo){this._lfo.stop();} } catch(e) {}
  }
}

// ── function: showNotification ──
function showNotification(text) {
  var el = document.getElementById('notification');
  if (!el) return;
  el.textContent = text;
  el.classList.remove('hidden');
  setTimeout(function(){ el.classList.add('hidden'); }, 2000);
}

// ── function: hideAllOverlays ──
function hideAllOverlays() {
  ['tutorialOverlay','dialogueOverlay','abilitySelect','bagMenu','teamMenu','encyclopediaMenu','mapMenu','shrineMenu','shopMenu','captureOverlay','pauseMenu','victoryOverlay','gameOverOverlay'].forEach(function(id){
    var el = document.getElementById(id);
    if (el) el.classList.add('hidden');
  });
}

// ── function: showAbilitySelect ──
function showAbilitySelect(creature, callback) {
  hideAllOverlays();
  var list = document.getElementById('abilityList');
  if (!list || !creature) return;
  var html = '';
  for (var i = 0; i < creature.abilities.length; i++) {
    var abName = creature.abilities[i];
    var ab = ABILITIES[abName];
    if (!ab) continue;
    var typeColor = TYPE_COLORS[ab.type] || '#a78bfa';
    html += '<div class="ability-item" data-ability="'+abName+'"><span class="ab-name">'+abName+'</span><span class="ab-info" style="color:'+typeColor+'">'+ab.type+(ab.power>0?' | Pwr:'+ab.power:'')+(ab.effect?' | '+ab.effect:'')+'</span></div>';
  }
  list.innerHTML = html;
  document.getElementById('abilitySelect').classList.remove('hidden');
  // Bind clicks
  var items = list.querySelectorAll('.ability-item');
  for (var j = 0; j < items.length; j++) {
    (function(item, name) {
      item.addEventListener('click', function() {
        hideAllOverlays();
        if (callback) callback(name);
      });
    })(items[j], items[j].getAttribute('data-ability'));
  }
}

// ── function: showBagMenu ──
function showBagMenu(callback) {
  hideAllOverlays();
  var list = document.getElementById('bagList');
  if (!list) return;
  var html = '';
  var keys = Object.keys(gameState.inventory);
  for (var i = 0; i < keys.length; i++) {
    var id = keys[i];
    var item = ITEMS[id];
    if (!item) continue;
    html += '<div class="item-row" data-item="'+id+'"><span>'+id+' x'+gameState.inventory[id]+'</span><small style="color:#9894b0">'+(item.desc||'')+'</small></div>';
  }
  list.innerHTML = html;
  document.getElementById('bagMenu').classList.remove('hidden');
  var rows = list.querySelectorAll('.item-row');
  for (var j = 0; j < rows.length; j++) {
    (function(row, itemId) {
      row.addEventListener('click', function() {
        if (callback) callback(itemId);
      });
    })(rows[j], rows[j].getAttribute('data-item'));
  }
}

// ── function: showTeamMenu ──
function showTeamMenu(callback) {
  hideAllOverlays();
  var list = document.getElementById('teamList');
  if (!list) return;
  var html = '';
  for (var i = 0; i < gameState.team.length; i++) {
    var c = gameState.team[i];
    var typeStr = Array.isArray(c.types) ? c.types.join('/') : c.types;
    html += '<div class="team-row"><span>'+c.name+' Lv.'+c.level+'</span><small style="color:'+(TYPE_COLORS[c.types[0]]||'#a78bfa')+'">'+typeStr+'</small></div>';
  }
  list.innerHTML = html;
  document.getElementById('teamMenu').classList.remove('hidden');
}

// ── function: showCodex ──
function showCodex() {
  hideAllOverlays();
  var list = document.getElementById('codexList');
  if (!list) return;
  var html = '';
  var keys = Object.keys(CREATURES);
  for (var i = 0; i < keys.length; i++) {
    var cr = CREATURES[keys[i]];
    var typeStr = Array.isArray(cr.type) ? cr.type.join('/') : cr.type;
    // Check if discovered
    var discovered = false;
    for (var j = 0; j < gameState.team.length; j++) {
      if (gameState.team[j].key === keys[i]) { discovered = true; break; }
    }
    html += '<div class="codex-entry"><span class="ce-name">'+(discovered?cr.name:'???')+'</span> <span class="ce-type" style="color:'+(TYPE_COLORS[Array.isArray(cr.type)?cr.type[0]:cr.type]||'#a78bfa')+'">'+typeStr+'</span>'+(discovered?'<div class="ce-lore">'+cr.lore+'</div>':'<div class="ce-lore">Undiscovered</div>')+'</div>';
  }
  list.innerHTML = html;
  document.getElementById('encyclopediaMenu').classList.remove('hidden');
}

// ── function: showZoneMap ──
function showZoneMap() {
  hideAllOverlays();
  var mapEl = document.getElementById('zoneMap');
  if (!mapEl) return;
  var html = '';
  for (var i = 0; i < ZONES.length; i++) {
    var unlocked = gameState.unlockedZones[i];
    var current = gameState.currentZone === i;
    html += '<div class="zone-node '+(unlocked?'unlocked':'locked')+'" data-zone="'+i+'" style="'+(current?'border-color:#a78bfa;background:rgba(167,139,250,0.2);':'')+'">'+ZONES[i].name+'</div>';
  }
  mapEl.innerHTML = html;
  document.getElementById('mapMenu').classList.remove('hidden');
  var nodes = mapEl.querySelectorAll('.zone-node');
  for (var j = 0; j < nodes.length; j++) {
    (function(node, zoneIdx) {
      node.addEventListener('click', function() {
        if (gameState.unlockedZones[zoneIdx]) {
          gameState.currentZone = zoneIdx;
          var z = ZONES[zoneIdx];
          gameState.playerX = z.shrinePos.x;
          gameState.playerY = z.shrinePos.y;
          hideAllOverlays();
          audio.startAmbient(zoneIdx);
        }
      });
    })(nodes[j], parseInt(nodes[j].getAttribute('data-zone')));
  }
}

// ── function: showShop ──
function showShop() {
  hideAllOverlays();
  var list = document.getElementById('shopList');
  if (!list) return;
  var html = '';
  for (var i = 0; i < SHOP_ITEMS.length; i++) {
    var si = SHOP_ITEMS[i];
    var item = ITEMS[si.id];
    html += '<div class="item-row" data-shop="'+i+'"><span>'+si.id+'</span><small>'+si.cost+' ◆</small></div>';
  }
  list.innerHTML = html;
  document.getElementById('shopGold').textContent = 'Resonance Shards: ' + gameState.shards;
  document.getElementById('shopMenu').classList.remove('hidden');
  var rows = list.querySelectorAll('.item-row');
  for (var j = 0; j < rows.length; j++) {
    (function(row, idx) {
      row.addEventListener('click', function() {
        var si2 = SHOP_ITEMS[idx];
        if (gameState.shards >= si2.cost) {
          gameState.shards -= si2.cost;
          gameState.addItem(si2.id);
          showNotification('Bought ' + si2.id);
          audio.playMenuSelect();
          document.getElementById('shopGold').textContent = 'Resonance Shards: ' + gameState.shards;
          // Refresh shop list
          var html2 = '';
          for (var k = 0; k < SHOP_ITEMS.length; k++) {
            var si3 = SHOP_ITEMS[k];
            html2 += '<div class="item-row" data-shop="'+k+'"><span>'+si3.id+'</span><small>'+si3.cost+' ◆</small></div>';
          }
          list.innerHTML = html2;
          // Rebind
          var rows2 = list.querySelectorAll('.item-row');
          for (var m = 0; m < rows2.length; m++) {
            (function(r, ii) {
              r.addEventListener('click', function() {
                if (gameState.shards >= SHOP_ITEMS[ii].cost) {
                  gameState.shards -= SHOP_ITEMS[ii].cost;
                  gameState.addItem(SHOP_ITEMS[ii].id);
                  showNotification('Bought ' + SHOP_ITEMS[ii].id);
                  audio.playMenuSelect();
                  document.getElementById('shopGold').textContent = 'Resonance Shards: ' + gameState.shards;
                }
              });
            })(rows2[m], m);
          }
        } else {
          showNotification('Not enough shards!');
        }
      });
    })(rows[j], j);
  }
}

// ── function: startCaptureMiniGame ──
function startCaptureMiniGame(enemy, callback) {
  hideAllOverlays();
  document.getElementById('captureOverlay').classList.remove('hidden');
  var fill = document.getElementById('captureFill');
  var instruction = document.getElementById('captureInstruction');
  if (!fill || !instruction) return;
  // Determine capture item tier
  var bestTier = 0;
  var usedItem = null;
  ['Prime Bindstone','Major Bindstone','Minor Bindstone'].forEach(function(id){
    var it = ITEMS[id];
    if (it && gameState.inventory[id] > 0 && it.tier > bestTier) { bestTier = it.tier; usedItem = id; }
  });
  if (!usedItem) { showNotification('No binding stones!'); document.getElementById('captureOverlay').classList.add('hidden'); return; }
  gameState.removeItem(usedItem);
  var hpRatio = enemy.hp / enemy.maxHp;
  var baseChance = (1 - hpRatio) * 0.6 + bestTier * 0.15;
  instruction.textContent = 'Tap when the bar is in the green zone!';
  var fillPos = 0, fillDir = 1, speed = 2 + enemy.level * 0.1;
  var taps = 0, maxTaps = 3;
  var captureAnim = setInterval(function() {
    fillPos += speed * fillDir;
    if (fillPos >= 100 || fillPos <= 0) fillDir *= -1;
    fill.style.width = fillPos + '%';
    // Green zone is 40-70%
    var inGreen = fillPos > 35 && fillPos < 65;
    fill.style.background = inGreen ? 'linear-gradient(90deg,#22c55e,#4ade80)' : 'linear-gradient(90deg,#7c3aed,#a78bfa)';
  }, 16);

  var tapHandler = function() {
    taps++;
    audio.playTone(600 + taps * 200, 0.1, 'sine', 0.1);
    if (fillPos > 35 && fillPos < 65) baseChance += 0.1;
    else baseChance -= 0.05;
    if (taps >= maxTaps || baseChance >= 0.98 || baseChance <= 0.02) {
      clearInterval(captureAnim);
      document.getElementById('btnCaptureTap').removeEventListener('click', tapHandler);
      var success = Math.random() < baseChance;
      if (success) {
        instruction.textContent = 'Binding successful!';
        audio.playCapture();
        setTimeout(function(){
          hideAllOverlays();
          callback(true, enemy);
        }, 800);
      } else {
        instruction.textContent = 'The Luminari broke free...';
        setTimeout(function(){
          hideAllOverlays();
          callback(false, null);
        }, 800);
      }
    }
  };
  document.getElementById('btnCaptureTap').addEventListener('click', tapHandler);
}

// ── function: checkShrine ──
function checkShrine() {
  var zone = ZONES[gameState.currentZone];
  if (Math.abs(gameState.playerX - zone.shrinePos.x) <= 1 && Math.abs(gameState.playerY - zone.shrinePos.y) <= 1) {
    return true;
  }
  return false;
}

// ── function: checkZoneBoundary ──
function checkZoneBoundary() {
  // Check if player is at edge and can transition to another zone
  var px = gameState.playerX, py = gameState.playerY;
  // Simple boundary: if at x=0 and previous zone unlocked, or x>=10 and next zone unlocked
  if (px <= 0 && gameState.currentZone > 0 && gameState.unlockedZones[gameState.currentZone-1]) {
    return gameState.currentZone - 1;
  }
  if (px >= 9 && gameState.currentZone < ZONES.length - 1 && gameState.unlockedZones[gameState.currentZone+1]) {
    return gameState.currentZone + 1;
  }
  return null;
}

// ── function: triggerWildEncounter ──
function triggerWildEncounter() {
  var zone = ZONES[gameState.currentZone];
  if (Math.random() > zone.encounterRate) return false;
  // Pick random wild creature from zone
  var pool = zone.wildCreatures;
  var key = pool[Math.floor(Math.random()*pool.length)];
  battleSystem.startBattle([key], false);
  gameState.state = 'battle';
  audio.playTone(200, 0.3, 'sawtooth', 0.1);
  return true;
}

// ── function: triggerBossEncounter ──
function triggerBossEncounter() {
  var zone = ZONES[gameState.currentZone];
  if (!zone.guardian) return false;
  if (gameState.bossesDefeated.indexOf(gameState.currentZone) >= 0) return false;
  // Boss at shrine position
  if (Math.abs(gameState.playerX - zone.shrinePos.x) <= 1 && Math.abs(gameState.playerY - zone.shrinePos.y) <= 1) {
    battleSystem.startBattle([zone.guardian], true);
    gameState.state = 'battle';
    audio.playTone(150, 0.5, 'sawtooth', 0.12);
    return true;
  }
  return false;
}

// ── function: tryTransform ──
function tryTransform() {
  for (var i = 0; i < gameState.team.length; i++) {
    var c = gameState.team[i];
    var tmpl = CREATURES[c.key];
    if (!tmpl || !tmpl.transform) continue;
    if (c.level >= tmpl.transform.level && gameState.inventory[tmpl.transform.catalyst]) {
      gameState.removeItem(tmpl.transform.catalyst);
      // Find transformed key
      var tKey = c.key.replace(/^(sylphos|terragon|velaris)$/,'$1_prime');
      var newC = createCreature(tKey, c.level);
      if (newC) {
        newC.xp = c.xp;
        learnAbilities(newC);
        gameState.team[i] = newC;
        showNotification(c.name + ' transformed into ' + newC.name + '!');
        audio.playCapture();
        return true;
      }
    }
  }
  return false;
}

// ── class: BattleSystem ──
class BattleSystem {
  constructor(canvas, gs) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.gs = gs;
    this.particles = new ParticleSystem();
    this.enemies = [];
    this.turnOrder = [];
    this.currentTurnIdx = 0;
    this.phase = 'idle';
    this.selectedCreature = null;
    this.animQueue = [];
    this.battleLog = [];
    this.floatingTexts = [];
    this.shakeTimer = 0;
    this.introTimer = 0;
    var dpr = window.devicePixelRatio || 1;
    this.w = canvas.width / dpr;
    this.h = canvas.height / dpr;
  }

  update() {
    if (this.phase === 'intro') {
      this.introTimer--;
      if (this.introTimer <= 0) {
        this.phase = 'playerTurn';
        var alive = this.gs.team.filter(function(c){return c.hp>0;});
        if (alive.length > 0) this.selectedCreature = alive[0];
      }
    }
  }

  startBattle(enemyKeys, isBoss) {
    if (!isBoss) isBoss = false;
    var zoneLevel = ZONES[this.gs.currentZone].guardianLevel || 5 + this.gs.currentZone * 4;
    for (var i = 0; i < enemyKeys.length; i++) {
      var lvl = Math.max(1, zoneLevel + Math.floor(Math.random()*3) - 1);
      var e = createCreature(enemyKeys[i], isBoss ? zoneLevel : lvl);
      if (!e) continue;
      e.x = this.w * (0.3 + i * 0.25);
      e.y = this.h * 0.28;
      e.isBoss = isBoss;
      this.enemies.push(e);
    }
    this.phase = 'intro';
    this.introTimer = 45;
    this.battleLog = [];
    this.floatingTexts = [];
    this.buildTurnOrder();
    document.getElementById('battleUI').classList.remove('hidden');
    this.updateBattleUI();
  }

  buildTurnOrder() {
    var all = [];
    for (var i = 0; i < this.gs.team.length; i++) {
      var c = this.gs.team[i];
      if (c.hp > 0) {
        var spd = c.spd;
        if (c.status && c.status.type === 'frost') spd *= 0.5;
        all.push({type:'player',idx:i,spd:spd});
      }
    }
    for (var j = 0; j < this.enemies.length; j++) {
      var e2 = this.enemies[j];
      if (e2.hp > 0) {
        var espd = e2.spd;
        if (e2.status && e2.status.type === 'frost') espd *= 0.5;
        all.push({type:'enemy',idx:j,spd:espd});
      }
    }
    all.sort(function(a,b){return b.spd - a.spd;});
    this.turnOrder = all;
    this.currentTurnIdx = 0;
  }

  nextTurn() {
    if (this.phase === 'victory' || this.phase === 'defeat') return;
    var aliveE = this.enemies.filter(function(e3){return e3.hp>0;});
    var aliveP = this.gs.team.filter(function(c2){return c2.hp>0;});
    if (aliveE.length === 0) { this.phase = 'victory'; this.onVictory(); return; }
    if (aliveP.length === 0) { this.phase = 'defeat'; this.onDefeat(); return; }

    while (this.currentTurnIdx < this.turnOrder.length) {
      var turn = this.turnOrder[this.currentTurnIdx];
      this.currentTurnIdx++;
      var unit, sMsg;
      if (turn.type === 'player') {
        unit = this.gs.team[turn.idx];
        if (!unit || unit.hp <= 0) continue;
        sMsg = processStatus(unit);
        if (sMsg) this.addLog(sMsg);
        if (unit.status && unit.status.type === 'shock' && Math.random() < 0.3) {
          this.addLog(unit.name + ' is paralyzed!');
          continue;
        }
        this.phase = 'playerTurn';
        this.selectedCreature = unit;
        return;
      } else {
        unit = this.enemies[turn.idx];
        if (!unit || unit.hp <= 0) continue;
        sMsg = processStatus(unit);
        if (sMsg) this.addLog(sMsg);
        if (unit.status && unit.status.type === 'shock' && Math.random() < 0.3) {
          this.addLog(unit.name + ' is paralyzed!');
          continue;
        }
        this.phase = 'enemyTurn';
        var bs = this;
        setTimeout(function(){bs.enemyAI(unit);}, 400);
        return;
      }
    }
    this.buildTurnOrder();
  }

  enemyAI(enemy) {
    var targets = this.gs.team.filter(function(c3){return c3.hp>0;});
    if (targets.length === 0 || enemy.abilities.length === 0) { this.nextTurn(); return; }
    var target = targets[Math.floor(Math.random()*targets.length)];
    var abName = enemy.abilities[Math.floor(Math.random()*enemy.abilities.length)];
    this.executeMove(enemy, target, abName);
  }

  executeMove(attacker, defender, abilityName) {
    var ab = ABILITIES[abilityName];
    if (!ab) { this.nextTurn(); return; }
    if (Math.random()*100 >= ab.acc) {
      this.addLog(attacker.name + "'s " + abilityName + " missed!");
      setTimeout(function(){this.nextTurn();}.bind(this), 500);
      return;
    }
    var msg = attacker.name + ' uses ' + abilityName + '!';
    if (ab.power === 0) {
      if (ab.effect === 'barrier') { applyStatusEffect(attacker, 'barrier'); this.addLog(attacker.name + ' raises a barrier.'); }
      else if (ab.effect === 'speedBoost') { attacker.spd = Math.floor(attacker.spd * 1.3); this.addLog(attacker.name + "'s speed rises!"); }
      else if (ab.effect === 'regen') { applyStatusEffect(attacker, 'regen'); this.addLog(attacker.name + ' begins regenerating.'); }
      setTimeout(function(){this.nextTurn();}.bind(this), 500);
      return;
    }
    var hits = ab.effect === 'multiHit' ? (2 + Math.floor(Math.random()*2)) : 1;
    var totalDmg = 0;
    for (var h = 0; h < hits; h++) {
      var result = calcDamage(attacker, abilityName, defender);
      if (result.damage === 0) continue;
      if (defender.status && defender.status.type === 'barrier') {
        this.addLog(defender.name + "'s barrier absorbs the hit!");
        defender.status = null;
        totalDmg += result.damage;
        continue;
      }
      defender.hp -= result.damage;
      totalDmg += result.damage;
      if (result.crit) msg += ' Critical!';
      if (result.effective) msg += ' Super effective!';
      else if (result.notVeryEffective) msg += ' Not very effective...';
      var ex = defender.x || this.w*0.5;
      var ey = defender.y || this.h*0.3;
      this.floatingTexts.push({x:ex,y:ey,text:'-'+result.damage,color:result.effective?'#facc15':result.crit?'#ef4444':'#e8e6f0',life:1});
      var effColor = result.effective ? '#facc15' : '#a78bfa';
      this.particles.emit(ex, ey, effColor, result.crit?20:8, 4);
      if (ab.effect && Math.random() < 0.4) {
        applyStatusEffect(defender, ab.effect);
        this.addLog(defender.name + ' is affected by ' + ab.effect + '!');
      }
    }
    defender.hp = Math.max(0, defender.hp);
    this.addLog(msg);
    if (totalDmg > 30) { this.shakeTimer = 8; }
    var bs2 = this;
    setTimeout(function(){bs2.nextTurn();}, 600);
    this.updateBattleUI();
  }

  addLog(text) {
    this.battleLog.push(text);
    var el = document.getElementById('battleLog');
    if (el) {
      var html = '';
      for (var i = Math.max(0,this.battleLog.length-5); i < this.battleLog.length; i++) {
        html += '<div>' + this.battleLog[i] + '</div>';
      }
      el.innerHTML = html;
      el.scrollTop = el.scrollHeight;
    }
  }

  updateBattleUI() {
    var ea = document.getElementById('enemyArea');
    if (ea) {
      var ehtml = '';
      for (var i = 0; i < this.enemies.length; i++) {
        var e4 = this.enemies[i];
        if (e4.hp <= 0) continue;
        var pct = Math.max(0, e4.hp/e4.maxHp*100);
        var cls = pct < 25 ? 'low' : pct < 50 ? 'mid' : '';
        var bossStar = e4.isBoss ? ' ★' : '';
        ehtml += '<div class="creature-card"><span class="name">'+e4.name+'</span> <span class="level">Lv.'+e4.level+bossStar+'</span><div class="hp-bar-bg"><div class="hp-bar-fill '+cls+'" style="width:'+pct+'%"></div></div><small>'+e4.hp+'/'+e4.maxHp+' HP</small></div>';
      }
      ea.innerHTML = ehtml;
    }
    var pp = document.getElementById('playerParty');
    if (pp) {
      var phtml = '';
      for (var j = 0; j < this.gs.team.length; j++) {
        var c4 = this.gs.team[j];
        var pct2 = Math.max(0, c4.hp/c4.maxHp*100);
        var cls2 = pct2 < 25 ? 'low' : pct2 < 50 ? 'mid' : '';
        var selStyle = c4 === this.selectedCreature ? 'border-color:#a78bfa;' : '';
        phtml += '<div class="creature-card" style="'+selStyle+'"><span class="name">'+c4.name+'</span> <span class="level">Lv.'+c4.level+'</span><div class="hp-bar-bg"><div class="hp-bar-fill '+cls2+'" style="width:'+pct2+'%"></div></div><small>'+c4.hp+'/'+c4.maxHp+' HP</small></div>';
      }
      pp.innerHTML = phtml;
    }
  }

  onVictory() {
    var expGain = 0, shardGain = 0;
    for (var i = 0; i < this.enemies.length; i++) {
      var e5 = this.enemies[i];
      expGain += Math.floor(e5.level * 12 + e5.maxHp * 0.5);
      shardGain += Math.floor(e5.level * 3 + Math.random()*5);
    }
    var hasBoss = false;
    for (var k = 0; k < this.enemies.length; k++) { if(this.enemies[k].isBoss) hasBoss=true; }
    if (hasBoss) {
      expGain = Math.floor(expGain * 2.5);
      shardGain = Math.floor(shardGain * 3);
      var nextZone = this.gs.currentZone + 1;
      if (nextZone < ZONES.length && !this.gs.unlockedZones[nextZone]) {
        this.gs.unlockedZones[nextZone] = true;
      }
      this.gs.bossesDefeated.push(this.gs.currentZone);
    }
    for (var m = 0; m < this.gs.team.length; m++) gainXp(this.gs.team[m], expGain / this.gs.team.length);
    this.gs.shards += shardGain;
    if (this.gs.storyProgress < STORY_NODES.length && this.gs.bossesDefeated.length > this.gs.storyProgress) {
      this.gs.storyProgress = Math.min(STORY_NODES.length, this.gs.bossesDefeated.length);
    }
    var isFinal = this.gs.currentZone === 5;
    if (isFinal && !this.gs.postGame) this.gs.postGame = true;
    document.getElementById('victoryTitle').textContent = isFinal ? 'Orynthia Resonates!' : 'Victory!';
    var zoneUnlocked = '';
    if (nextZone < ZONES.length && this.gs.unlockedZones[nextZone]) zoneUnlocked = ' | New zone unlocked!';
    document.getElementById('victoryText').textContent = 'EXP: +' + Math.floor(expGain) + ' | Shards: +' + shardGain + zoneUnlocked;
    document.getElementById('victoryOverlay').classList.remove('hidden');
  }

  onDefeat() {
    document.getElementById('gameOverOverlay').classList.remove('hidden');
  }

  endBattle(returnToShrine) {
    this.enemies = [];
    this.phase = 'idle';
    document.getElementById('battleUI').classList.add('hidden');
    if (returnToShrine) {
      var zone = ZONES[this.gs.currentZone];
      this.gs.playerX = zone.shrinePos.x;
      this.gs.playerY = zone.shrinePos.y;
    }
  }

  drawBattle() {
    var ctx = this.ctx;
    var zone2 = ZONES[this.gs.currentZone];
    var grad = ctx.createLinearGradient(0, 0, 0, this.h);
    grad.addColorStop(0, zone2.gradient[0]);
    grad.addColorStop(1, '#0a0510');
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, this.w, this.h);
    if (this.shakeTimer > 0) {
      var sx = (Math.random()-0.5)*this.shakeTimer*2;
      var sy = (Math.random()-0.5)*this.shakeTimer*2;
      ctx.save();
      ctx.translate(sx, sy);
      this.shakeTimer--;
    }
    for (var i = 0; i < this.enemies.length; i++) {
      var e6 = this.enemies[i];
      if (e6.hp <= 0) continue;
      var color = TYPE_COLORS[e6.types[0]] || '#a78bfa';
      ctx.shadowColor = color;
      ctx.shadowBlur = 15;
      ctx.fillStyle = color;
      ctx.globalAlpha = 0.6 + Math.sin(Date.now()*0.003)*0.2;
      this.drawCreatureShape(ctx, e6.x || this.w*0.5, e6.y || this.h*0.3, 28, color);
      ctx.globalAlpha = 1;
      ctx.shadowBlur = 0;
    }
    for (var j = this.floatingTexts.length-1; j >= 0; j--) {
      var ft = this.floatingTexts[j];
      ft.y -= 1.2; ft.life -= 0.025;
      if (ft.life <= 0) { this.floatingTexts.splice(j,1); continue; }
      ctx.globalAlpha = ft.life;
      ctx.fillStyle = ft.color;
      ctx.font = 'bold 18px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText(ft.text, ft.x, ft.y);
    }
    ctx.globalAlpha = 1;
    this.particles.update();
    this.particles.draw(ctx);
    if (this.shakeTimer >= 0) ctx.restore();
  }

  drawCreatureShape(ctx, x, y, r, color) {
    ctx.beginPath();
    for (var i = 0; i < 6; i++) {
      var angle = Math.PI/3 * i - Math.PI/6;
      var px = x + r * Math.cos(angle);
      var py = y + r * Math.sin(angle);
      if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
    }
    ctx.closePath();
    ctx.fill();
  }
}

// ── class: OverworldRenderer ──
class OverworldRenderer {
  constructor(canvas, gs) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.gs = gs;
    this.particles = new ParticleSystem();
    this.ambientParticles = [];
    this.time = 0;
    this.resize();
    window.addEventListener('resize', function(){this.resize();}.bind(this));
  }

  resize() {
    var dpr = Math.min(window.devicePixelRatio || 1, 2);
    this.canvas.width = window.innerWidth * dpr;
    this.canvas.height = window.innerHeight * dpr;
    this.ctx.scale(dpr, dpr);
    this.w = window.innerWidth;
    this.h = window.innerHeight;
  }

  drawBackground() {
    var ctx = this.ctx;
    var zone = ZONES[this.gs.currentZone];
    if (!zone) return;
    this.time += 0.016;

    // Background gradient
    var grad = ctx.createLinearGradient(0, 0, 0, this.h);
    grad.addColorStop(0, zone.gradient[0]);
    grad.addColorStop(1, zone.gradient[1]);
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, this.w, this.h);

    // Zone-specific decorations
    this.drawZoneDecorations(ctx, zone);

    // Ambient particles
    if (Math.random() < 0.2) {
      this.ambientParticles.push({x:Math.random()*this.w,y:this.h+10,vy:-0.3-Math.random()*0.8,vx:(Math.random()-0.5)*0.5,life:1,size:1+Math.random()*2});
    }
    for (var i = this.ambientParticles.length-1; i >= 0; i--) {
      var p = this.ambientParticles[i];
      p.x += p.vx; p.y += p.vy; p.life -= 0.003;
      if (p.life <= 0 || p.y < -10) { this.ambientParticles.splice(i,1); continue; }
      ctx.globalAlpha = p.life * 0.4;
      ctx.fillStyle = zone.particles;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI*2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;

    // Grid lines (subtle)
    ctx.strokeStyle = 'rgba(255,255,255,0.03)';
    ctx.lineWidth = 1;
    var offsetX = (this.gs.playerX * TILE) % TILE;
    var offsetY = (this.gs.playerY * TILE) % TILE;
    for (var x = -offsetX; x < this.w + TILE; x += TILE) {
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, this.h); ctx.stroke();
    }
    for (var y = -offsetY; y < this.h + TILE; y += TILE) {
      ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(this.w, y); ctx.stroke();
    }

    // Shrine marker
    var shrinePx = (zone.shrinePos.x - this.gs.playerX) * TILE + this.w/2;
    var shrinePy = (zone.shrinePos.y - this.gs.playerY) * TILE + this.h/2;
    if (Math.abs(shrinePx) < this.w && Math.abs(shrinePy) < this.h) {
      var pulse = Math.sin(this.time * 3) * 4;
      ctx.fillStyle = 'rgba(167,139,250,' + (0.2 + Math.sin(this.time*2)*0.1) + ')';
      ctx.beginPath();
      ctx.arc(shrinePx + TILE/2, shrinePy + TILE/2, 14 + pulse, 0, Math.PI*2);
      ctx.fill();
      ctx.fillStyle = '#c4b5fd';
      ctx.font = '18px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('\u2726', shrinePx + TILE/2, shrinePy + TILE/2 + 6);
    }

    // Zone boundary indicators
    if (this.gs.playerX <= 0 && this.gs.currentZone > 0 && this.gs.unlockedZones[this.gs.currentZone-1]) {
      ctx.fillStyle = 'rgba(96,165,250,' + (0.3+Math.sin(this.time*2)*0.1) + ')';
      ctx.fillRect(this.w/2 - TILE*3, this.h/2 + TILE/2, TILE*6, 8);
      ctx.fillStyle = '#60a5fa';
      ctx.font = '10px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('← ' + ZONES[this.gs.currentZone-1].name, this.w/2, this.h/2 + TILE*2);
    }
    if (this.gs.playerX >= 9 && this.gs.currentZone < ZONES.length-1 && this.gs.unlockedZones[this.gs.currentZone+1]) {
      ctx.fillStyle = 'rgba(96,165,250,' + (0.3+Math.sin(this.time*2)*0.1) + ')';
      ctx.fillRect(this.w/2 - TILE*3, this.h/2 + TILE/2, TILE*6, 8);
      ctx.fillStyle = '#60a5fa';
      ctx.font = '10px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText(ZONES[this.gs.currentZone+1].name + ' →', this.w/2, this.h/2 + TILE*2);
    }

    // Player character (glowing orb with trail)
    var px = this.w/2;
    var py = this.h/2;
    var playerPulse = Math.sin(this.time * 4) * 3;

    // Outer glow
    ctx.shadowColor = zone.particles;
    ctx.shadowBlur = 20 + playerPulse;
    ctx.fillStyle = 'rgba(232,230,240,0.15)';
    ctx.beginPath();
    ctx.arc(px, py, 18 + playerPulse, 0, Math.PI*2);
    ctx.fill();

    // Inner core
    ctx.fillStyle = '#e8e6f0';
    ctx.beginPath();
    ctx.arc(px, py, 10, 0, Math.PI*2);
    ctx.fill();
    ctx.shadowBlur = 0;

    // HUD overlay
    this.drawHUD(ctx, zone);

    // Particles on top
    this.particles.update();
    this.particles.draw(ctx);
  }

  drawZoneDecorations(ctx, zone) {
    var t = this.time;
    switch(this.gs.currentZone) {
      case 0: // Lumenwood - floating light orbs
        for (var i = 0; i < 8; i++) {
          var ox = Math.sin(t*0.5 + i*1.3)*this.w*0.4 + this.w/2;
          var oy = Math.cos(t*0.3 + i*0.9)*this.h*0.3 + this.h/2;
          ctx.globalAlpha = 0.15;
          ctx.fillStyle = '#60a5fa';
          ctx.beginPath();
          ctx.arc(ox, oy, 8+Math.sin(t+i)*3, 0, Math.PI*2);
          ctx.fill();
        }
        break;
      case 1: // Crystal Hollows - crystal shapes
        for (var j = 0; j < 6; j++) {
          var cx = Math.sin(j*2.1)*this.w*0.35 + this.w/2;
          var cy = Math.cos(j*1.7)*this.h*0.3 + this.h/2;
          ctx.globalAlpha = 0.1;
          ctx.fillStyle = '#c084fc';
          ctx.save();
          ctx.translate(cx, cy);
          ctx.rotate(t*0.2+j);
          ctx.fillRect(-6,-15,12,30);
          ctx.restore();
        }
        break;
      case 2: // Runeplateau - glowing runes
        for (var k = 0; k < 5; k++) {
          var rx = Math.sin(k*1.8)*this.w*0.3 + this.w/2;
          var ry = Math.cos(k*2.2)*this.h*0.25 + this.h/2;
          ctx.globalAlpha = 0.12 + Math.sin(t+k)*0.05;
          ctx.fillStyle = '#f97316';
          ctx.font = '24px sans-serif';
          ctx.textAlign = 'center';
          var runes = '\u2605\u2606\u2726\u2727\u26A0';
          ctx.fillText(runes[k%runes.length], rx, ry);
        }
        break;
      case 3: // Emberdeep - embers rising
        for (var m = 0; m < 10; m++) {
          var ex = Math.sin(m*1.5+t*0.5)*this.w*0.4 + this.w/2;
          var ey = this.h - ((t*30+m*40)%this.h);
          ctx.globalAlpha = 0.2;
          ctx.fillStyle = '#ef4444';
          ctx.beginPath();
          ctx.arc(ex, ey, 2+Math.sin(t+m)*1, 0, Math.PI*2);
          ctx.fill();
        }
        break;
      case 4: // Prism Spire - light rays
        for (var n = 0; n < 6; n++) {
          var angle = t*0.3 + n*Math.PI/3;
          ctx.globalAlpha = 0.05;
          ctx.strokeStyle = '#facc15';
          ctx.lineWidth = 2;
          ctx.beginPath();
          ctx.moveTo(this.w/2, this.h/2);
          ctx.lineTo(this.w/2+Math.cos(angle)*this.w*0.6, this.h/2+Math.sin(angle)*this.h*0.6);
          ctx.stroke();
        }
        break;
      case 5: // Aegis Vault - shield patterns
        for (var o = 0; o < 4; o++) {
          var sx = Math.sin(o*Math.PI/2)*this.w*0.3 + this.w/2;
          var sy = Math.cos(o*Math.PI/2)*this.h*0.3 + this.h/2;
          ctx.globalAlpha = 0.08;
          ctx.strokeStyle = '#94a3b8';
          ctx.lineWidth = 1;
          ctx.beginPath();
          ctx.arc(sx, sy, 25+Math.sin(t+o)*5, 0, Math.PI*2);
          ctx.stroke();
        }
        break;
    }
    ctx.globalAlpha = 1;
  }

  drawHUD(ctx, zone) {
    // Zone name
    ctx.fillStyle = 'rgba(232,230,240,0.6)';
    ctx.font = '14px sans-serif';
    ctx.textAlign = 'left';
    ctx.fillText(zone.name, 16, 28);

    // Team info (abbreviated)
    if (this.gs.team.length > 0) {
      var teamNames = this.gs.team.map(function(c){return c.name;}).join(', ');
      ctx.fillStyle = 'rgba(232,230,240,0.4)';
      ctx.font = '11px sans-serif';
      if (teamNames.length > 50) teamNames = teamNames.substring(0,47)+'...';
      ctx.fillText('Team: '+teamNames, 16, 48);
    }

    // Shards count
    ctx.fillStyle = '#facc15';
    ctx.textAlign = 'right';
    ctx.font = '12px sans-serif';
    ctx.fillText('\u25C6 '+this.gs.shards, this.w-16, 28);

    // Zone progress indicator
    var progress = this.gs.bossesDefeated.length;
    ctx.fillStyle = 'rgba(232,230,240,0.3)';
    ctx.font = '10px sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText('Guardians: '+progress+'/4', this.w/2, 28);

    // Post-game indicator
    if (this.gs.postGame) {
      ctx.fillStyle = '#ef4444';
      ctx.font = '10px sans-serif';
      ctx.textAlign = 'right';
      ctx.fillText('POST-GAME', this.w-16, 48);
    }

    // Controls hint (bottom)
    ctx.fillStyle = 'rgba(232,230,240,0.2)';
    ctx.font = '10px sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText('WASD/Arrows: Move | Space: Interact | Esc: Menu', this.w/2, this.h-12);
  }

  emitBattleParticles() {
    this.particles.emit(this.w/2, this.h/2, '#a78bfa', 30, 6);
  }
}

// ── const: ZONES ──
const ZONES = [
  { id:0, name:'Lumenwood', gradient:['#1a2332','#0d1b2a'], particles:'#60a5fa', encounterRate:0.15, guardian:'wardenshade', guardianLevel:8, storyNode:'You awaken in Lumenwood, where currents of light flow through ancient trees. A faint resonance calls you forward.', wildCreatures:['aquorin','lithara'], shrinePos:{x:2,y:2} },
  { id:1, name:'Crystal Hollows', gradient:['#1a1033','#0f0a1e'], particles:'#c084fc', encounterRate:0.18, guardian:null, storyNode:'The hollows hum with veiled energy. Shadows dance between crystalline formations.', wildCreatures:['umbrax','aquorin','lithara'], shrinePos:{x:5,y:3} },
  { id:2, name:'Runeplateau', gradient:['#1a0f0a','#2d1810'], particles:'#f97316', encounterRate:0.20, guardian:'flamerune', guardianLevel:18, storyNode:'Ancient glyphs glow upon the plateau stones. The air crackles with inscribed power.', wildCreatures:['runeth','pyralis','umbrax'], shrinePos:{x:3,y:4} },
  { id:3, name:'Emberdeep', gradient:['#1a0505','#2d0a0a'], particles:'#ef4444', encounterRate:0.22, guardian:null, storyNode:'Heat radiates from below. The ground pulses like a living heart.', wildCreatures:['ignara','solvex','runeth'], shrinePos:{x:6,y:2} },
  { id:4, name:'Prism Spire', gradient:['#0a1520','#0d1f30'], particles:'#facc15', encounterRate:0.25, guardian:null, storyNode:'Light fractures into a thousand colors as you ascend the crystalline spire.', wildCreatures:['prisma','nyxara','aurion'], shrinePos:{x:4,y:5} },
  { id:5, name:'Aegis Vault', gradient:['#0a0a12','#12121e'], particles:'#94a3b8', encounterRate:0.28, guardian:'prismguardian', guardianLevel:35, storyNode:'The final vault. Here, all paths converge and the resonance reaches its peak.', wildCreatures:['bulwark','nyxara','aurion','verdant'], shrinePos:{x:3,y:3} },
];

// ── function: createCreature ──
function createCreature(key, level) {
  var tmpl = CREATURES[key];
  if (!tmpl) return null;
  var types = Array.isArray(tmpl.type) ? tmpl.type : [tmpl.type];
  var stats = {};
  for (var s in tmpl.baseStats) {
    var growth = s === 'hp' ? 1.2 : 1.15;
    stats[s] = Math.floor(tmpl.baseStats[s] + (level - 1) * tmpl.baseStats[s] / MAX_LEVEL * growth);
  }
  var creature = { key:key, name:tmpl.name, types:types, lore:tmpl.lore, level:level, xp:0, hp:stats.hp, maxHp:stats.hp, atk:stats.atk, def:stats.def, spd:stats.spd, abilities:[], status:null, transformable:!!tmpl.transform };
  learnAbilities(creature);
  return creature;
}

// ── function: learnAbilities ──
function learnAbilities(creature) {
  var tmpl = CREATURES[creature.key];
  if (!tmpl) return;
  for (var i = 0; i < tmpl.learnset.length; i++) {
    var entry = tmpl.learnset[i];
    // Find the ability name - it's any key that isn't 'lv'
    var abName = null;
    for (var k in entry) {
      if (k !== 'lv') { abName = k; break; }
    }
    if (!abName) continue;
    if (entry.lv <= creature.level && creature.abilities.indexOf(abName) === -1) {
      creature.abilities.push(abName);
    }
  }
}

// ── main ──
var gameState = new GameState();
var overworld;
var battleSystem;
var audio = new AudioEngine();
window._audio = audio;
var canvas = document.getElementById('gameCanvas');
var keysDown = {};
var tutorialStep = 0;
var tutorialSteps = [
  'Welcome to Orynthia. Use WASD or arrow keys to move.',
  'You will encounter wild Luminari in the wilds. Press Space at shrines to rest.',
  'In battle, choose Strikes to defeat foes and Bind to capture them.',
  'Defeat zone Guardians to unlock new areas. Good luck, Binder!'
];

function init() {
  overworld = new OverworldRenderer(canvas, gameState);
  battleSystem = new BattleSystem(canvas, gameState);
  
  var hasSave = gameState.load('default');
  document.getElementById('btnContinue').style.display = hasSave ? '' : 'none';

  document.getElementById('btnNewGame').addEventListener('click', function() { audio.init(); startNewGame(); });
  document.getElementById('btnContinue').addEventListener('click', function() { audio.init(); loadGame(); });

  window.addEventListener('keydown', onKeyDown);
  window.addEventListener('keyup', onKeyUp);

  var isTouch = 'ontouchstart' in window;
  if (isTouch) document.getElementById('touchControls').classList.remove('hidden');
  setupTouchControls();

  requestAnimationFrame(gameLoop);
}

function startNewGame() {
  gameState = new GameState();
  tutorialStep = 0;
  gameState.state = 'tutorial';
  document.getElementById('titleScreen').classList.add('hidden');
  showTutorial();
}

function loadGame() {
  if (gameState.load('default')) {
    document.getElementById('titleScreen').classList.add('hidden');
    gameState.state = 'overworld';
    audio.startAmbient(gameState.currentZone);
  }
}

function showTutorial() {
  var el = document.getElementById('tutorialText');
  if (el && tutorialStep < tutorialSteps.length) {
    el.textContent = tutorialSteps[tutorialStep];
    document.getElementById('tutorialOverlay').classList.remove('hidden');
  } else {
    endTutorial();
  }
}

function endTutorial() {
  document.getElementById('tutorialOverlay').classList.add('hidden');
  var starter = createCreature('sylphos', 5);
  if (starter) gameState.team.push(starter);
  gameState.state = 'overworld';
  audio.startAmbient(0);
}

function onKeyDown(e) {
  keysDown[e.key] = true;
  
  if (gameState.state === 'title') return;
  
  if (e.key === 'Escape' && gameState.state === 'overworld') {
    document.getElementById('pauseMenu').classList.remove('hidden');
    return;
  }
  if (e.key === 'Escape' && !document.getElementById('pauseMenu').classList.contains('hidden')) {
    hideAllOverlays();
    return;
  }

  if (gameState.state !== 'overworld') return;
  if (gameState.moveCooldown > 0) return;
  
  var moved = false;
  if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') { gameState.playerY--; moved = true; }
  else if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') { gameState.playerY++; moved = true; }
  else if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') { gameState.playerX--; moved = true; }
  else if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') { gameState.playerX++; moved = true; }
  
  if (moved) {
    audio.playStep();
    gameState.moveCooldown = 8;
    
    if (checkShrine()) {
      triggerBossEncounter();
      if (gameState.state !== 'battle') {
        document.getElementById('shrineMenu').classList.remove('hidden');
        gameState.state = 'shrine';
      }
      return;
    }
    
    var newZone = checkZoneBoundary();
    if (newZone !== null) {
      gameState.currentZone = newZone;
      var z = ZONES[newZone];
      gameState.playerX = z.shrinePos.x + 2;
      gameState.playerY = z.shrinePos.y;
      audio.startAmbient(newZone);
      showNotification('Entered ' + z.name);
      
      if (gameState.storyProgress < STORY_NODES.length) {
        setTimeout(function() {
          document.getElementById('dialogueText').textContent = STORY_NODES[gameState.storyProgress];
          document.getElementById('dialogueOverlay').classList.remove('hidden');
          gameState.state = 'story';
        }, 500);
      }
      return;
    }
    
    var zone = ZONES[gameState.currentZone];
    var rate = zone.encounterRate;
    if (gameState.postGame) rate *= 1.3;
    if (Math.random() < rate) {
      triggerWildEncounter();
    }
  }
  
  if (e.key === ' ' && checkShrine() && gameState.state === 'overworld') {
    e.preventDefault();
    triggerBossEncounter();
    if (gameState.state !== 'battle') {
      document.getElementById('shrineMenu').classList.remove('hidden');
      gameState.state = 'shrine';
    }
  }
}

function onKeyUp(e) { keysDown[e.key] = false; }

function setupTouchControls() {
  var btnMap = { btnUp:'ArrowUp', btnDown:'ArrowDown', btnLeft:'ArrowLeft', btnRight:'ArrowRight' };
  for (var id in btnMap) {
    (function(el, k){ el.addEventListener('touchstart', function(){onKeyDown({key:k});},{passive:true}); })(document.getElementById(id), btnMap[id]);
  }
}

// Shrine menu buttons
document.getElementById('btnShrineHeal').addEventListener('click', function() { gameState.healTeam(); showNotification('All Luminari restored!'); audio.playHeal(); });
document.getElementById('btnShrineShop').addEventListener('click', function() { hideAllOverlays(); showShop(); });
document.getElementById('btnShrineSave').addEventListener('click', function() { gameState.save(); showNotification('Binding inscribed!'); });
document.getElementById('btnShrineLeave').addEventListener('click', function() { document.getElementById('shrineMenu').classList.add('hidden'); gameState.state = 'overworld'; tryTransform(); });

// Battle menu buttons
document.getElementById('btnFight').addEventListener('click', function() {
  if (gameState.phase === 'playerTurn' && battleSystem.selectedCreature) {
    showAbilitySelect(battleSystem.selectedCreature, function(abName) {
      var target = battleSystem.enemies.filter(function(e){return e.hp>0;})[0];
      if (target) battleSystem.executeMove(battleSystem.selectedCreature, target, abName);
    });
  }
});

document.getElementById('btnBag').addEventListener('click', function() {
  showBagMenu(function(itemId) {
    var item = ITEMS[itemId];
    if (!item) return;
    hideAllOverlays();
    if (item.type === 'heal' && battleSystem.selectedCreature) {
      gameState.removeItem(itemId);
      battleSystem.selectedCreature.hp = Math.min(battleSystem.selectedCreature.maxHp, battleSystem.selectedCreature.hp + item.hpRestore);
      showNotification('Used ' + itemId);
      audio.playHeal();
      battleSystem.updateBattleUI();
    } else if (item.type === 'capture') {
      var target2 = battleSystem.enemies.filter(function(e){return e.hp>0;})[0];
      if (target2) {
        startCaptureMiniGame(target2, function(success, captured) {
          if (success && captured) {
            if (gameState.team.length < TEAM_SIZE) { gameState.team.push(captured); showNotification('Bound ' + captured.name + '!'); }
            else { showNotification('Team is full! Released.'); }
            battleSystem.enemies = battleSystem.enemies.filter(function(e){return e !== captured;});
          }
          battleSystem.updateBattleUI();
        });
      }
    }
  });
});

document.getElementById('btnTeam').addEventListener('click', function() { showTeamMenu(); });

document.getElementById('btnCapture').addEventListener('click', function() {
  var target3 = battleSystem.enemies.filter(function(e){return e.hp>0;})[0];
  if (target3) {
    startCaptureMiniGame(target3, function(success, captured) {
      if (success && captured) {
        if (gameState.team.length < TEAM_SIZE) { gameState.team.push(captured); showNotification('Bound ' + captured.name + '!'); }
        else { showNotification('Team is full! Released.'); }
        battleSystem.enemies = battleSystem.enemies.filter(function(e){return e !== captured;});
      }
      battleSystem.updateBattleUI();
    });
  }
});

// Menu close buttons
document.getElementById('btnAbilityBack').addEventListener('click', hideAllOverlays);
document.getElementById('btnBagClose').addEventListener('click', function() { hideAllOverlays(); });
document.getElementById('btnTeamClose').addEventListener('click', hideAllOverlays);
document.getElementById('btnCodexClose').addEventListener('click', hideAllOverlays);
document.getElementById('btnMapClose').addEventListener('click', hideAllOverlays);
document.getElementById('btnShopClose').addEventListener('click', function() { hideAllOverlays(); document.getElementById('shrineMenu').classList.remove('hidden'); });

// Pause menu
document.getElementById('btnResume').addEventListener('click', function() { hideAllOverlays(); gameState.state = 'overworld'; });
document.getElementById('btnCodex').addEventListener('click', showCodex);
document.getElementById('btnMap').addEventListener('click', showZoneMap);
document.getElementById('btnMute').addEventListener('click', function() { audio.toggleMute(); showNotification(audio.muted ? 'Sound muted' : 'Sound enabled'); });

// Tutorial & dialogue
document.getElementById('btnTutorialNext').addEventListener('click', function() { tutorialStep++; showTutorial(); });
document.getElementById('btnDialogueNext').addEventListener('click', function() {
  document.getElementById('dialogueOverlay').classList.add('hidden');
  gameState.state = 'overworld';
});

// Victory & Game Over
document.getElementById('btnVictoryContinue').addEventListener('click', function() {
  hideAllOverlays();
  battleSystem.endBattle(false);
  gameState.state = 'overworld';
  audio.playVictory();
  tryTransform();
});

document.getElementById('btnGameOverContinue').addEventListener('click', function() {
  hideAllOverlays();
  battleSystem.endBattle(true);
  gameState.healTeam();
  gameState.state = 'overworld';
});

function gameLoop() {
  var ctx = canvas.getContext('2d');
  
  if (gameState.moveCooldown > 0) gameState.moveCooldown--;
  
  if (gameState.state === 'battle') {
    battleSystem.update();
  }
  
  if (gameState.state === 'title') {
    ctx.fillStyle = '#0a0a12';
    var w = canvas.width/(window.devicePixelRatio||1);
    var h = canvas.height/(window.devicePixelRatio||1);
    ctx.fillRect(0, 0, w, h);
    var t = Date.now() * 0.001;
    for (var i = 0; i < 30; i++) {
      var px = Math.sin(t + i * 0.7) * 200 + w/2;
      var py = Math.cos(t * 0.8 + i * 1.1) * 150 + h/2;
      ctx.globalAlpha = 0.15;
      ctx.fillStyle = '#a78bfa';
      ctx.beginPath();
      ctx.arc(px, py, 3 + Math.sin(t+i)*2, 0, Math.PI*2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;
  }
  else if (gameState.state === 'overworld' || gameState.state === 'shrine') {
    overworld.drawBackground();
  }
  else if (gameState.state === 'battle') {
    battleSystem.drawBattle();
  }
  
  requestAnimationFrame(gameLoop);
}

init();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"composer","sessionId":"e9932500-4d50-4a98-971c-32c55975c3b5","tokensIn":2905872,"tokensOut":60163,"tokensTotal":2966035,"turns":59,"toolCalls":58,"failedToolCalls":3,"timestamp":"2026-07-18T03:20:08.344Z"} -->