Creature Collection
Qwen 3.5 9b · composer

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 94,759 bytes · SHA-256 a3dd272a5d36
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>New Binding - Chronicle of the Resonant</title>
<style>
/* ── css: core ── */
/* New Binding - Chronicle of the Resonant */
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
html, body { width: 100%; height: 100%; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #0a0a12; color: #e8e8ff; }
/* Color palette - Resonance types */
:root { --bg-dark: #0a0a12; --bg-light: #151524; --accent: #7dd3fc; --accent-glow: rgba(125,211,252,0.3); --text-main: #e8e8ff; --text-muted: #9ca3af; --danger: #f87171; --success: #6ee7b7; --warning: #fbbf24; }
#app { position: relative; width: 100vw; height: 100vh; }
/* Canvas layers */
#world-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
#battle-canvas { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 800px; height: 600px; z-index: 2; border-radius: 12px; box-shadow: 0 0 40px rgba(0,0,0,0.5); display: none; }
/* Glassmorphism UI */
.ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; pointer-events: none; }
.modal-overlay { position: fixed; inset: 0; background: rgba(10,10,18,0.95); z-index: 20; display: flex; align-items: center; justify-content: center; pointer-events: auto; }
.modal { background: linear-gradient(135deg, rgba(21,21,36,0.95), rgba(10,10,18,0.98)); border-radius: 16px; padding: 32px; min-width: 480px; max-width: 90vw; max-height: 90vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0,0,0,0.6); backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.1); }
/* Buttons */
.btn { display: inline-flex; align-items: center; justify-content: center; padding: 14px 28px; border: none; border-radius: 10px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; text-transform: uppercase; letter-spacing: 0.5px; }
.btn-primary { background: linear-gradient(135deg, var(--accent), #60a5fa); color: #0f172a; box-shadow: 0 4px 20px var(--accent-glow); }
.btn-secondary { background: rgba(255,255,255,0.1); color: var(--text-main); border: 1px solid rgba(255,255,255,0.2); }
.btn:hover { transform: translateY(-2px); box-shadow: 0 8px 30px rgba(0,0,0,0.4); }
.btn:active { transform: translateY(0); }
/* Text styling */
h1 { font-size: 32px; text-align: center; background: linear-gradient(90deg, var(--accent), #c4b5fd, var(--accent)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 16px; }
h2 { font-size: 24px; color: var(--accent); margin-bottom: 12px; }
h3 { font-size: 18px; color: var(--text-main); margin-bottom: 8px; }
p { line-height: 1.6; color: var(--text-muted); margin-bottom: 12px; }
/* HUD elements */
.hp-bar { height: 20px; background: rgba(0,0,0,0.5); border-radius: 10px; overflow: hidden; position: relative; margin-top: 4px; }
.hp-fill { height: 100%; transition: width 0.3s ease; }
.hp-full { background: linear-gradient(90deg, #6ee7b7, var(--accent)); }
.hp-half { background: linear-gradient(90deg, #fbbf24, var(--accent)); }
.hp-low { background: linear-gradient(90deg, var(--danger), var(--accent)); }
/* Animations */
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
@keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
@keyframes glow { 0%, 100% { box-shadow: 0 0 10px var(--accent-glow); } 50% { box-shadow: 0 0 30px var(--accent-glow); } }
.animate-pulse { animation: pulse 2s ease-in-out infinite; }
.animate-float { animation: float 3s ease-in-out infinite; }
.animate-shake { animation: shake 0.3s ease-in-out; }
.animate-glow { animation: glow 2s ease-in-out infinite; }
/* Type colors */
.type-flux { border-color: #fbbf24; box-shadow: 0 0 15px rgba(251,191,36,0.3); }
.type-crysta { border-color: #818cf8; box-shadow: 0 0 15px rgba(129,140,248,0.3); }
.type-void { border-color: #7c3aed; box-shadow: 0 0 15px rgba(124,58,237,0.3); }
.type-ember { border-color: #f97316; box-shadow: 0 0 15px rgba(249,115,22,0.3); }
.type-storm { border-color: #06b6d4; box-shadow: 0 0 15px rgba(6,182,212,0.3); }
.type-iron { border-color: #eab308; box-shadow: 0 0 15px rgba(234,179,8,0.3); }
.type-spirit { border-color: #ec4899; box-shadow: 0 0 15px rgba(236,72,153,0.3); }
/* Scrollbar styling */
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: var(--bg-dark); }
::-webkit-scrollbar-thumb { background: var(--text-muted); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
/* Selection indicators */
.selected { outline: 3px solid var(--accent) !important; box-shadow: inset 0 0 20px rgba(125,211,252,0.2); }
/* Floating text container */
.floating-texts { position: absolute; pointer-events: none; font-weight: 700; font-size: 16px; text-shadow: 0 2px 4px rgba(0,0,0,0.5); z-index: 100; }
/* ── css: ui-systems ── */
/* Title Screen */
#title-screen { position: absolute; inset: 0; z-index: 50; display: flex; flex-direction: column; align-items: center; justify-content: center; background: radial-gradient(ellipse at center, #1a1a35 0%, var(--bg-dark) 60%); }
.title-logo { font-size: 72px; font-weight: 900; text-align: center; background: linear-gradient(180deg, var(--accent), #c4b5fd, var(--accent)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 0 0 60px rgba(125,211,252,0.5); animation: float 3s ease-in-out infinite; margin-bottom: 40px; }
.title-subtitle { font-size: 20px; color: var(--text-muted); letter-spacing: 4px; text-transform: uppercase; margin-bottom: 60px; }
.title-buttons { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; }
/* Battle Screen */
#battle-layout { position: relative; width: 100%; height: 100%; display: grid; grid-template-rows: auto 1fr auto; padding: 20px; gap: 16px; }
.battle-ui-top { display: flex; justify-content: space-between; align-items: center; background: rgba(21,21,36,0.8); border-radius: 12px; padding: 16px; backdrop-filter: blur(4px); }
.enemy-info { flex: 1; max-width: 350px; }
.enemy-name { font-size: 20px; color: var(--accent); margin-bottom: 8px; }
.enemy-hp-container { width: 100%; height: 24px; background: rgba(0,0,0,0.4); border-radius: 12px; overflow: hidden; }
.enemy-hp-bar { height: 100%; transition: width 0.3s ease; }
.battle-ui-center { display: flex; gap: 40px; align-items: center; justify-content: center; height: 100%; }
.battle-entity { position: relative; }
.entity-card { background: linear-gradient(135deg, rgba(21,21,36,0.9), rgba(10,10,18,0.9)); border-radius: 16px; padding: 16px; border: 2px solid transparent; transition: all 0.2s ease; }
.entity-name { font-size: 18px; color: var(--accent); text-align: center; margin-bottom: 8px; }
.entity-stats { display: flex; justify-content: space-between; font-size: 12px; color: var(--text-muted); }
.entity-status-icons { display: flex; gap: 4px; justify-content: center; margin-top: 8px; }
.status-icon { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; background: rgba(0,0,0,0.3); }
.battle-ui-bottom { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.move-btn { height: 64px; border: none; border-radius: 10px; background: rgba(255,255,255,0.08); color: var(--text-main); font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 8px; }
.move-btn:hover { background: rgba(255,255,255,0.15); transform: translateY(-4px); box-shadow: 0 8px 20px rgba(0,0,0,0.3); }
.move-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.move-name { font-size: 14px; color: var(--text-main); }
.move-type { font-size: 10px; text-transform: uppercase; letter-spacing: 1px; opacity: 0.7; margin-top: 4px; }
/* Party Selection */
.party-panel { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); background: rgba(21,21,36,0.9); border-radius: 16px; padding: 16px; display: flex; flex-direction: column; gap: 8px; min-width: 180px; }
.party-member { position: relative; background: rgba(255,255,255,0.05); border-radius: 10px; padding: 10px; cursor: pointer; border: 2px solid transparent; transition: all 0.2s ease; }
.party-member:hover { background: rgba(255,255,255,0.1); }
.party-member.selected { border-color: var(--accent); box-shadow: inset 0 0 10px rgba(125,211,252,0.2); }
/* Inventory */
.inventory-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 10px; padding: 16px; max-height: 400px; overflow-y: auto; }
.item-slot { width: 76px; height: 76px; background: rgba(255,255,255,0.05); border-radius: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; font-size: 24px; }
.item-slot:hover { background: rgba(255,255,255,0.1); transform: scale(1.05); }
/* Team */
.team-member { display: flex; align-items: center; gap: 12px; padding: 12px; margin-bottom: 8px; background: rgba(255,255,255,0.05); border-radius: 10px; cursor: pointer; transition: all 0.2s ease; }
.team-member:hover { background: rgba(255,255,255,0.1); }
.team-member.selected { background: rgba(125,211,252,0.15); border-left: 3px solid var(--accent); }
.team-avatar { width: 48px; height: 48px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 24px; background: rgba(255,255,255,0.1); }
.team-info { flex: 1; }
.team-level { font-weight: bold; color: var(--accent); }
.team-affinity { font-size: 11px; text-transform: uppercase; padding: 2px 6px; border-radius: 4px; display: inline-block; }
/* Encyclopedia entry */
.enc-entry { background: rgba(255,255,255,0.03); border-radius: 12px; overflow: hidden; margin-bottom: 16px; }
.enc-header { padding: 16px; display: flex; gap: 16px; align-items: center; }
.enc-avatar { width: 80px; height: 80px; border-radius: 12px; background: rgba(255,255,255,0.1); display: flex; align-items: center; justify-content: center; font-size: 36px; }
.enc-content { flex: 1; }
/* Dialog */
#dialog-container { position: absolute; bottom: 80px; left: 50%; transform: translateX(-50%); width: max-content; background: linear-gradient(180deg, rgba(21,21,36,0.98), rgba(10,10,18,0.95)); padding: 20px 32px; border-radius: 16px; margin-bottom: 24px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); max-width: 80%; }
.dialog-text { font-size: 16px; line-height: 1.8; }
/* Floating damage numbers */
.floating-text { position: absolute; pointer-events: none; animation: floatUp 1s ease-out forwards; text-shadow: 0 2px 4px rgba(0,0,0,0.5); z-index: 100; font-weight: 700; }
.floating-text.crit { color: #fbbf24; font-size: 24px; transform: scale(1.3); }
.floating-text.heal { color: #6ee7b7; }
.floating-text.super-effective { color: #c4b5fd; text-shadow: 0 0 10px rgba(196,181,253,0.5); }
/* Notification area */
#notifications { position: fixed; top: 20px; right: 20px; display: flex; flex-direction: column; gap: 8px; pointer-events: none; z-index: 100; }
.notification { padding: 12px 20px; background: rgba(10,10,18,0.9); border-radius: 8px; font-size: 14px; animation: slideIn 0.3s ease-out; border-left: 4px solid var(--accent); box-shadow: 0 4px 20px rgba(0,0,0,0.3); }
@keyframes slideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } }
/* Type badges */
.type-badge.flux { background: linear-gradient(135deg, #fbbf24, #fb923c); color: #7c2d12; }
.type-badge.crysta { background: linear-gradient(135deg, #818cf8, #6366f1); color: #312e81; }
.type-badge.void { background: linear-gradient(135deg, #7c3aed, #6d28d9); color: #4c1d95; }
.type-badge.ember { background: linear-gradient(135deg, #f97316, #ea580c); color: #7c2d12; }
.type-badge.storm { background: linear-gradient(135deg, #06b6d4, #0891b2); color: #164e63; }
.type-badge.iron { background: linear-gradient(135deg, #eab308, #ca8a04); color: #573b02; }
.type-badge.spirit { background: linear-gradient(135deg, #ec4899, #db2777); color: #831843; }
/* Story vignette */
.story-vignette { position: fixed; inset: 0; z-index: 60; pointer-events: none; opacity: 0; transition: opacity 1s ease; background: var(--bg-dark); }
.story-vignette.active { opacity: 1; }
/* Overlay for battle end */
.battle-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; z-index: 60; opacity: 0; transition: opacity 0.5s ease; }
.battle-overlay.visible { opacity: 1; }
.battle-message { font-size: 42px; font-weight: 900; text-transform: uppercase; letter-spacing: 8px; text-align: center; }
/* ── css: screens ── */
/* Inventory Screen */
.screen-title { font-size: 28px; color: var(--accent); margin-bottom: 16px; padding-bottom: 12px; border-bottom: 2px solid rgba(255,255,255,0.1); }
.screen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.back-btn { padding: 8px 24px; background: rgba(255,255,255,0.1); color: var(--text-main); border-radius: 8px; cursor: pointer; transition: all 0.2s; font-weight: 600; }
.back-btn:hover { background: rgba(255,255,255,0.2); }
/* Item types */
.item-type-herb { color: #6ee7b7; }
.item-type-catalyst { color: #fbbf24; }
.item-type-collection { color: #ec4899; }
/* Encyclopedia */
.enc-tabs { display: flex; gap: 8px; margin-bottom: 16px; }
.enc-tab { padding: 10px 20px; background: rgba(255,255,255,0.08); border-radius: 8px; cursor: pointer; transition: all 0.2s; color: var(--text-muted); font-weight: 600; }
.enc-tab.active { background: var(--accent); color: #1e293b; }
/* Map */
#map-container { position: absolute; inset: 0; z-index: 5; }
.map-overlay { position: absolute; inset: 0; display: grid; grid-template-rows: auto 1fr auto; padding: 24px; gap: 16px; }
.map-header { display: flex; justify-content: space-between; align-items: center; background: rgba(21,21,36,0.9); border-radius: 12px; padding: 16px; backdrop-filter: blur(4px); }
.map-info h2 { font-size: 20px; color: var(--accent); margin-bottom: 8px; }
.map-stats { display: flex; gap: 16px; }
/* Zone info */
.zone-badge { display: inline-block; padding: 4px 12px; background: rgba(255,255,255,0.1); border-radius: 20px; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; }
/* Shrine */
.shrine-icon { font-size: 36px; margin-bottom: 8px; display: block; animation: pulse 2s ease-in-out infinite; }
/* Shop */
.shop-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; max-height: 50vh; overflow-y: auto; }
.shop-item { background: rgba(255,255,255,0.05); border-radius: 10px; padding: 16px; text-align: center; cursor: pointer; transition: all 0.2s; border: 2px solid transparent; }
.shop-item:hover { background: rgba(255,255,255,0.1); transform: scale(1.02); }
.shop-item.bought { opacity: 0.4; cursor: default; }
/* Transform notification */
.transform-notification { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: linear-gradient(135deg, rgba(21,21,36,0.98), rgba(10,10,18,0.95)); border-radius: 20px; padding: 32px; text-align: center; box-shadow: 0 20px 60px rgba(125,211,252,0.3); z-index: 80; pointer-events: none; opacity: 0; transition: all 0.5s ease; }
.transform-notification.visible { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.transform-title { font-size: 32px; color: var(--accent); margin-bottom: 16px; animation: float 3s ease-in-out infinite; }
/* Tutorial */
.tutorial-highlight { position: relative; }
.tutorial-highlight::after { content: ''; position: absolute; inset: -4px; border-radius: 8px; box-shadow: inset 0 0 0 2px var(--accent), inset 0 0 30px rgba(125,211,252,0.3); opacity: 0; transition: opacity 0.3s; pointer-events: none; }
.tutorial-highlight:hover::after { opacity: 1; }
/* Loading */
#loading-screen { position: fixed; inset: 0; z-index: 100; background: var(--bg-dark); display: flex; flex-direction: column; align-items: center; justify-content: center; }
.loading-spinner { width: 64px; height: 64px; border: 4px solid rgba(125,211,252,0.2); border-top-color: var(--accent); border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Screen transitions */
.screen-transition { position: fixed; inset: 0; z-index: 70; background: var(--bg-dark); pointer-events: none; opacity: 0; transition: opacity 0.5s ease; }
.screen-transition.fade-out { opacity: 1; }
/* Affinity type classes */
.type-flux { border-color: #fbbf24 !important; color: #fbbf24; background: rgba(251,191,36,0.1); }
.type-crysta { border-color: #818cf8 !important; color: #818cf8; background: rgba(129,140,248,0.1); }
.type-void { border-color: #7c3aed !important; color: #7c3aed; background: rgba(124,58,237,0.1); }
.type-ember { border-color: #f97316 !important; color: #f97316; background: rgba(249,115,22,0.1); }
.type-storm { border-color: #06b6d4 !important; color: #06b6d4; background: rgba(6,182,212,0.1); }
.type-iron { border-color: #eab308 !important; color: #eab308; background: rgba(234,179,8,0.1); }
.type-spirit { border-color: #ec4899 !important; color: #ec4899; background: rgba(236,72,153,0.1); }
/* XP bar */
.xp-bar-container { height: 8px; background: rgba(0,0,0,0.4); border-radius: 4px; overflow: hidden; margin-top: 4px; }
.xp-fill { height: 100%; width: 0%; transition: width 0.3s ease; background: linear-gradient(90deg, var(--accent), #8b5cf6); }
</style>
</head>
<body>
<div id="app"></div>
<div id="loading-screen"><div class="loading-spinner"></div><p style="margin-top: 16px; color: var(--text-muted)">Loading New Binding...</p></div>
<div id="title-screen" class="modal-overlay">
<h1 class="title-logo">New Binding</h1>
<p class="title-subtitle">Chronicle of the Resonant</p>
<div class="title-buttons">
<button id="btn-start-new" class="btn btn-primary">New Journey</button>
<button id="btn-continue" class="btn btn-secondary">Continue</button>
</div>
</div>
<div id="story-vignette" class="story-vignette"></div>
<div id="dialog-container" class="ui-layer"></div>
<div id="notifications"></div>
<canvas id="world-canvas" width="800" height="600"></canvas>
<canvas id="battle-canvas" width="800" height="600"></canvas>
<div id="transform-notification" class="transform-notification">
<h2 class="transform-title">Evolution!</h2>
</div>
<script>
'use strict';
// ── const: AFFINITIES ──
/* Type System - 7 Resonance Affinities */
const AFFINITIES = {
FLUX: { id: 'flux', name: 'Flux', color: '#fbbf24', description: "Unstable energy that shatters barriers." },
CRYSTA: { id: 'crysta', name: 'Crysta', color: '#818cf8', description: "Hard crystal structures that deflect attacks." },
VOID: { id: 'void', name: 'Void', color: '#7c3aed', description: "Manipulates shadows and reality itself." },
EMBER: { id: 'ember', name: 'Ember', color: '#f97316', description: "Burning heat and explosive fire." },
STORM: { id: 'storm', name: 'Storm', color: '#06b6d4', description: "Electrical discharge and atmospheric force." },
IRON: { id: 'iron', name: 'Iron', color: '#eab308', description: "Unbreakable metal and industrial might." },
SPIRIT: { id: 'spirit', name: 'Spirit', color: '#ec4899', description: "Ethereal essence that transcends form." }
};
// ── const: TYPE_CHART ──
const TYPE_CHART = {
flux: { strong_against: ['crysta'], weak_against: ['ember'] },
crysta: { strong_against: ['flux'], weak_against: ['storm'] },
void: { strong_against: ['spirit'], weak_against: ['iron'] },
ember: { strong_against: ['void', 'crysta'], weak_against: ['crysta', 'void'] },
storm: { strong_against: ['flux', 'ember'], weak_against: ['crysta', 'void'] },
iron: { strong_against: ['storm', 'ember'], weak_against: ['spirit'] },
spirit: { strong_against: ['iron', 'storm', 'flux', 'ember'], weak_against: ['flux'] }
};
// ── const: TYPE_COLORS ──
const TYPE_COLORS = { flux: '#fbbf24', crysta: '#818cf8', void: '#7c3aed', ember: '#f97316', storm: '#06b6d4', iron: '#eab308', spirit: '#ec4899' };
// ── const: CREATURE_DATA ──
/* Base Creatures - 18 unique entities */
const CREATURE_DATA = [
// Starter Trio
{ id: 'lumivolt', name: 'Lumivolt', affinity: 'flux', lore: "A small creature of living lightning that dances on currents of energy.", hp: 28, atk: 14, def: 10, spd: 16, abilities: [
{ id: 'spark', name: 'Static Spark', power: 12, accuracy: 95, pp: 25, type: 'flux', effect: 'none' },
{ id: 'electro-blast', name: 'Electro Blast', power: 22, accuracy: 88, pp: 15, type: 'flux', effect: 'speed_down' },
{ id: 'overcharge', name: 'Overcharge', power: 28, accuracy: 78, pp: 10, type: 'flux', effect: 'self_heal_10' },
{ id: 'voltic-fury', name: 'Voltic Fury', power: 38, accuracy: 65, pp: 6, type: 'flux', effect: 'crit_boost' }
]},
{ id: 'petallia', name: 'Petallia', affinity: 'crysta', lore: "A crystalline being that forms beautiful geometric patterns from pure energy.", hp: 30, atk: 12, def: 16, spd: 14, abilities: [
{ id: 'ice-shard', name: 'Frost Shard', power: 10, accuracy: 98, pp: 25, type: 'crysta', effect: 'none' },
{ id: 'glacial-surge', name: 'Glacial Surge', power: 24, accuracy: 90, pp: 12, type: 'crysta', effect: 'speed_down' },
{ id: 'freezing-gaze', name: 'Freezing Gaze', power: 18, accuracy: 85, pp: 15, type: 'crysta', effect: 'frost' },
{ id: 'ice-wall', name: 'Ice Wall', power: 20, accuracy: 95, pp: 12, type: 'crysta', effect: 'shield_def_30' }
]},
{ id: 'obsidia', name: 'Obsidia', affinity: 'void', lore: "Shadows manifest as this creature, able to bend light and reality.", hp: 26, atk: 16, def: 12, spd: 18, abilities: [
{ id: 'shadow-strike', name: 'Shadow Strike', power: 14, accuracy: 95, pp: 25, type: 'void', effect: 'none' },
{ id: 'dark-pulse', name: 'Dark Pulse', power: 26, accuracy: 88, pp: 12, type: 'void', effect: 'speed_down' },
{ id: 'phantom-vein', name: 'Phantom Vein', power: 30, accuracy: 75, pp: 8, type: 'void', effect: 'self_heal_15' },
{ id: 'eclipse', name: 'Eclipse', power: 42, accuracy: 60, pp: 5, type: 'void', effect: 'crit_boost' }
]},
// Zone 1 Creatures
{ id: 'ferrum', name: 'Ferrum', affinity: 'iron', lore: "A mechanical construct built from scrap metal that seeks to protect its territory.", hp: 32, atk: 18, def: 18, spd: 10, abilities: [
{ id: 'metal-bash', name: 'Metal Bash', power: 16, accuracy: 95, pp: 25, type: 'iron', effect: 'none' },
{ id: 'steel-slam', name: 'Steel Slam', power: 28, accuracy: 85, pp: 12, type: 'iron', effect: 'speed_down' },
{ id: 'reinforce', name: 'Reinforce', power: 0, accuracy: 100, pp: 15, type: 'iron', effect: 'def_up_20' },
{ id: 'heavy-fist', name: 'Heavy Fist', power: 36, accuracy: 70, pp: 8, type: 'iron', effect: 'self_heal_12' }
]},
{ id: 'wraithwing', name: 'Wraithwing', affinity: 'spirit', lore: "A spectral bird that haunts the ruins of fallen civilizations.", hp: 24, atk: 20, def: 10, spd: 20, abilities: [
{ id: 'phantom-strike', name: 'Phantom Strike', power: 15, accuracy: 95, pp: 25, type: 'spirit', effect: 'none' },
{ id: 'shadow-dive', name: 'Shadow Dive', power: 26, accuracy: 85, pp: 12, type: 'spirit', effect: 'crit_boost' },
{ id: 'soul-sever', name: 'Soul Sever', power: 34, accuracy: 70, pp: 8, type: 'spirit', effect: 'self_heal_10' },
{ id: 'spectral-wake', name: 'Spectral Wake', power: 22, accuracy: 95, pp: 12, type: 'spirit', effect: 'speed_up' }
]},
// Zone 2 Creatures
{ id: 'infernum', name: 'Infernum', affinity: 'ember', lore: "A fiery construct born from the heart of ancient volcanoes.", hp: 34, atk: 20, def: 16, spd: 12, abilities: [
{ id: 'fire-bolt', name: 'Fire Bolt', power: 18, accuracy: 95, pp: 25, type: 'ember', effect: 'none' },
{ id: 'flame-ring', name: 'Flame Ring', power: 20, accuracy: 90, pp: 15, type: 'ember', effect: 'burn' },
{ id: 'inferno-fury', name: 'Inferno Fury', power: 32, accuracy: 75, pp: 8, type: 'ember', effect: 'none' },
{ id: 'volcanic-eruption', name: 'Volcanic Eruption', power: 40, accuracy: 65, pp: 6, type: 'ember', effect: 'burn_2' }
]},
{ id: 'tempestrix', name: 'Tempestrix', affinity: 'storm', lore: "A creature of swirling clouds that controls lightning with telekinetic force.", hp: 30, atk: 18, def: 14, spd: 22, abilities: [
{ id: 'thunder-jolt', name: 'Thunder Jolt', power: 16, accuracy: 95, pp: 25, type: 'storm', effect: 'none' },
{ id: 'lightning-swarm', name: 'Lightning Swarm', power: 24, accuracy: 88, pp: 12, type: 'storm', effect: 'burn_30' },
{ id: 'cyclone-whirl', name: 'Cyclone Whirl', power: 26, accuracy: 85, pp: 10, type: 'storm', effect: 'speed_down' },
{ id: 'thunder-cage', name: 'Thunder Cage', power: 34, accuracy: 70, pp: 6, type: 'storm', effect: 'paralyze' }
]},
// Zone 3 Creatures
{ id: 'nethshade', name: 'Nethshade', affinity: 'void', lore: "A manifestation of the void that devours light and hope.", hp: 36, atk: 22, def: 14, spd: 18, abilities: [
{ id: 'void-stab', name: 'Void Stab', power: 18, accuracy: 95, pp: 25, type: 'void', effect: 'none' },
{ id: 'dark-swarm', name: 'Dark Swarm', power: 26, accuracy: 90, pp: 12, type: 'void', effect: 'speed_down' },
{ id: 'void-wisp', name: 'Void Wisp', power: 30, accuracy: 85, pp: 10, type: 'void', effect: 'self_heal_12' },
{ id: 'abyss-call', name: 'Abyss Call', power: 44, accuracy: 60, pp: 5, type: 'void', effect: 'crit_boost' }
]},
{ id: 'quartzix', name: 'Quartzix', affinity: 'crysta', lore: "A living crystal that sings with the frequency of pure energy.", hp: 32, atk: 16, def: 20, spd: 15, abilities: [
{ id: 'shard-thrust', name: 'Shard Thrust', power: 14, accuracy: 98, pp: 25, type: 'crysta', effect: 'none' },
{ id: 'prism-shield', name: 'Prism Shield', power: 18, accuracy: 95, pp: 15, type: 'crysta', effect: 'shield_def_40' },
{ id: 'shatter-point', name: 'Shatter Point', power: 28, accuracy: 80, pp: 10, type: 'crysta', effect: 'crit_boost' },
{ id: 'refractive-blast', name: 'Refractive Blast', power: 36, accuracy: 65, pp: 6, type: 'crysta', effect: 'speed_down' }
]},
// Zone 4 Creatures
{ id: 'pyreheart', name: 'Pyreheart', affinity: 'ember', lore: "A being of pure conflagration that burns with cosmic fire.", hp: 38, atk: 24, def: 16, spd: 14, abilities: [
{ id: 'blaze-fang', name: 'Blaze Fang', power: 20, accuracy: 95, pp: 25, type: 'ember', effect: 'none' },
{ id: 'inferno-dash', name: 'Inferno Dash', power: 26, accuracy: 88, pp: 12, type: 'ember', effect: 'speed_up' },
{ id: 'bonfire-surge', name: 'Bonfire Surge', power: 30, accuracy: 80, pp: 10, type: 'ember', effect: 'burn' },
{ id: 'apocalypse-flare', name: 'Apocalypse Flare', power: 42, accuracy: 65, pp: 5, type: 'ember', effect: 'burn_30' }
]},
{ id: 'titanforged', name: 'Titanforged', affinity: 'iron', lore: "A massive war construct that channels the power of ancient factories.", hp: 40, atk: 22, def: 22, spd: 10, abilities: [
{ id: 'plating-swing', name: 'Plating Swing', power: 18, accuracy: 95, pp: 25, type: 'iron', effect: 'none' },
{ id: 'industrial-stomp', name: 'Industrial Stomp', power: 26, accuracy: 85, pp: 12, type: 'iron', effect: 'speed_down' },
{ id: 'hardened-core', name: 'Hardened Core', power: 0, accuracy: 100, pp: 12, type: 'iron', effect: 'def_up_30' },
{ id: 'planet-crush', name: 'Planet Crush', power: 40, accuracy: 65, pp: 6, type: 'iron', effect: 'self_heal_15' }
]},
// Zone 5 Creatures
{ id: 'zephyron', name: 'Zephyron', affinity: 'storm', lore: "A storm giant that channels atmospheric pressure into devastating attacks.", hp: 36, atk: 24, def: 18, spd: 16, abilities: [
{ id: 'air-blast', name: 'Air Blast', power: 16, accuracy: 98, pp: 25, type: 'storm', effect: 'none' },
{ id: 'hurricane-spin', name: 'Hurricane Spin', power: 24, accuracy: 88, pp: 12, type: 'storm', effect: 'speed_down' },
{ id: 'tempest-call', name: 'Tempest Call', power: 28, accuracy: 80, pp: 10, type: 'storm', effect: 'speed_up' },
{ id: 'typhoon-storm', name: 'Typhoon Storm', power: 40, accuracy: 65, pp: 6, type: 'storm', effect: 'paralyze' }
]},
{ id: 'etheria', name: 'Etheria', affinity: 'spirit', lore: "A spirit bound to the world itself that manipulates souls and luck.", hp: 34, atk: 22, def: 16, spd: 20, abilities: [
{ id: 'spirit-touch', name: 'Spirit Touch', power: 14, accuracy: 98, pp: 25, type: 'spirit', effect: 'heal_10' },
{ id: 'phantom-flurry', name: 'Phantom Flurry', power: 26, accuracy: 85, pp: 12, type: 'spirit', effect: 'crit_boost' },
{ id: 'soul-link', name: 'Soul Link', power: 0, accuracy: 100, pp: 10, type: 'spirit', effect: 'self_heal_15' },
{ id: 'ether-blade', name: 'Ether Blade', power: 38, accuracy: 70, pp: 6, type: 'spirit', effect: 'crit_boost' }
]},
// Zone 6 Creatures (Guardians/Bosses)
{ id: 'golemx', name: 'Golemx', affinity: 'iron', lore: "The iron guardian of the first zone, a massive construct from ancient machinery.", hp: 80, atk: 28, def: 35, spd: 12, abilities: [
{ id: 'golem-punch', name: 'Golem Punch', power: 22, accuracy: 95, pp: 25, type: 'iron', effect: 'none' },
{ id: 'steel-tremor', name: 'Steel Tremor', power: 28, accuracy: 85, pp: 12, type: 'iron', effect: 'speed_down' },
{ id: 'fortification', name: 'Fortification', power: 0, accuracy: 100, pp: 10, type: 'iron', effect: 'def_up_40' },
{ id: 'tectonic-slash', name: 'Tectonic Slash', power: 38, accuracy: 75, pp: 8, type: 'iron', effect: 'none' }
]},
{ id: 'magmaheart', name: 'Magmaheart', affinity: 'ember', lore: "The heart of the volcanic zone, a being of pure magma and fire.", hp: 90, atk: 30, def: 28, spd: 14, abilities: [
{ id: 'lava-spit', name: 'Lava Spit', power: 24, accuracy: 95, pp: 25, type: 'ember', effect: 'burn' },
{ id: 'magma-dome', name: 'Magma Dome', power: 30, accuracy: 85, pp: 10, type: 'ember', effect: 'shield_def_50' },
{ id: 'volcanic-eruption', name: 'Volcanic Eruption', power: 40, accuracy: 70, pp: 6, type: 'ember', effect: 'burn_30' },
{ id: 'inferno-heart', name: 'Inferno Heart', power: 35, accuracy: 80, pp: 10, type: 'ember', effect: 'self_heal_20' }
]},
{ id: 'voidwalker', name: 'Voidwalker', affinity: 'void', lore: "A being from beyond reality that walks the boundary between worlds.", hp: 85, atk: 32, def: 24, spd: 20, abilities: [
{ id: 'shadow-dagger', name: 'Shadow Dagger', power: 26, accuracy: 95, pp: 25, type: 'void', effect: 'none' },
{ id: 'dark-swarm', name: 'Dark Swarm', power: 28, accuracy: 85, pp: 10, type: 'void', effect: 'speed_down' },
{ id: 'void-step', name: 'Void Step', power: 36, accuracy: 75, pp: 8, type: 'void', effect: 'crit_boost' },
{ id: 'reality-break', name: 'Reality Break', power: 42, accuracy: 70, pp: 6, type: 'void', effect: 'self_heal_15' }
]},
{ id: 'soulguard', name: 'Soulguard', affinity: 'spirit', lore: "The guardian of souls that watches over all living creatures.", hp: 75, atk: 26, def: 30, spd: 18, abilities: [
{ id: 'soul-aura', name: 'Soul Aura', power: 18, accuracy: 98, pp: 25, type: 'spirit', effect: 'none' },
{ id: 'phantom-fang', name: 'Phantom Fang', power: 26, accuracy: 85, pp: 12, type: 'spirit', effect: 'crit_boost' },
{ id: 'soul-shield', name: 'Soul Shield', power: 0, accuracy: 100, pp: 10, type: 'spirit', effect: 'shield_def_40' },
{ id: 'siphon-soul', name: 'Siphon Soul', power: 32, accuracy: 80, pp: 8, type: 'spirit', effect: 'self_heal_20' }
]}
];
// ── const: ITEMS ──
/* Item Database */
const ITEMS = {
// Collection Items (3 tiers)
capture_basic: { id: 'capture_basic', name: 'Resonance Orb', type: 'collection', tier: 1, rarity: 'common', description: 'A basic resonant orb that captures creatures.', effect: 'capture' },
capture_adv: { id: 'capture_adv', name: 'Prism Orb', type: 'collection', tier: 2, rarity: 'uncommon', description: 'A refined orb with enhanced capture properties.', effect: 'capture' },
capture_elite: { id: 'capture_elite', name: 'Void Core', type: 'collection', tier: 3, rarity: 'rare', description: 'A powerful core that guarantees high capture success.', effect: 'capture' },
// Restoration Consumables
potion_small: { id: 'potion_small', name: 'Essence Vial', type: 'heal', effect: 'heal_25' },
potion_medium: { id: 'potion_medium', name: 'Essence Flask', type: 'heal', effect: 'heal_50' },
potion_large: { id: 'potion_large', name: 'Primal Elixir', type: 'heal', effect: 'heal_full' },
bandage: { id: 'bandage', name: 'Field Bandage', type: 'heal', effect: 'heal_30' },
// Catalysts for Transformation
catalyst_basic: { id: 'catalyst_basic', name: 'Resonance Crystal', type: 'catalyst', tier: 1, description: 'Transforms creatures at level 20+.' },
catalyst_advanced: { id: 'catalyst_advanced', name: 'Prism Shard', type: 'catalyst', tier: 2, description: 'Transforms creatures at level 35+.' },
catalyst_elite: { id: 'catalyst_elite', name: 'Void Fragment', type: 'catalyst', tier: 3, description: 'Transforms creatures at level 45+.' },
// Field Tools
map_scroll: { id: 'map_scroll', name: 'World Map Scroll', type: 'tool', effect: 'view_map' },
compass: { id: 'compass', name: 'Resonance Compass', type: 'tool', effect: 'locate_nearby' }
};
// ── const: ZONES ──
/* Zone Definitions */
const ZONES = [
{ id: 0, name: 'Shimmering Fields', color: '#86efac', gradient: 'linear-gradient(180deg, #2d5a3e 0%, #166534 100%)', background: 'linear-gradient(180deg, #2d5a3e 0%, #166534 100%)', particleType: 'sparkle', creatureRates: { flux: 0.3, crysta: 0.2, void: 0.2, ember: 0.1, storm: 0.1, iron: 0.1, spirit: 0.1 }, bossId: 'golemx' },
{ id: 1, name: 'Crystal Caverns', color: '#818cf8', gradient: 'linear-gradient(180deg, #3730a3 0%, #4c1d95 100%)', background: 'linear-gradient(180deg, #3730a3 0%, #4c1d95 100%)', particleType: 'crystal', creatureRates: { flux: 0.2, crysta: 0.3, void: 0.2, ember: 0.1, storm: 0.1, iron: 0.1, spirit: 0.1 }, bossId: null },
{ id: 2, name: 'Obsidian Wastes', color: '#7c3aed', gradient: 'linear-gradient(180deg, #4a044e 0%, #2e1065 100%)', background: 'linear-gradient(180deg, #4a044e 0%, #2e1065 100%)', particleType: 'ember', creatureRates: { flux: 0.1, crysta: 0.1, void: 0.3, ember: 0.2, storm: 0.1, iron: 0.1, spirit: 0.1 }, bossId: null },
{ id: 3, name: 'Prism Depths', color: '#fbbf24', gradient: 'linear-gradient(180deg, #713f12 0%, #9a3412 100%)', background: 'linear-gradient(180deg, #713f12 0%, #9a3412 100%)', particleType: 'shard', creatureRates: { flux: 0.15, crysta: 0.25, void: 0.2, ember: 0.2, storm: 0.15, iron: 0.15, spirit: 0.1 }, bossId: 'magmaheart' },
{ id: 4, name: 'Storm Peaks', color: '#06b6d4', gradient: 'linear-gradient(180deg, #0c4a6e 0%, #0f766e 100%)', background: 'linear-gradient(180deg, #0c4a6e 0%, #0f766e 100%)', particleType: 'lightning', creatureRates: { flux: 0.2, crysta: 0.2, void: 0.15, ember: 0.15, storm: 0.25, iron: 0.1, spirit: 0.1 }, bossId: null },
{ id: 5, name: 'Ethereal Sanctum', color: '#ec4899', gradient: 'linear-gradient(180deg, #831843 0%, #7f1d1d 100%)', background: 'linear-gradient(180deg, #831843 0%, #7f1d1d 100%)', particleType: 'spirit', creatureRates: { flux: 0.2, crysta: 0.2, void: 0.15, ember: 0.15, storm: 0.2, iron: 0.1, spirit: 0.3 }, bossId: 'voidwalker' }
];
// ── const: STORY_NODES ──
/* Story Nodes */
const STORY_NODES = [
{ zone: 0, text: "The journey begins in the Shimmering Fields... A mysterious force awakens within you.", color: '#86efac', title: "Awakening" },
{ zone: 1, text: "Crystal Caverns hold secrets of the ancients. Your path grows clearer...", color: '#818cf8', title: "The Crystalline Path" },
{ zone: 3, text: "Prism Depths burn with a strange heat. The guardian stirs... Defy it!", color: '#fbbf24', title: "Volcanic Challenge" },
{ zone: 5, text: "The Ethereal Sanctum pulses with otherworldly power. You stand ready...", color: '#ec4899', title: "Final Confrontation" }
];
// ── const: STATUS_EFFECTS ──
/* Status Effects */
const STATUS_EFFECTS = {
burn: { name: 'Burn', icon: '🔥', duration: 3, description: 'Takes damage each turn' },
frost: { name: 'Frost', icon: '❄️', duration: 3, description: 'Speed reduced by 50%' },
shock: { name: 'Shock', icon: '⚡', duration: 2, description: 'Chance to skip a turn' },
barrier: { name: 'Barrier', icon: '🛡️', duration: 1, description: 'Deflects one hit' },
regen: { name: 'Regen', icon: '💚', duration: 3, description: 'Heals each turn' }
};
// ── const: TRANSFORMATIONS ──
/* Transform Forms */
const TRANSFORMATIONS = [
{ base: 'lumivolt', form: 'lumivolt-primal', prefix: 'Primal', levelMin: 25, catalyst: 'catalyst_basic' },
{ base: 'petallia', form: 'petallia-primal', prefix: 'Primal', levelMin: 25, catalyst: 'catalyst_basic' },
{ base: 'obsidia', form: 'obsidia-primal', prefix: 'Primal', levelMin: 25, catalyst: 'catalyst_basic' },
{ base: 'ferrum', form: 'ferrum-primal', prefix: 'Primal', levelMin: 30, catalyst: 'catalyst_advanced' },
{ base: 'wraithwing', form: 'wraithwing-primal', prefix: 'Primal', levelMin: 25, catalyst: 'catalyst_basic' },
{ base: 'infernum', form: 'infernum-primal', prefix: 'Primal', levelMin: 30, catalyst: 'catalyst_advanced' },
{ base: 'tempestrix', form: 'tempestrix-primal', prefix: 'Primal', levelMin: 25, catalyst: 'catalyst_basic' },
{ base: 'nethshade', form: 'nethshade-primal', prefix: 'Primal', levelMin: 30, catalyst: 'catalyst_advanced' },
{ base: 'quartzix', form: 'quartzix-primal', prefix: 'Primal', levelMin: 25, catalyst: 'catalyst_basic' }
];
// ── const: ABILITIES ──
/* Ability Registry */
const ABILITIES = {}; // Will be populated from creature data
// ── function: initAbilities ──
function initAbilities() {
CREATURE_DATA.forEach(c => {
c.abilities.forEach(abi => {
if (!ABILITIES[abi.id]) {
ABILITIES[abi.id] = {
id: abi.id,
name: abi.name,
power: abi.power || 10,
accuracy: abi.accuracy,
pp: abi.pp,
type: abi.type,
effect: abi.effect
};
}
});
});
}
// ── function: getTypeMultiplier ──
// Get damage multiplier from type chart
function getTypeMultiplier(attackerType, defenderType) {
const chart = TYPE_CHART[attackerType];
if (chart && chart.strong_against.includes(defenderType)) return 1.5; // Super effective
return 1.0;
}
// ── function: getTypeDamage ──
// Check for weakness/resistance
function getTypeDamage(attackerType, defenderType) {
const multiplier = getTypeMultiplier(attackerType, defenderType);
if (multiplier > 1) return { multiplier: multiplier, isWeakness: true };
return { multiplier: 1.0, isWeakness: false };
}
// ── function: calcStats ──
// Calculate creature stats based on level and base stats
function calcStats(baseCreature, level) {
// Level scaling for stats
const levelMultiplier = (level / 20); // Max at level 50
return {
id: baseCreature.id,
name: baseCreature.name,
affinity: baseCreature.affinity,
lore: baseCreature.lore,
// Level-up scaled stats
maxHp: Math.floor(30 * levelMultiplier + (baseCreature.hp - 30) * levelMultiplier / 10),
atk: Math.floor(14 * levelMultiplier + (baseCreature.atk - 14) * levelMultiplier / 10),
def: Math.floor(12 * levelMultiplier + (baseCreature.def - 12) * levelMultiplier / 10),
spd: Math.floor(14 * levelMultiplier + (baseCreature.spd - 14) * levelMultiplier / 10),
abilities: baseCreature.abilities.map(a => ({
...a,
currentPP: a.pp
})),
// Form data for transformations
formPrefix: null,
isTransformed: false,
xpToNextLevel: Math.floor(100 * (level + 1))
};
}
// ── function: calculateDamage ──
// Calculate damage using the formula provided
function calculateDamage(attacker, defender, ability) {
// Get type multiplier
const typeData = getTypeDamage(ability.type, defender.affinity);
// Apply STAB bonus if same affinity
let stabMultiplier = 1.0;
if (ability.type === attacker.affinity) {
stabMultiplier = 1.25;
}
// Critical hit check
const critChance = ability.power > 30 ? 0.06 : 0.04; // Higher power moves more likely to crit
const isCrit = Math.random() < critChance;
const critMultiplier = isCrit ? 1.5 : 1.0;
// Damage formula: (0.4 × Level × ATK / DEF × Multiplier) + small random
const baseDamage = Math.floor(0.4 * attacker.level * attacker.atk / defender.def);
const damage = Math.max(1, Math.floor(baseDamage * typeData.multiplier * stabMultiplier * critMultiplier));
return {
damage: damage,
isCrit: isCrit,
isSuperEffective: typeData.isWeakness,
multiplier: typeData.multiplier * stabMultiplier * critMultiplier
};
}
// ── function: applyStatusEffect ──
// Apply status effects
function applyStatusEffect(target, effectId) {
const effect = STATUS_EFFECTS[effectId];
if (!effect) return null;
let effectKey = 'status-' + effectId;
if (!target.effects[effectKey]) {
target.effects[effectKey] = { ...effect, duration: effect.duration };
showStatusNotification(target.name, effect.icon);
}
return target.effects[effectKey];
}
// ── function: healEntity ──
// Healers and status healers
function healEntity(entity, amount) {
const healAmount = Math.min(amount, entity.maxHp - entity.hp);
entity.hp = Math.min(entity.hp + healAmount, entity.maxHp);
return healAmount;
}
// ── function: calculateCaptureRate ──
// Calculate capture success rate
function calculateCaptureRate(target, itemTier) {
const tierMultipliers = { 1: 0.6, 2: 0.8, 3: 1.0 };
const targetHealthFactor = target.hp / target.maxHp;
// Base rate decreases as HP gets lower
let baseRate = (1 - target.healthPercent) * 0.5;
return Math.min(1.0, (baseRate + tierMultipliers[itemTier] - 0.4) * 1.2);
}
// ── function: spawnWildCreature ──
// Create a wild creature encounter
function spawnWildCreature(zoneId) {
const zone = ZONES[zoneId];
// Pick a creature based on zone rates
const typeRoll = Math.random();
let cumulative = 0;
let selectedType = 'flux';
for (const [type, rate] of Object.entries(zone.creatureRates)) {
cumulative += rate;
if (typeRoll < cumulative) {
selectedType = type;
}
}
// Find a creature with this affinity in the current zone
const creaturesInZone = CREATURE_DATA.filter(c => {
if (zone.bossId && c.id === zone.bossId) return true; // Bosses always available
// Skip bosses for wild encounters unless it's that zone's boss
if (c.hp > 50 && !zone.bossId) return false;
return true;
});
// Filter by affinity and pick one
const matching = creaturesInZone.filter(c => c.affinity === selectedType);
const creature = matching[Math.floor(Math.random() * matching.length)];
// Scale wild creature stats based on level range
const levelRange = [15, 25];
const level = Math.floor(Math.random() * (levelRange[1] - levelRange[0] + 1)) + levelRange[0];
return calcStats(creature, level);
}
// ── function: showFloatingText ──
// Show floating damage text
function showFloatingText(x, y, text, type = 'damage') {
const el = document.createElement('div');
el.className = 'floating-text';
el.style.left = x + 'px';
el.style.top = (y - 20) + 'px';
el.textContent = text;
if (type === 'crit') el.classList.add('crit');
if (type === 'heal') el.classList.add('heal');
if (type === 'super-effective') el.classList.add('super-effective');
document.getElementById('battle-canvas').appendChild(el);
setTimeout(() => {
el.remove();
}, 1000);
}
// ── function: screenShake ──
// Screen shake effect
function screenShake() {
const canvas = document.getElementById('battle-canvas');
canvas.style.animation = 'shake 0.3s ease-in-out';
setTimeout(() => {
canvas.style.animation = '';
}, 300);
}
// ── function: showTransformNotification ──
// Show transformation notification
function showTransformNotification(baseId) {
const notif = document.querySelector('.transform-notification');
if (notif) {
notif.classList.add('visible');
notif.querySelector('.transform-title').textContent =
`Evolution: ${getCreatureById(baseId).name} → ${getCreatureById(baseId + '-primal').name}`;
setTimeout(() => {
notif.classList.remove('visible');
}, 4000);
}
}
// ── function: getCreatureById ──
// Get creature by ID
function getCreatureById(id) {
const base = CREATURE_DATA.find(c => c.id === id.split('-primal')[0]) || CREATURE_DATA[0];
return calcStats(base, 1); // Return base stats
}
// ── function: checkStoryNode ──
function checkStoryNode() {
const currentZoneIdx = GAME.zoneIndex;
const node = STORY_NODES.find(n => n.zone === currentZoneIdx);
if (node) {
showStoryVignette(node);
}
}
// ── function: showStoryVignette ──
function showStoryVignette(node) {
// Clear previous vignette
const oldVignette = document.querySelector('.story-vignette');
if (oldVignette) oldVignette.remove();
const vignette = document.getElementById('story-vignette');
if (!vignette || !node) return;
// Set color and show
vignette.style.background = node.color;
vignette.classList.add('active');
// Create or get dialog container
const dialog = ensureDialogContainer();
Array.from(dialog.children).forEach(c => c.remove());
// Create new title and text elements
const titleEl = document.createElement('h3');
titleEl.style.margin = '0 0 12px 0';
titleEl.style.fontSize = '18px';
titleEl.style.color = '#ffffff';
const textEl = document.createElement('p');
textEl.textContent = node.text;
textEl.style.margin = '0';
textEl.style.lineHeight = '1.6';
dialog.appendChild(titleEl);
dialog.appendChild(textEl);
// Close after reading (after 8 seconds)
setTimeout(() => {
closeStoryVignette();
}, 8000);
}
// ── function: closeStoryVignette ──
function closeStoryVignette() {
const vignette = document.querySelector('.story-vignette');
if (vignette) {
vignette.classList.remove('active');
}
}
// ── function: handleInput ──
/* Game Input Handling */
function handleInput(e) {
if (GAME.gameState !== 'overworld') return;
const keyMap = {
'w': [-1, 0], 's': [1, 0], 'a': [0, -1], 'd': [0, 1],
'ArrowUp': [-1, 0], 'ArrowDown': [1, 0], 'ArrowLeft': [0, -1], 'ArrowRight': [0, 1],
' ': () => GAME.toggleDebugMode()
};
const action = keyMap[e.key] || null;
if (action) {
e.preventDefault();
const delta = typeof action === 'function' ? action() : action;
const newX = GAME.position.x + delta[0];
const newY = GAME.position.y + delta[1];
// Boundary checks
if (newX >= 0 && newX < ZONES.length && newY >= 0 && newY < ZONES.length) {
GAME.position = { x: newX, y: newY };
GAME.render();
// Check for boss/anchor
const zone = ZONES[newY];
if (zone.bossId && !GAME.party.some(p => p.id === zone.bossId)) {
handleBossEncounter(zone);
}
}
}
}
// ── function: handleCanvasKey ──
function handleCanvasKey(e) {
if (GAME.gameState !== 'overworld') return;
const keyMap = {
'w': [-1, 0], 's': [1, 0], 'a': [0, -1], 'd': [0, 1],
'ArrowUp': [-1, 0], 'ArrowDown': [1, 0], 'ArrowLeft': [0, -1], 'ArrowRight': [0, 1],
'e': () => { if (GAME.inventory.potion_small > 0) { GAME.usePotion('potion_small', 0); return [-1,-1]; } },
'b': () => { if (GAME.party.length < 6 && GAME.party.every(p => p.hp >= p.maxHp)) { GAME.openPartyMenu(); } return [-1,-1]; }
};
const action = keyMap[e.key] || null;
if (action) {
e.preventDefault();
const delta = typeof action === 'function' ? action() : action;
if (!delta) return;
const newX = GAME.position.x + delta[0];
const newY = GAME.position.y + delta[1];
if (newX >= 0 && newX < ZONES.length && newY >= 0 && newY < ZONES.length) {
GAME.position = { x: newX, y: newY };
GAME.render();
const zone = ZONES[newY];
if (zone.bossId && !GAME.party.some(p => p.id === zone.bossId)) {
handleBossEncounter(zone);
}
}
}
}
// ── function: toggleDebugMode ──
function toggleDebugMode() {
GAME.debugMode = !GAME.debugMode || false;
return [-1,-1];
}
// ── function: ensureDialogContainer ──
/* Story Vignette System - Fixed */
// Create or get vignette dialog container
function ensureDialogContainer() {
let container = document.getElementById('dialog-container');
if (!container) {
const div = document.createElement('div');
div.id = 'dialog-container';
div.className = 'ui-layer';
const contentDiv = document.createElement('div');
contentDiv.style.padding = '20px';
contentDiv.style.background = 'rgba(10,10,18,0.95)';
contentDiv.style.borderRadius = '16px';
contentDiv.style.marginBottom = '24px';
contentDiv.style.maxWidth = '80%';
contentDiv.style.color = 'var(--text-main)';
const titleEl = document.createElement('h3');
titleEl.style.margin = '0 0 12px 0';
titleEl.style.fontSize = '18px';
const textEl = document.createElement('p');
textEl.style.margin = '0';
contentDiv.appendChild(titleEl);
contentDiv.appendChild(textEl);
div.appendChild(contentDiv);
return div;
}
return container;
}
// ── main ──
/* =====================================================
NEW BINDING - CHRONICLE OF THE RESONANT
Main Game Initialization (v8)
===================================================== */
// Initialize abilities first
initAbilities();
/* =====================================================
MAIN GAME OBJECT
===================================================== */
const GAME = {
canvas: null,
ctx: null,
width: 800,
height: 600,
tileSize: 40,
gameState: 'title',
party: [],
inventory: {},
zoneIndex: 0,
position: { x: 1, y: 1 },
battleState: null,
enemyIdx: null,
activeEnemy: null,
animations: [],
particles: [],
init() {
this.canvas = document.getElementById('world-canvas');
this.ctx = this.canvas.getContext('2d');
this.resize();
window.addEventListener('resize', () => this.resize());
const startBtn = document.getElementById('btn-start-new');
const continueBtn = document.getElementById('btn-continue');
if (startBtn) {
startBtn.onclick = () => this.startNewGame();
}
if (continueBtn) {
continueBtn.onclick = () => {
const saved = localStorage.getItem('newBindingSave');
if (saved) {
try {
const saveData = JSON.parse(saved);
this.loadGame(saveData);
} catch(e) {
console.warn('Failed to load save:', e);
}
} else {
this.startNewGame();
}
};
}
window.addEventListener('keydown', (e) => this.handleInput(e));
if (this.canvas) {
this.canvas.addEventListener('keydown', (e) => this.handleCanvasKey(e));
}
},
resize() {
const worldCanvas = document.getElementById('world-canvas');
if (!worldCanvas) return;
const dpr = window.devicePixelRatio || 1;
this.width = worldCanvas.clientWidth;
this.height = worldCanvas.clientHeight;
worldCanvas.width = this.width * dpr;
worldCanvas.height = this.height * dpr;
worldCanvas.style.width = this.width + 'px';
worldCanvas.style.height = this.height + 'px';
},
startNewGame() {
if (document.getElementById('loading-screen')) {
document.getElementById('loading-screen').style.display = 'none';
}
if (document.getElementById('title-screen')) {
document.getElementById('title-screen').style.display = 'none';
}
const starters = ['lumivolt', 'petallia', 'obsidia'];
const starterId = starters[Math.floor(Math.random() * starters.length)];
const starterData = calcStats(CREATURE_DATA.find(c => c.id === starterId), 5);
this.party = [starterData];
this.inventory = {
'potion_small': 5,
'capture_basic': 2,
'catalyst_basic': 0
};
this.zoneIndex = 0;
this.position = { x: 1, y: 1 };
showStoryVignette(STORY_NODES[0]);
this.gameState = 'overworld';
this.render();
},
saveGame() {
const data = {
party: this.party,
inventory: this.inventory,
zoneIndex: this.zoneIndex,
position: this.position,
battleState: null
};
try {
localStorage.setItem('newBindingSave', JSON.stringify(data));
showNotification('Game saved!');
} catch(e) {
console.warn('Failed to save game:', e);
}
},
loadGame(savedData) {
this.party = savedData.party;
this.inventory = savedData.inventory;
this.zoneIndex = savedData.zoneIndex;
this.position = savedData.position;
this.render();
const zoneData = ZONES[this.zoneIndex];
if (zoneData && zoneData.shrineId) {
this.party.forEach(p => {
p.hp = p.maxHp;
delete p.effects;
});
showStoryVignette(STORY_NODES.find(n => n.zone === this.zoneIndex));
} else if (STORY_NODES.find(n => n.zone === this.zoneIndex)) {
showStoryVignette(STORY_NODES.find(n => n.zone === this.zoneIndex));
}
return this;
},
handleInput(e) {
if (this.gameState !== 'overworld') return;
const keyMap = {
'w': [-1, 0], 's': [1, 0], 'a': [0, -1], 'd': [0, 1],
'ArrowUp': [-1, 0], 'ArrowDown': [1, 0],
'ArrowLeft': [0, -1], 'ArrowRight': [0, 1]
};
const action = keyMap[e.key] || null;
if (action) {
e.preventDefault();
const newX = this.position.x + action[0];
const newY = this.position.y + action[1];
const maxZonesX = ZONES.length;
const maxZonesY = Math.ceil(240 / this.tileSize);
if (newX >= 0 && newX < maxZonesX && newY >= 0 && newY < maxZonesY) {
this.position = { x: newX, y: newY };
this.render();
const zone = ZONES[newY];
if (zone && zone.bossId) {
handleBossEncounter(zone);
}
}
}
},
handleCanvasKey(e) {
if (this.gameState !== 'overworld') return;
const keyMap = {
'w': [-1, 0], 's': [1, 0], 'a': [0, -1], 'd': [0, 1],
'ArrowUp': [-1, 0], 'ArrowDown': [1, 0],
'ArrowLeft': [0, -1], 'ArrowRight': [0, 1]
};
const action = keyMap[e.key] || null;
if (action) {
e.preventDefault();
const newX = this.position.x + action[0];
const newY = this.position.y + action[1];
const maxZonesX = ZONES.length;
const maxZonesY = Math.ceil(240 / this.tileSize);
if (newX >= 0 && newX < maxZonesX && newY >= 0 && newY < maxZonesY) {
this.position = { x: newX, y: newY };
this.render();
const zone = ZONES[newY];
if (zone && zone.bossId) {
handleBossEncounter(zone);
}
}
}
},
render() {
if (!this.canvas || !this.ctx) return;
const zoneData = ZONES[this.zoneIndex];
if (zoneData) {
this.ctx.fillStyle = zoneData.background;
this.ctx.fillRect(0, 0, this.width, this.height);
}
this.ctx.strokeStyle = 'rgba(255,255,255,0.1)';
this.ctx.lineWidth = 1;
const tileSize = 40;
for (let i = 0; i <= this.width / tileSize; i++) {
this.ctx.beginPath();
this.ctx.moveTo(i * tileSize, 0);
this.ctx.lineTo(i * tileSize, this.height);
this.ctx.stroke();
}
for (let i = 0; i <= this.height / tileSize; i++) {
this.ctx.beginPath();
this.ctx.moveTo(0, i * tileSize);
this.ctx.lineTo(this.width, i * tileSize);
this.ctx.stroke();
}
this.drawParticles();
if (this.party.length > 0) {
const player = this.party[0];
const px = Math.floor(player.x * this.tileSize + this.tileSize / 2);
const py = Math.floor(player.y * this.tileSize + this.tileSize / 2);
this.drawEntityAt(px, py, player);
}
this.drawHUD();
},
drawParticles() {
const zoneData = ZONES[this.zoneIndex];
if (!zoneData || !zoneData.particleType) return;
let particles = GAME.particles || [];
if (Math.random() < 0.02 && particles.length < 30) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 1 + 0.5;
particles.push({
x: GAME.width / 2,
y: GAME.height / 2,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 80 + Math.random() * 100,
color: zoneData.color
});
}
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);
} else {
this.ctx.globalAlpha = p.life / 200;
this.ctx.fillStyle = p.color;
this.ctx.fillRect(p.x, p.y, 3, 3);
this.ctx.globalAlpha = 1;
}
}
GAME.particles = particles;
},
drawEntityAt(x, y, creature) {
const ctx = this.ctx;
ctx.strokeStyle = 'rgba(255,255,255,0.4)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, 30, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = TYPE_COLORS[creature.affinity] || '#818cf8';
ctx.globalAlpha = 0.9;
ctx.beginPath();
ctx.arc(x, y, 26, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = creature.affinity;
ctx.lineWidth = 2;
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.arc(x, y, 24, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = 'white';
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(creature.affinity.charAt(0).toUpperCase(), x, y);
if (creature.formPrefix) {
ctx.font = 'italic 12px Arial';
ctx.fillStyle = '#fbbf24';
ctx.fillText(creature.formPrefix, x - 20, y + 50);
}
},
drawHUD() {
const zoneData = ZONES[this.zoneIndex];
if (!zoneData) return;
this.ctx.font = 'bold 18px Arial';
this.ctx.fillStyle = 'rgba(255,255,255,0.9)';
this.ctx.textAlign = 'left';
this.ctx.fillText(zoneData.name, 16, 30);
if (zoneData.bossId && !zoneData.bossDefeated) {
const bossName = CREATURE_DATA.find(c => c.id === zoneData.bossId)?.name || 'Guardian';
this.ctx.font = 'bold 24px Arial';
this.ctx.fillStyle = 'rgba(255,100,100,0.9)';
this.ctx.textAlign = 'center';
this.ctx.fillText('⚠️ ' + bossName.toUpperCase(), GAME.width / 2, GAME.height / 2 + 30);
}
if (zoneData.shrineId) {
const shrineX = GAME.width - 60;
const shrineY = GAME.height - 50;
this.ctx.font = 'bold 28px Arial';
this.ctx.fillStyle = '#6ee7b7';
this.ctx.textAlign = 'center';
this.ctx.fillText('🛕', shrineX, shrineY);
}
if (this.party.length === 1 && this.party[0].level < 5) {
this.ctx.font = 'italic 12px Arial';
this.ctx.fillStyle = 'rgba(255,255,255,0.7)';
this.ctx.fillText('Training...', GAME.width - 90, 30);
}
},
// Battle methods (same as before for brevity)
startBattle: function(enemyIdx, enemyData) {
if (!this.battleState) {
this.battleState = {
playerTurn: true,
phase: 'intro',
messages: [],
turnsRemaining: 20,
defeated: false,
animations: []
};
}
this.gameState = 'battle';
if (!enemyData) {
const zoneData = ZONES[this.zoneIndex];
enemyData = spawnWildCreature(this.zoneIndex);
}
const player = this.party[0] || this.party[this.battleState.playerPartyIdx || 0];
if (player) {
enemyData.level = Math.floor(player.level * (0.8 + Math.random() * 0.4));
} else {
enemyData.level = 15;
}
const zoneData = ZONES[this.zoneIndex];
const isBoss = !!zoneData.bossId;
this.drawBattleIntro(isBoss);
setTimeout(() => {
if (!isBoss) {
this.offerSave();
} else {
this.startBattleTurns();
}
}, 1500);
},
drawBattleIntro: function(isBoss) {
const canvas = document.getElementById('battle-canvas');
if (!canvas) return;
canvas.style.display = 'block';
const title = isBoss ? '⚠️ CHALLENGE ⚠️' : 'WILD ENCOUNTER!';
this.ctx.fillStyle = isBoss ? '#f87171' : '#fbbf24';
this.ctx.font = 'bold 36px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(title, canvas.width / 2, canvas.height / 2);
const enemyName = isBoss ? CREATURE_DATA.find(c => c.id === ZONES[this.zoneIndex].bossId)?.name || 'Guardian' :
(CREATURE_DATA[this.enemyIdx]?.name || 'Wild Creature');
this.ctx.font = 'bold 24px Arial';
this.ctx.fillStyle = 'rgba(255,255,255,0.9)';
this.ctx.fillText(enemyName, canvas.width / 2, canvas.height / 2 + 45);
},
offerSave: function() {
const msg = document.getElementById('dialog-container');
if (!msg) return;
const container = document.createElement('div');
container.style.padding = '16px';
container.style.background = 'rgba(255,255,255,0.1)';
container.style.borderRadius = '8px';
container.style.marginTop = '12px';
const p = document.createElement('p');
p.textContent = 'A shrine appears! Would you like to save?';
const btnContainer = document.createElement('div');
btnContainer.style.display = 'flex';
btnContainer.style.gap = '8px';
btnContainer.style.justifyContent = 'flex-end';
const saveBtn = document.createElement('button');
saveBtn.className = 'btn btn-primary';
saveBtn.textContent = 'Save';
saveBtn.onclick = () => { this.saveGame(); msg.remove(); };
const noBtn = document.createElement('button');
noBtn.className = 'btn btn-secondary';
noBtn.textContent = 'No Thanks';
noBtn.onclick = () => { msg.remove(); };
btnContainer.appendChild(saveBtn);
btnContainer.appendChild(noBtn);
container.appendChild(p);
container.appendChild(btnContainer);
msg.appendChild(container);
},
startBattleTurns: function() {
this.battleState.phase = 'battle';
this.renderBattle();
},
renderBattle: function() {
const canvas = document.getElementById('battle-canvas');
if (!canvas || this.battleState?.defeated) return;
canvas.fillStyle = 'rgba(10,10,18,0.95)';
canvas.fillRect(0, 0, this.width, this.height);
const bs = this.battleState;
const player = this.party[bs.playerPartyIdx || 0];
const enemy = bs.activeEnemy;
this.drawBattleUI(player, enemy);
this.drawBattleEntities();
this.drawMoveButtons(player);
this.drawBattleMessages();
},
drawBattleUI: function(player, enemy) {
const canvas = document.getElementById('battle-canvas');
if (!canvas) return;
const playerHpPct = player.hp / player.maxHp;
const enemyHpPct = enemy.hp / enemy.maxHp;
this.ctx.font = 'bold 20px Arial';
this.ctx.fillStyle = '#e8e8ff';
this.ctx.textAlign = 'left';
this.ctx.fillText(player.name, 16, 35);
const playerMaxHp = 40;
this.drawHealthBar(16, 50, playerHpPct * playerMaxHp, playerMaxHp, '#e8e8ff');
const enemyX = canvas.width - 220;
this.ctx.fillText(enemy.name, enemyX + 16, 35);
this.drawHealthBar(enemyX + 16, 50, enemyHpPct * playerMaxHp, playerMaxHp, '#f87171');
},
drawHealthBar: function(x, y, current, max, color) {
const barWidth = 140;
const barHeight = 20;
this.ctx.fillStyle = 'rgba(0,0,0,0.5)';
this.ctx.fillRect(x, y, barWidth, barHeight);
const fillWidth = (current / max) * barWidth;
this.ctx.fillStyle = color;
this.ctx.fillRect(x, y, fillWidth, barHeight);
this.ctx.strokeStyle = 'rgba(255,255,255,0.3)';
this.ctx.lineWidth = 2;
this.ctx.strokeRect(x, y, barWidth, barHeight);
},
drawBattleEntities: function() {
const canvas = document.getElementById('battle-canvas');
if (!canvas) return;
const bs = this.battleState;
const player = this.party[bs.playerPartyIdx || 0];
const enemy = bs.activeEnemy;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
this.drawEntity(enemy, centerX + 150, centerY - 20);
this.drawEntity(player, centerX - 80, centerY - 20);
},
drawEntity: function(creature, x, y) {
const canvas = document.getElementById('battle-canvas');
if (!canvas) return;
if (creature.formPrefix) {
this.ctx.save();
this.ctx.shadowColor = creature.affinity;
this.ctx.shadowBlur = 20;
}
this.ctx.strokeStyle = 'rgba(255,255,255,0.3)';
this.ctx.lineWidth = 3;
this.ctx.beginPath();
this.ctx.arc(x, y, 40, 0, Math.PI * 2);
this.ctx.stroke();
this.ctx.fillStyle = TYPE_COLORS[creature.affinity] || '#818cf8';
this.ctx.globalAlpha = 0.9;
this.ctx.beginPath();
this.ctx.arc(x, y, 36, 0, Math.PI * 2);
this.ctx.fill();
this.ctx.strokeStyle = creature.affinity;
this.ctx.lineWidth = 2;
this.ctx.globalAlpha = 1;
this.ctx.beginPath();
this.ctx.arc(x, y, 35, 0, Math.PI * 2);
this.ctx.stroke();
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 24px Arial';
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText(creature.affinity.charAt(0).toUpperCase(), x, y);
if (creature.formPrefix) {
this.ctx.font = 'italic 12px Arial';
this.ctx.fillStyle = '#fbbf24';
this.ctx.fillText(creature.formPrefix, x - 20, y + 55);
}
if (creature.formPrefix) {
this.ctx.restore();
}
},
drawMoveButtons: function(player) {
const canvas = document.getElementById('battle-canvas');
if (!canvas) return;
const bottomY = canvas.height - 140;
player.abilities.forEach((ability, idx) => {
if (ability.currentPP <= 0) return;
const btnX = 20 + idx * 165;
const btnY = bottomY + 40;
const btnWidth = 150;
const btnHeight = 64;
this.ctx.fillStyle = 'rgba(255,255,255,0.08)';
this.ctx.beginPath();
this.ctx.roundRect(btnX, btnY, btnWidth, btnHeight, 10);
this.ctx.fill();
const moveName = ability.name.substring(0, 14);
this.ctx.fillStyle = '#e8e8ff';
this.ctx.font = 'bold 14px Arial';
this.ctx.fillText(moveName, btnX + 20, btnY + 35);
const typeColor = TYPE_COLORS[ability.type] || '#818cf8';
const typeText = ability.name.split(' ').pop().toUpperCase();
this.ctx.font = 'italic 10px Arial';
this.ctx.fillStyle = typeColor;
this.ctx.fillText(typeText, btnX + 20, btnY + 50);
});
},
executeMove: function(moveIdx) {
const player = this.party[this.battleState.playerPartyIdx || 0];
const ability = player.abilities[moveIdx];
if (ability.currentPP <= 0) return;
this.executeMoveEffect(ability, true);
},
executeMoveEffect: function(ability, isAttack) {
const canvas = document.getElementById('battle-canvas');
if (!canvas) return;
const player = this.party[this.battleState.playerPartyIdx || 0];
const enemy = this.battleState.activeEnemy;
ability.currentPP--;
this.battleState.messages.push('You used ' + ability.name + '!');
if (isAttack) {
const damageData = calculateDamage(player, enemy, ability);
canvas.style.animation = 'shake 0.3s ease-in-out';
setTimeout(() => canvas.style.animation = '', 300);
showFloatingText(canvas.width / 2 + 250, canvas.height / 2 - 60, '-' + damageData.damage, damageData.isCrit ? 'crit' : '');
if (damageData.isSuperEffective) {
const critText = damageData.isCrit ? '(CRITICAL!)' : '';
showFloatingText(canvas.width / 2 + 250, canvas.height / 2 - 40, 'SUPER EFFECTIVE' + critText, 'super-effective');
}
enemy.hp -= damageData.damage;
if (enemy.hp < 0) enemy.hp = 0;
const typeEffects = {
flux: { burn: false, frost: false, shock: false },
crysta: { burn: false, frost: true, shock: false },
void: { burn: false, frost: false, shock: true },
ember: { burn: true, frost: false, shock: false },
storm: { burn: false, frost: false, shock: false },
iron: { burn: false, frost: false, shock: false },
spirit: { burn: false, frost: false, shock: false }
};
if (typeEffects[ability.type] && typeEffects[ability.type][ability.effect]) {
applyStatusEffect(enemy, ability.effect);
}
if (enemy.hp <= 0) {
this.battleState.phase = 'victory';
showFloatingText(canvas.width / 2, canvas.height / 2 - 50, 'VICTORY!', 'super-effective');
const expGain = Math.floor(enemy.level * 1.3) + 50;
player.xp += expGain;
if (player.xp >= player.xpToNextLevel) {
player.level++;
player.xp -= player.xpToNextLevel;
showFloatingText(canvas.width / 2, canvas.height / 2, 'LEVEL UP!', 'super-effective');
const newStats = calcStats(CREATURE_DATA.find(c => c.id === player.id.split('-primal')[0]), player.level);
player.maxHp = newStats.maxHp;
player.atk = newStats.atk;
player.def = newStats.def;
player.spd = newStats.spd;
}
setTimeout(() => this.offerCapture(player, enemy, ability.type), 2000);
} else {
this.battleState.phase = 'enemy-turn';
setTimeout(() => this.renderBattle(), 1000);
}
}
this.renderBattleMessages();
},
offerCapture: function(player, enemy) {
if (this.battleState.defeated || !this.inventory['capture_basic']) return;
const zoneData = ZONES[this.zoneIndex];
const isBoss = !!zoneData.bossId;
if (isBoss) {
setTimeout(() => this.renderBattleMessages(), 1500);
return;
}
const msgContainer = document.getElementById('dialog-container');
if (!msgContainer) return;
const container = document.createElement('div');
container.style.padding = '16px';
container.style.background = 'rgba(255,255,255,0.08)';
container.style.borderRadius = '8px';
container.style.marginTop = '12px';
const p = document.createElement('p');
p.textContent = 'A wild ' + enemy.name + ' wants to befriend you! Press E to catch it with a Resonance Orb!';
const btnContainer = document.createElement('div');
btnContainer.style.display = 'flex';
btnContainer.style.gap = '8px';
btnContainer.style.justifyContent = 'flex-end';
const captureBtn = document.createElement('button');
captureBtn.className = 'btn btn-primary';
captureBtn.textContent = 'Catch!';
captureBtn.style.fontSize = '12px';
captureBtn.onclick = () => this.attemptCapture(player, enemy);
const cancelBtn = document.createElement('button');
cancelBtn.className = 'btn btn-secondary';
cancelBtn.textContent = 'Cancel';
cancelBtn.onclick = () => { container.remove(); };
btnContainer.appendChild(captureBtn);
btnContainer.appendChild(cancelBtn);
container.appendChild(p);
container.appendChild(btnContainer);
msgContainer.appendChild(container);
},
attemptCapture: function(player, enemy) {
const captureRate = calculateCaptureRate(enemy, 1);
if (Math.random() < captureRate) {
showFloatingText(GAME.width / 2, GAME.height / 2, 'Captured ' + enemy.name + '!', 'super-effective');
const capturedCreature = calcStats(CREATURE_DATA.find(c => c.id === enemy.id), Math.floor(enemy.level * 0.8));
this.party.push(capturedCreature);
if (this.battleState.phase === 'victory') {
this.gameState = 'overworld';
this.render();
showStoryVignette(STORY_NODES[this.zoneIndex]);
} else {
setTimeout(() => this.renderBattle(), 2000);
}
} else {
showFloatingText(GAME.width / 2, GAME.height / 2 + 40, 'Capture Failed!', '');
}
setTimeout(() => this.renderBattleMessages(), 1500);
},
renderBattleMessages: function() {
const msgContainer = document.getElementById('dialog-container');
if (!msgContainer) return;
Array.from(msgContainer.children).forEach(c => c.remove());
const bs = this.battleState || {};
if (!bs.messages || bs.messages.length === 0) return;
const msgBox = document.createElement('div');
msgBox.style.padding = '16px';
msgBox.style.background = 'rgba(255,255,255,0.05)';
msgBox.style.borderRadius = '8px';
msgBox.style.marginBottom = '12px';
const text = document.createElement('p');
text.textContent = bs.messages.join('\n');
text.style.whiteSpace = 'pre-wrap';
msgBox.appendChild(text);
msgContainer.appendChild(msgBox);
},
drawBattleMessages: function() {
const canvas = document.getElementById('battle-canvas');
if (!canvas) return;
const bs = this.battleState || {};
const msgY = canvas.height - 140 + 80;
this.ctx.font = 'bold 14px Arial';
this.ctx.fillStyle = '#e8e8ff';
this.ctx.textAlign = 'center';
if (bs.phase === 'intro') {
const enemyName = bs.activeEnemy?.name || 'Wild Creature';
this.ctx.fillText('A wild ' + enemyName + ' appeared!', canvas.width / 2, msgY);
} else if (bs.phase === 'victory' && bs.messages.length > 0) {
this.ctx.fillText(bs.messages[bs.messages.length - 1] || 'VICTORY!', canvas.width / 2, msgY);
} else if (bs.phase === 'enemy-turn') {
this.ctx.fillText('Enemy is taking a turn...', canvas.width / 2, msgY);
} else if (bs.messages && bs.messages.length > 0) {
const lastMsg = bs.messages[bs.messages.length - 1];
this.ctx.fillText(lastMsg || '', canvas.width / 2, msgY);
}
},
// Inventory/Team screens
openInventory: function() {
this.showScreen('inventory');
},
showScreen: function(screenType) {
const battleCanvas = document.getElementById('battle-canvas');
if (battleCanvas) {
battleCanvas.style.display = 'none';
}
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'screen-modal';
modal.onclick = () => this.closeScreen();
const content = document.createElement('div');
content.className = 'modal';
if (screenType === 'inventory') {
this.createInventoryContent(content);
} else if (screenType === 'team') {
this.createTeamContent(content);
} else if (screenType === 'encyclopedia') {
this.createEncyclopediaContent(content);
} else if (screenType === 'shop') {
this.createShopContent(content);
}
modal.appendChild(content);
document.body.appendChild(modal);
},
createInventoryContent: function(container) {
const title = document.createElement('h2');
title.textContent = 'Bag';
const backBtn = document.createElement('button');
backBtn.className = 'back-btn';
backBtn.textContent = 'Back';
backBtn.onclick = () => this.closeScreen();
const grid = document.createElement('div');
grid.className = 'inventory-grid';
const itemTypes = [
{ id: 'potion_small', name: 'Essence Vial', icon: '🧪' },
{ id: 'capture_basic', name: 'Resonance Orb', icon: '🔮' }
];
itemTypes.forEach(item => {
const count = this.inventory[item.id] || 0;
for (let i = 0; i < Math.min(count, 5); i++) {
const slot = document.createElement('div');
slot.className = 'item-slot';
slot.textContent = item.icon;
slot.title = item.name + ' (' + count + ')';
slot.onclick = () => this.useItem(item.id);
grid.appendChild(slot);
}
});
container.appendChild(title);
container.appendChild(backBtn);
container.appendChild(grid);
},
createTeamContent: function(container) {
const title = document.createElement('h2');
title.textContent = 'Team';
const backBtn = document.createElement('button');
backBtn.className = 'back-btn';
backBtn.textContent = 'Back';
backBtn.onclick = () => this.closeScreen();
const grid = document.createElement('div');
grid.style.display = 'grid';
grid.style.gap = '8px';
grid.style.padding = '16px';
this.party.forEach((creature, idx) => {
const member = document.createElement('div');
member.className = 'team-member';
if (idx === 0) member.classList.add('selected');
const avatar = document.createElement('div');
avatar.className = 'team-avatar';
avatar.textContent = creature.affinity.charAt(0).toUpperCase();
const info = document.createElement('div');
info.className = 'team-info';
const levelEl = document.createElement('span');
levelEl.className = 'team-level';
levelEl.textContent = 'Lv.' + creature.level;
info.appendChild(levelEl);
const nameEl = document.createElement('strong');
nameEl.textContent = creature.name;
nameEl.style.display = 'block';
const affinityEl = document.createElement('span');
affinityEl.className = 'team-affinity ' + creature.affinity;
affinityEl.textContent = creature.affinity.toUpperCase();
info.appendChild(nameEl);
info.appendChild(affinityEl);
member.appendChild(avatar);
member.appendChild(info);
if (idx === 0) {
member.onclick = () => this.selectPartyMember(0);
}
grid.appendChild(member);
});
container.appendChild(title);
container.appendChild(backBtn);
container.appendChild(grid);
},
createEncyclopediaContent: function(container) {
const title = document.createElement('h2');
title.textContent = 'Creature Codex';
const backBtn = document.createElement('button');
backBtn.className = 'back-btn';
backBtn.textContent = 'Back';
backBtn.onclick = () => this.closeScreen();
const grid = document.createElement('div');
grid.style.display = 'grid';
grid.style.gap = '8px';
grid.style.padding = '16px';
// Group creatures by zone
for (let i = 0; i < CREATURE_DATA.length; i++) {
const creature = CREATURE_DATA[i];
if (i > 0 && !creature.isBoss) {
grid.appendChild(backBtn);
backBtn.style.marginTop = '16px';
break;
}
const entry = document.createElement('div');
entry.className = 'enc-entry';
const header = document.createElement('div');
header.className = 'enc-header';
const avatar = document.createElement('div');
avatar.style.width = '60px';
avatar.style.height = '60px';
avatar.style.borderRadius = '12px';
avatar.style.background = TYPE_COLORS[creature.affinity] || '#818cf8';
avatar.style.display = 'flex';
avatar.style.alignItems = 'center';
avatar.style.justifyContent = 'center';
avatar.style.fontSize = '30px';
avatar.textContent = creature.affinity.charAt(0).toUpperCase();
const content = document.createElement('div');
content.style.flex = '1';
const nameEl = document.createElement('strong');
nameEl.textContent = creature.name;
nameEl.style.color = '#e8e8ff';
nameEl.style.marginBottom = '4px';
const affinityBadge = document.createElement('span');
affinityBadge.style.display = 'inline-block';
affinityBadge.style.background = TYPE_COLORS[creature.affinity];
affinityBadge.style.color = '#1a1a2e';
affinityBadge.style.padding = '2px 8px';
affinityBadge.style.borderRadius = '4px';
affinityBadge.style.fontSize = '11px';
affinityBadge.style.textTransform = 'uppercase';
affinityBadge.textContent = creature.affinity;
const loreEl = document.createElement('p');
loreEl.style.color = '#9ca3af';
loreEl.style.marginTop = '8px';
loreEl.textContent = creature.lore;
content.appendChild(nameEl);
content.appendChild(affinityBadge);
content.appendChild(loreEl);
header.appendChild(avatar);
header.appendChild(content);
entry.appendChild(header);
// Add abilities
const abilitiesDiv = document.createElement('div');
abilitiesDiv.style.marginTop = '8px';
abilitiesDiv.style.color = '#6ee7b7';
abilitiesDiv.style.fontSize = '12px';
creature.abilities.slice(0, 3).forEach((abi, idx) => {
const abiv = document.createElement('div');
abiv.textContent = abi.name;
if (idx < 2) abiv.style.marginRight = '16px';
});
abilitiesDiv.appendChild(abiv);
entry.appendChild(abilitiesDiv);
grid.appendChild(entry);
}
container.appendChild(title);
container.appendChild(backBtn);
container.appendChild(grid);
},
createShopContent: function(container) {
const title = document.createElement('h2');
title.textContent = 'Trade Post';
const backBtn = document.createElement('button');
backBtn.className = 'back-btn';
backBtn.textContent = 'Back';
backBtn.onclick = () => this.closeScreen();
const grid = document.createElement('div');
grid.className = 'shop-grid';
const shopItems = [
{ id: 'potion_large', name: 'Primal Elixir', icon: '🧪', price: 0, owned: true },
{ id: 'bandage', name: 'Field Bandage', icon: '💚', price: 100, owned: false },
{ id: 'capture_adv', name: 'Prism Orb', icon: '🔮', price: 250, owned: false }
];
shopItems.forEach(item => {
const slot = document.createElement('div');
slot.className = 'shop-item';
if (item.owned) {
slot.classList.add('bought');
}
const icon = document.createElement('span');
icon.style.fontSize = '32px';
icon.textContent = item.icon;
const nameEl = document.createElement('div');
nameEl.style.marginTop = '8px';
nameEl.textContent = item.name;
if (item.owned) {
nameEl.style.color = '#6ee7b7';
} else {
nameEl.style.color = '#e8e8ff';
}
slot.appendChild(icon);
slot.appendChild(nameEl);
grid.appendChild(slot);
});
container.appendChild(title);
container.appendChild(backBtn);
container.appendChild(grid);
},
useItem: function(itemId) {
if (!this.inventory[itemId]) return;
const item = ITEMS[itemId];
if (item.effect === 'heal_25') {
this.party[0].hp = Math.min(this.party[0].maxHp, this.party[0].hp + 25);
showNotification('Healed 25 HP!');
} else if (item.effect === 'heal_full') {
this.party[0].hp = this.party[0].maxHp;
showNotification('Fully healed!');
}
this.inventory[itemId]--;
if (this.inventory[itemId] <= 0) {
delete this.inventory[itemId];
}
},
selectPartyMember: function(idx) {
// Simplified - only one party member for now
},
closeScreen: function() {
const modal = document.getElementById('screen-modal');
if (modal) {
modal.remove();
}
this.gameState = 'overworld';
this.render();
}
};
/* =====================================================
HELPER FUNCTIONS FOR BOSS ENCOUNTER
===================================================== */
function handleBossEncounter(zoneData) {
if (GAME.party.some(p => p.id === zoneData.bossId)) {
showNotification('Boss already defeated! ' + zoneData.name);
return;
}
const bossData = calcStats(CREATURE_DATA.find(c => c.id === zoneData.bossId), 20);
const enemy = { ...bossData, hp: bossData.maxHp };
GAME.startBattle(0, enemy);
}
/* =====================================================
INITIALIZATION
===================================================== */
GAME.particles = [];
GAME.init();
function renderInitial() {
const worldCanvas = document.getElementById('world-canvas');
if (worldCanvas) {
worldCanvas.style.display = 'block';
const ctx = worldCanvas.getContext('2d');
const zoneData = ZONES[GAME.zoneIndex];
if (zoneData) {
ctx.fillStyle = zoneData.background;
ctx.fillRect(0, 0, GAME.width, GAME.height);
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
ctx.lineWidth = 1;
const tileSize = 40;
for (let i = 0; i <= GAME.width / tileSize; i++) {
ctx.beginPath();
ctx.moveTo(i * tileSize, 0);
ctx.lineTo(i * tileSize, GAME.height);
ctx.stroke();
}
for (let i = 0; i <= GAME.height / tileSize; i++) {
ctx.beginPath();
ctx.moveTo(0, i * tileSize);
ctx.lineTo(GAME.width, i * tileSize);
ctx.stroke();
}
}
if (GAME.party.length > 0) {
const p = GAME.party[0];
const centerX = GAME.width / 2;
const centerY = GAME.height / 2;
GAME.drawEntityAt(centerX, centerY, p);
}
GAME.drawHUD();
}
}
function showTitleScreen() {
const saved = localStorage.getItem('newBindingSave');
if (document.getElementById('loading-screen')) {
document.getElementById('loading-screen').style.display = 'none';
}
if (saved) {
try {
const saveData = JSON.parse(saved);
GAME.loadGame(saveData);
} catch(e) {
console.warn('Failed to load save:', e);
}
} else if (GAME.party.length > 0) {
GAME.loadGame(GAME);
} else {
GAME.startNewGame();
}
}
renderInitial();
showTitleScreen();
/* =====================================================
UI FUNCTIONS
===================================================== */
function showNotification(text) {
const container = document.getElementById('notifications');
if (!container) return;
const notif = document.createElement('div');
notif.className = 'notification';
notif.textContent = text;
container.appendChild(notif);
setTimeout(() => {
notif.style.animation = 'slideIn 0.3s ease-out reverse';
setTimeout(() => notif.remove(), 300);
}, 2500);
}
/* =====================================================
MAIN GAME LOOP
===================================================== */
function gameLoop() {
if (GAME.animations && GAME.animations.length > 0) {
GAME.animations = GAME.animations.filter(a => a.life > 0);
}
requestAnimationFrame(gameLoop);
}
gameLoop();
/* =====================================================
END OF INITIALIZATION
===================================================== */
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.5-9b","provider":"lmstudio","persona":"composer","sessionId":"517b8e10-08d2-4882-bf78-3fbab74c5cd5","tokensIn":2338359,"tokensOut":96697,"tokensTotal":2435056,"turns":42,"toolCalls":41,"failedToolCalls":7,"timestamp":"2026-07-14T03:18:17.397Z"} -->