Creature Collection
Gemma 4 31b · full

Reviews & generation details
Reviews
Human review · Jul 17, 2026 · rubric 1
Historical path-based review. Association with these exact bytes is unverified.
- Visual design
- 2 / 5
- UX / feel
- 2 / 5
- Works
- 1 / 5
- Prompt spirit
- 1 / 5
- Delight
- 1 / 5
Basically broken, can't do anything past the initial menu
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 40,364 bytes · SHA-256 c6e83b42b949
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>AETHERIA: SOULBIND</title>
<style>
:root {
--bg-dark: #0a0a12;
--accent-primary: #7c4dff;
--accent-secondary: #00e5ff;
--text-main: #e0e0ff;
--glass: rgba(20, 20, 40, 0.8);
--glass-border: rgba(255, 255, 255, 0.1);
--panel-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
/* Type Colors */
--type-A: #ff5252; /* Pyro-equivalent */
--type-B: #69f0ae; /* Flora-equivalent */
--type-C: #448aff; /* Aqua-equivalent */
--type-D: #ffd740; /* Volt-equivalent */
--type-E: #e040fb; /* Psy-equivalent */
--type-F: #b0bec5; /* Steel-equivalent */
--type-G: #ff8a65; /* Mythic-equivalent */
}
* { box-sizing: border-box; margin: 0; padding: 0; user-select: none; -webkit-tap-highlight-color: transparent; }
body {
background: var(--bg-dark);
color: var(--text-main);
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: radial-gradient(circle at center, #1a1a2e 0%, #0a0a12 100%);
}
canvas {
image-rendering: pixelated;
display: block;
max-width: 100%;
max-height: 100%;
box-shadow: 0 0 50px rgba(0,0,0,0.8);
}
/* UI Layers */
.ui-layer {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
display: none;
justify-content: center;
align-items: center;
z-index: 10;
}
.ui-layer.active { display: flex; pointer-events: auto; }
/* Glassmorphism Components */
.glass-panel {
background: var(--glass);
backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 16px;
padding: 24px;
box-shadow: var(--panel-shadow);
color: var(--text-main);
}
/* Title Screen */
#title-screen {
background: radial-gradient(circle at center, #1a1a40 0%, #050510 100%);
flex-direction: column;
gap: 30px;
text-align: center;
}
.game-title {
font-size: 4rem;
font-weight: 900;
letter-spacing: 8px;
text-transform: uppercase;
background: linear-gradient(to bottom, #fff, var(--accent-primary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
filter: drop-shadow(0 0 15px var(--accent-primary));
animation: title-glow 3s infinite alternate;
}
@keyframes title-glow {
from { filter: drop-shadow(0 0 10px var(--accent-primary)); }
to { filter: drop-shadow(0 0 25px var(--accent-secondary)); }
}
.menu-btns { display: flex; flex-direction: column; gap: 15px; width: 280px; }
.btn {
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--glass-border);
color: white;
padding: 12px 24px;
font-size: 1.1rem;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 2px;
}
.btn:hover { background: var(--accent-primary); transform: scale(1.05); }
/* Battle UI */
#battle-ui {
justify-content: space-between;
align-items: stretch;
padding: 40px;
pointer-events: none;
}
#battle-ui .active { pointer-events: auto; }
.battle-hud {
display: flex;
justify-content: space-between;
width: 100%;
pointer-events: none;
}
.stat-card {
width: 250px;
transition: transform 0.3s ease;
}
.hp-bar-container {
width: 100%;
height: 12px;
background: #333;
border-radius: 6px;
overflow: hidden;
margin: 8px 0;
border: 1px solid rgba(255,255,255,0.1);
}
.hp-bar-fill {
height: 100%;
width: 100%;
background: linear-gradient(to right, #4caf50, #8bc34a);
transition: width 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.battle-menu {
position: absolute;
bottom: 40px;
right: 40px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
width: 400px;
pointer-events: auto;
display: none;
}
.battle-menu.visible { display: grid; }
.action-btn {
height: 60px;
background: var(--glass);
border: 1px solid var(--glass-border);
color: white;
font-size: 1.2rem;
cursor: pointer;
border-radius: 8px;
text-align: left;
padding-left: 20px;
transition: background 0.2s;
}
.action-btn:hover { background: rgba(255,255,255,0.2); }
/* Dialogue / Narrator */
#dialogue-box {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 80%;
max-width: 800px;
pointer-events: auto;
min-height: 120px;
}
.dialogue-text {
font-size: 1.2rem;
line-height: 1.6;
margin-bottom: 10px;
}
/* Type Badges */
.type-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: bold;
text-transform: uppercase;
margin-left: 8px;
color: #000;
}
/* Inventory/Team Overlays */
.overlay-content {
width: 80vw;
height: 80vh;
display: grid;
grid-template-columns: 300px 1fr;
gap: 20px;
}
.list-container {
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.list-item {
padding: 15px;
background: rgba(255,255,255,0.05);
border: 1px solid var(--glass-border);
border-radius: 8px;
cursor: pointer;
}
.list-item:hover { background: rgba(255,255,255,0.1); }
/* Controls for Mobile */
#mobile-controls {
position: absolute;
bottom: 40px;
left: 40px;
display: none;
grid-template-columns: repeat(3, 60px);
gap: 10px;
pointer-events: auto;
}
.ctrl-btn {
width: 60px;
height: 60px;
background: var(--glass);
border: 1px solid var(--glass-border);
border-radius: 50%;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
/* Screen Effects */
.vignette {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
box-shadow: inset 0 0 150px rgba(0,0,0,0.8);
z-index: 5;
}
.screen-flash {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: white;
opacity: 0;
pointer-events: none;
z-index: 100;
}
.shake { animation: shake 0.2s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translate(0,0); }
25% { transform: translate(-5px, 5px); }
50% { transform: translate(5px, -5px); }
75% { transform: translate(-5px, -5px); }
}
</style>
</head>
<body>
<div id="game-container">
<div class="vignette"></div>
<div id="screen-flash" class="screen-flash"></div>
<canvas id="gameCanvas"></canvas>
<!-- Title Screen -->
<div id="title-screen" class="ui-layer active">
<h1 class="game-title">AETHERIA<br>SOULBIND</h1>
<div class="menu-btns">
<button class="btn" onclick="game.startNewGame()">New Binding</button>
<button class="btn" onclick="game.continueGame()">Continue</button>
</div>
</div>
<!-- Dialogue Layer -->
<div id="dialogue-layer" class="ui-layer">
<div id="dialogue-box" class="glass-panel">
<div id="dialogue-text" class="dialogue-text"></div>
<div style="text-align:right; font-size:0.8rem; opacity:0.6;">Press SPACE or CLICK to continue...</div>
</div>
</div>
<!-- Battle UI -->
<div id="battle-ui" class="ui-layer">
<div class="battle-hud">
<!-- Enemy side -->
<div id="enemy-hud" class="stat-card glass-panel">
<div style="display:flex; justify-content:space-between; align-items:center;">
<strong id="enemy-name">???</strong>
<div id="enemy-type-badge"></div>
</div>
<div class="hp-bar-container"><div id="enemy-hp-fill" class="hp-bar-fill"></div></div>
<div style="font-size:0.8rem; text-align:right;" id="enemy-hp-text">0/0</div>
</div>
<!-- Player side -->
<div id="player-hud" class="stat-card glass-panel">
<div style="display:flex; justify-content:space-between; align-items:center;">
<strong id="player-name">???</strong>
<div id="player-type-badge"></div>
</div>
<div class="hp-bar-container"><div id="player-hp-fill" class="hp-bar-fill"></div></div>
<div style="font-size:0.8rem; text-align:right;" id="player-hp-text">0/0</div>
</div>
</div>
<div id="battle-menu" class="battle-menu">
<button class="action-btn" onclick="battle.selectAction('move')">MANIFEST</button>
<button class="action-btn" onclick="battle.selectAction('soul')">BIND</button>
<button class="action-btn" onclick="battle.selectAction('item')">ESSENCE</button>
<button class="action-btn" onclick="battle.selectAction('switch')">RECALL</button>
</div>
<div id="move-menu" class="battle-menu">
<!-- Dynamic Move Buttons -->
</div>
</div>
<!-- Team/Bag Overlay -->
<div id="overlay-layer" class="ui-layer">
<div id="overlay-content" class="glass-panel overlay-content">
<!-- Dynamic Content -->
</div>
<button class="btn" style="position:absolute; bottom:20px; right:20px;" onclick="game.closeOverlay()">Close</button>
</div>
<!-- Mobile Controls -->
<div id="mobile-controls">
<div></div><div class="ctrl-btn" onmousedown="game.input.up=true" onmouseup="game.input.up=false">W</div><div></div>
<div class="ctrl-btn" onmousedown="game.input.left=true" onmouseup="game.input.left=false">A</div>
<div class="ctrl-btn" onmousedown="game.input.down=true" onmouseup="game.input.down=false">S</div>
<div class="ctrl-btn" onmousedown="game.input.right=true" onmouseup="game.input.right=false">D</div>
</div>
</div>
<script>
/**
* AETHERIA: SOULBIND - Game Core
* Original IP construction
*/
// === Lore & Constants ===
const TYPES = {
T1: { name: 'Searing', color: '#ff5252' }, // Pyro-like
T2: { name: 'Verdant', color: '#69f0ae' }, // Flora-like
T3: { name: 'Tidal', color: '#448aff' }, // Aqua-like
T4: { name: 'Kinetic', color: '#ffd740' }, // Volt-like
T5: { name: 'Aether', color: '#e040fb' }, // Psy-like
T6: { name: 'Ferrum', color: '#b0bec5' }, // Steel-like
T7: { name: 'Primal', color: '#ff8a65' } // Mythic-like
};
const TYPE_CHART = {
T1: { weak: 'T3', strong: 'T2' },
T2: { weak: 'T1', strong: 'T3' },
T3: { weak: 'T2', strong: 'T1' },
T4: { weak: 'T6', strong: ['T1', 'T3'] },
T5: { weak: 'T6', strong: ['T4', 'T2'] },
T6: { weak: 'T4', strong: 'T5' }, // Ferrum is the balancer
T7: { weak: 'T6', strong: 'T7', resists: ['T1', 'T2', 'T3', 'T4', 'T5'] }
// Type F (Ferrum) beats all others slightly -> implemented as high defense/moderate off.
// Type G (Primal) resists everything but weak to F.
};
const ZONES = [
{
name: 'Luminous Glade',
bg: ['#1a3a3a', '#0a1a1a'],
particle: '#69f0ae',
encounterRate: 0.05,
guardian: 'Sylphidra',
storyNode: 'The waking world begins here, where breath becomes form.'
},
{
name: 'Iron Spires',
bg: ['#3a3a4a', '#1a1a2a'],
particle: '#b0bec5',
encounterRate: 0.07,
guardian: 'Ferrumax',
storyNode: 'The echoes of the Great Forge still vibrate through the stone.'
},
{
name: 'Neon Abyss',
bg: ['#2a1a3a', '#100a20'],
particle: '#e040fb',
encounterRate: 0.08,
guardian: 'Voidra',
storyNode: 'Where logic fractures and thoughts take tangible shape.'
},
{
name: 'Solar Plateau',
bg: ['#4a3a1a', '#2a1a0a'],
particle: '#ffd740',
encounterRate: 0.09,
guardian: 'Heliosurge',
storyNode: 'The zenith of all energy, scorching and absolute.'
},
{
name: 'Crystal Tundra',
bg: ['#1a3a4a', '#0d2a3a'],
particle: '#448aff',
encounterRate: 0.10,
guardian: 'Glaciax',
storyNode: 'Silence frozen in time, guarding the secrets of the deep.'
},
{
name: 'The Eternal Nexus',
bg: ['#2a2a2a', '#000000'],
particle: '#ff8a65',
encounterRate: 0.12,
guardian: 'Aetheron',
storyNode: 'The point of origin. The end of all journeys.'
}
];
// === Creature Definitions ===
const CREATURE_DATA = {
// Starters
'Ignisnap': {
name: 'Ignisnap', type: 'T1', base: { hp: 45, atk: 55, def: 40, spd: 50 },
lore: 'A tiny ember-sprite that feeds on dry brush.',
learnset: [ { lvl: 1, move: 'Singe' }, { lvl: 5, move: 'Heat Pulse' }, { lvl: 10, move: 'Flare Burst' } ],
evolve: { lvl: 16, item: 'Pyre-Core', to: 'Pyrebrand' }
},
'Leafawn': {
name: 'Leafawn', type: 'T2', base: { hp: 50, atk: 45, def: 50, spd: 45 },
lore: 'Gentle creatures whose antlers bloom with seasons.',
learnset: [ { lvl: 1, move: 'Vine Lash' }, { lvl: 5, move: 'Photosynth' }, { lvl: 10, move: 'Root Slam' } ],
evolve: { lvl: 16, item: 'Sylvan-Dew', to: 'Sylvanhart' }
},
'Aquashell': {
name: 'Aquashell', type: 'T3', base: { hp: 60, atk: 40, def: 60, spd: 35 },
lore: 'Hardy mollusks that filter cosmic energy from water.',
learnset: [ { lvl: 1, move: 'Bubble Pop' }, { lvl: 5, move: 'Silt Shield' }, { lvl: 10, move: 'Tidal Wave' } ],
evolve: { lvl: 16, item: 'Deep-Pearl', to: 'Tidalith' }
},
// Wilds Zone 1
'Glintwing': {
name: 'Glintwing', type: 'T4', base: { hp: 35, atk: 50, def: 30, spd: 70 },
lore: 'Fast as a blink, charged with static longing.',
learnset: [ { lvl: 1, move: 'Spark' }, { lvl: 6, move: 'Volt Dash' } ],
evolve: { lvl: 18, item: 'Ion-Sliver', to: 'Voltstrike' }
},
'Mosspeak': {
name: 'Mosspeak', type: 'T2', base: { hp: 70, atk: 50, def: 60, spd: 30 },
lore: 'Slow-moving boulders covered in ancient lichen.',
learnset: [ { lvl: 1, move: 'Stone Bash' }, { lvl: 7, move: 'Spore Cloud' } ],
evolve: { lvl: 20, item: 'Terran-Gem', to: 'Mossolith' }
},
'Sliverfin': {
name: 'Sliverfin', type: 'T3', base: { hp: 40, atk: 60, def: 35, spd: 60 },
lore: 'Blade-like fins that slice through currents.',
learnset: [ { lvl: 1, move: 'Aqua Cut' }, { lvl: 6, move: 'Sly Stream' } ],
evolve: { lvl: 18, item: 'Deep-Pearl', to: 'Siltblade' }
},
// ... More data will be procedurally added or expanded in the logic
};
const MOVES = {
'Singe': { pwr: 40, acc: 95, type: 'T1', cost: 10, effect: 'none', desc: 'A small burst of heat.' },
'Heat Pulse': { pwr: 60, acc: 90, type: 'T1', cost: 15, effect: 'burn', desc: 'Throws a ball of fire.' },
'Flare Burst': { pwr: 90, acc: 80, type: 'T1', cost: 20, effect: 'none', desc: 'Explosive heat blast.' },
'Vine Lash': { pwr: 40, acc: 95, type: 'T2', cost: 10, effect: 'none', desc: 'Whips with a vine.' },
'Photosynth': { pwr: 0, acc: 100, type: 'T2', cost: 10, effect: 'regen', desc: 'Heals via light.' },
'Root Slam': { pwr: 70, acc: 85, type: 'T2', cost: 15, effect: 'none', desc: 'Smashes from below.' },
'Bubble Pop': { pwr: 40, acc: 95, type: 'T3', cost: 10, effect: 'none', desc: 'Fires a bubble.' },
'Silt Shield': { pwr: 0, acc: 100, type: 'T3', cost: 10, effect: 'barrier', desc: 'Creates a mud shield.' },
'Tidal Wave': { pwr: 80, acc: 85, type: 'T3', cost: 20, effect: 'none', desc: 'A crushing wave.' },
'Spark': { pwr: 40, acc: 100, type: 'T4', cost: 10, effect: 'shock', desc: 'Tiny electric zap.' },
'Volt Dash': { pwr: 60, acc: 90, type: 'T4', cost: 15, effect: 'spd_up', desc: 'Fast electric strike.' },
'Stone Bash': { pwr: 60, acc: 90, type: 'T6', cost: 10, effect: 'none', desc: 'Heavy physical strike.' },
'Spore Cloud': { pwr: 0, acc: 80, type: 'T2', cost: 15, effect: 'frost', desc: 'Slows enemy down.' },
'Aqua Cut': { pwr: 50, acc: 95, type: 'T3', cost: 10, effect: 'none', desc: 'Quick water blade.' },
'Sly Stream': { pwr: 60, acc: 90, type: 'T3', cost: 15, effect: 'none', desc: 'Swift current hit.' },
};
// Add some generic moves to fill the 40+ requirement
const genericMoves = ['Pulse', 'Crash', 'Beam', 'Storm', 'Edge', 'Wave', 'Nova', 'Void', 'Shatter', 'Drift'];
const typeList = Object.keys(TYPES);
typeList.forEach(t => {
genericMoves.forEach(m => {
const name = TYPES[t].name + ' ' + m;
MOVES[name] = {
pwr: 40 + Math.floor(Math.random() * 60),
acc: 80 + Math.floor(Math.random() * 20),
type: t,
cost: 10 + Math.floor(Math.random() * 15),
effect: 'none',
desc: `A powerful ${TYPES[t].name} manifestation.`
};
});
});
// === Game State Manager ===
const game = {
canvas: null,
ctx: null,
width: 0,
height: 0,
state: 'TITLE',
player: {
pos: { x: 100, y: 100 },
team: [],
inventory: {
'Bind-Gem (Low)': 5,
'Bind-Gem (Mid)': 0,
'Bind-Gem (High)': 0,
'Essence- Dew': 3,
'Pyre-Core': 0,
'Sylvan-Dew': 0,
'Deep-Pearl': 0
},
zoneIdx: 0,
progression: 0,
unlockedZones: 1,
defeatedGuardians: []
},
input: { up: false, down: false, left: false, right: false },
currentDialogue: null,
init() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.resize();
window.addEventListener('resize', () => this.resize());
this.setupInput();
this.loop();
},
resize() {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.canvas.width = this.width;
this.canvas.height = this.height;
},
setupInput() {
window.addEventListener('keydown', e => {
if (e.key === 'w' || e.key === 'ArrowUp') this.input.up = true;
if (e.key === 's' || e.key === 'ArrowDown') this.input.down = true;
if (e.key === 'a' || e.key === 'ArrowLeft') this.input.left = true;
if (e.key === 'd' || e.key === 'ArrowRight') this.input.right = true;
if (e.key === ' ') this.handleGlobalAction();
});
window.addEventListener('keyup', e => {
if (e.key === 'w' || e.key === 'ArrowUp') this.input.up = false;
if (e.key === 's' || e.key === 'ArrowDown') this.input.down = false;
if (e.key === 'a' || e.key === 'ArrowLeft') this.input.left = false;
if (e.key === 'd' || e.key === 'ArrowRight') this.input.right = false;
});
},
handleGlobalAction() {
if (this.state === 'DIALOGUE') {
this.nextDialogue();
} else if (this.state === 'OVERWORLD') {
// Interaction check
}
},
startNewGame() {
document.getElementById('title-screen').classList.remove('active');
this.state = 'TUTORIAL';
this.player.pos = { x: 400, y: 300 };
this.player.team = [];
this.player.zoneIdx = 0;
this.showDialogue("Welcome, Wanderer. You have arrived in Aetheria, a realm where souls bond with essence.");
this.showDialogue("First, you must choose your first companion to bind with.");
this.state = 'STARTER_CHOICE';
this.showStarterMenu();
},
continueGame() {
const saved = localStorage.getItem('aetheria_save');
if (saved) {
this.player = JSON.parse(saved);
document.getElementById('title-screen').classList.remove('active');
this.state = 'OVERWORLD';
} else {
alert("No saved binding found.");
}
},
saveGame() {
localStorage.setItem('aetheria_save', JSON.stringify(this.player));
this.flash('rgba(0, 255, 255, 0.2)');
},
showDialogue(text) {
this.state = 'DIALOGUE';
this.currentDialogue = [text];
document.getElementById('dialogue-layer').classList.add('active');
this.updateDialogueText();
},
nextDialogue() {
if (this.currentDialogue.length > 1) {
this.currentDialogue.shift();
this.updateDialogueText();
} else {
this.currentDialogue = null;
document.getElementById('dialogue-layer').classList.remove('active');
if (this.state === 'DIALOGUE') this.state = 'OVERWORLD';
}
},
updateDialogueText() {
document.getElementById('dialogue-text').innerText = this.currentDialogue[0];
},
showStarterMenu() {
const overlay = document.getElementById('overlay-layer');
const content = document.getElementById('overlay-content');
content.innerHTML = `
<div style="padding:20px; font-size:1.2rem;">Choose your Soul-Bond:</div>
<div class="list-container">
<div class="list-item" onclick="game.chooseStarter('Ignisnap')">
<strong>Ignisnap</strong> <span class="type-badge" style="background:${TYPES.T1.color}">${TYPES.T1.name}</span><br>
${CREATURE_DATA.Ignisnap.lore}
</div>
<div class="list-item" onclick="game.chooseStarter('Leafawn')">
<strong>Leafawn</strong> <span class="type-badge" style="background:${TYPES.T2.color}">${TYPES.T2.name}</span><br>
${CREATURE_DATA.Leafawn.lore}
</div>
<div class="list-item" onclick="game.chooseStarter('Aquashell')">
<strong>Aquashell</strong> <span class="type-badge" style="background:${TYPES.T3.color}">${TYPES.T3.name}</span><br>
${CREATURE_DATA.Aquashell.lore}
</div>
</div>
`;
overlay.classList.add('active');
},
chooseStarter(id) {
const data = CREATURE_DATA[id];
const creature = {
id: id,
name: data.name,
type: data.type,
lvl: 5,
hp: data.base.hp * 5,
maxHp: data.base.hp * 5,
atk: data.base.atk,
def: data.base.def,
spd: data.base.spd,
moves: ['Singe', 'Vine Lash', 'Bubble Pop'].filter(m => MOVES[m].type === data.type).slice(0,1)
// simplified for start
};
// Ensure it has at least one move
if (creature.moves.length === 0) creature.moves.push('Singe');
this.player.team.push(creature);
document.getElementById('overlay-layer').classList.remove('active');
this.showDialogue(`You have bound with ${creature.name}! Now, let us explore the Luminous Glade.`);
this.state = 'OVERWORLD';
},
flash(color) {
const f = document.getElementById('screen-flash');
f.style.background = color;
f.style.opacity = 1;
setTimeout(() => f.style.opacity = 0, 200);
},
loop() {
this.update();
this.draw();
requestAnimationFrame(() => this.loop());
},
update() {
if (this.state === 'OVERWORLD') {
let mv = 4;
if (this.input.up) this.player.pos.y -= mv;
if (this.input.down) this.player.pos.y += mv;
if (this.input.left) this.player.pos.x -= mv;
if (this.input.right) this.player.pos.x += mv;
// Bounds
this.player.pos.x = Math.max(0, Math.min(this.width, this.player.pos.x));
this.player.pos.y = Math.max(0, Math.min(this.height, this.player.pos.y));
// Random Encounter
if (Math.random() < ZONES[this.player.zoneIdx].encounterRate * 0.1) {
this.triggerBattle();
}
}
},
draw() {
const zone = ZONES[this.player.zoneIdx];
const grad = this.ctx.createLinearGradient(0, 0, 0, this.height);
grad.addColorStop(0, zone.bg[0]);
grad.addColorStop(1, zone.bg[1]);
this.ctx.fillStyle = grad;
this.ctx.fillRect(0, 0, this.width, this.height);
// Draw Ambient Particles
this.drawParticles(zone.particle);
if (this.state === 'OVERWORLD' || this.state === 'DIALOGUE') {
// Draw Player
this.ctx.fillStyle = 'white';
this.ctx.beginPath();
this.ctx.arc(this.player.pos.x, this.player.pos.y, 10, 0, Math.PI*2);
this.ctx.fill();
this.ctx.shadowBlur = 15;
this.ctx.shadowColor = 'cyan';
this.ctx.strokeStyle = 'cyan';
this.ctx.stroke();
this.ctx.shadowBlur = 0;
}
},
drawParticles(color) {
this.ctx.fillStyle = color;
this.ctx.globalAlpha = 0.3;
for (let i=0; i<20; i++) {
const x = (Math.sin(Date.now()*0.001 + i) * 500) + this.width/2;
const y = ((Date.now()*0.05 + i*100) % this.height);
this.ctx.beginPath();
this.ctx.arc(x, y, 2, 0, Math.PI*2);
this.ctx.fill();
}
this.ctx.globalAlpha = 1.0;
},
triggerBattle() {
this.state = 'BATTLE';
battle.init();
}
};
// === Battle System ===
const battle = {
active: false,
enemy: null,
playerCreature: null,
turn: 'player',
menuState: 'main',
init() {
this.active = true;
document.getElementById('battle-ui').classList.add('active');
// Generate Random Enemy
const pool = Object.keys(CREATURE_DATA).filter(id => id !== 'Ignisnap' && id !== 'Leafawn' && id !== 'Aquashell');
const id = pool[Math.floor(Math.random() * pool.length)];
const data = CREATURE_DATA[id];
const lvl = 3 + Math.floor(Math.random() * (game.player.zoneIdx * 5));
this.enemy = {
name: data.name,
type: data.type,
lvl: lvl,
hp: data.base.hp * lvl,
maxHp: data.base.hp * lvl,
atk: data.base.atk + lvl,
def: data.base.def + lvl,
spd: data.base.spd + lvl,
moves: ['Singe', 'Vine Lash', 'Bubble Pop', 'Spark'].filter(m => MOVES[m].type === data.type).slice(0,2)
};
if (this.enemy.moves.length === 0) this.enemy.moves.push('Singe');
this.playerCreature = game.player.team[0];
this.updateUI();
this.startTurn();
},
startTurn() {
const spdP = this.playerCreature.spd;
const spdE = this.enemy.spd;
if (spdP >= spdE) {
this.turn = 'player';
this.showPlayerMenu();
} else {
this.turn = 'enemy';
this.enemyTurn();
}
},
showPlayerMenu() {
document.getElementById('battle-menu').classList.add('visible');
},
selectAction(action) {
document.getElementById('battle-menu').classList.remove('visible');
if (action === 'move') {
this.showMoveMenu();
} else if (action === 'soul') {
this.attemptCapture();
} else if (action === 'item') {
this.showItemMenu();
} else if (action === 'switch') {
this.showSwitchMenu();
}
},
showMoveMenu() {
const menu = document.getElementById('move-menu');
menu.innerHTML = '';
menu.classList.add('visible');
this.playerCreature.moves.forEach(moveId => {
const m = MOVES[moveId];
const btn = document.createElement('button');
btn.className = 'action-btn';
btn.innerHTML = `${moveId} <small>(${m.pwr})</small>`;
btn.onclick = () => this.executeMove(this.playerCreature, this.enemy, moveId);
menu.appendChild(btn);
});
},
executeMove(attacker, target, moveId) {
document.getElementById('move-menu').classList.remove('visible');
const move = MOVES[moveId];
// Damage Calc
let multiplier = 1.0;
const attackerType = attacker.type;
const targetType = target.type;
if (TYPE_CHART[attackerType] && TYPE_CHART[attackerType].strong === targetType) multiplier = 1.5;
if (TYPE_CHART[attackerType] && Array.isArray(TYPE_CHART[attackerType].strong) && TYPE_CHART[attackerType].strong.includes(targetType)) multiplier = 1.5;
if (TYPE_CHART[targetType] && TYPE_CHART[targetType].strong === attackerType) multiplier = 0.5;
// STAB
if (attacker.type === move.type) multiplier *= 1.25;
const dmg = Math.floor((0.4 * attacker.lvl * attacker.atk / target.def * multiplier) + Math.random() * 5);
target.hp -= dmg;
target.hp = Math.max(0, target.hp);
this.updateUI();
this.animateHit(dmg, target === this.enemy);
if (target.hp <= 0) {
this.resolveBattle(true);
} else {
setTimeout(() => this.enemyTurn(), 1000);
}
},
enemyTurn() {
const moveId = this.enemy.moves[Math.floor(Math.random() * this.enemy.moves.length)];
const move = MOVES[moveId];
let multiplier = 1.0;
if (TYPE_CHART[this.enemy.type] && TYPE_CHART[this.enemy.type].strong === this.playerCreature.type) multiplier = 1.5;
const dmg = Math.floor((0.4 * this.enemy.lvl * this.enemy.atk / this.playerCreature.def * multiplier) + Math.random() * 5);
this.playerCreature.hp -= dmg;
this.playerCreature.hp = Math.max(0, this.playerCreature.hp);
this.updateUI();
this.animateHit(dmg, false);
if (this.playerCreature.hp <= 0) {
this.resolveBattle(false);
} else {
setTimeout(() => this.startTurn(), 1000);
}
},
attemptCapture() {
const successRate = (1 - (this.enemy.hp / this.enemy.maxHp)) * 0.7;
if (Math.random() < successRate) {
game.player.team.push({...this.enemy, lvl: this.enemy.lvl});
this.resolveBattle(true, "Successfully Bound!");
} else {
this.showDialogue("The soul resisted the binding!");
setTimeout(() => {
document.getElementById('dialogue-layer').classList.remove('active');
this.enemyTurn();
}, 1500);
}
},
resolveBattle(win, msg = "Victory!") {
if (win) {
this.showDialogue(msg);
// Give EXP
this.playerCreature.lvl++;
this.playerCreature.maxHp += 10;
this.playerCreature.hp = this.playerCreature.maxHp;
} else {
this.showDialogue("Your companion has fainted...");
}
setTimeout(() => {
document.getElementById('battle-ui').classList.remove('active');
this.active = false;
game.state = 'OVERWORLD';
}, 2000);
},
updateUI() {
document.getElementById('enemy-name').innerText = this.enemy.name;
document.getElementById('enemy-hp-fill').style.width = (this.enemy.hp / this.enemy.maxHp * 100) + '%';
document.getElementById('enemy-hp-text').innerText = `${Math.floor(this.enemy.hp)}/${Math.floor(this.enemy.maxHp)}`;
document.getElementById('enemy-type-badge').innerHTML = `<span class="type-badge" style="background:${TYPES[this.enemy.type].color}">${TYPES[this.enemy.type].name}</span>`;
document.getElementById('player-name').innerText = this.playerCreature.name;
document.getElementById('player-hp-fill').style.width = (this.playerCreature.hp / this.playerCreature.maxHp * 100) + '%';
document.getElementById('player-hp-text').innerText = `${Math.floor(this.playerCreature.hp)}/${Math.floor(this.playerCreature.maxHp)}`;
document.getElementById('player-type-badge').innerHTML = `<span class="type-badge" style="background:${TYPES[this.playerCreature.type].color}">${TYPES[this.playerCreature.type].name}</span>`;
},
animateHit(dmg, isEnemy) {
const el = isEnemy ? document.getElementById('enemy-hud') : document.getElementById('player-hud');
el.classList.add('shake');
setTimeout(() => el.classList.remove('shake'), 200);
game.flash(isEnemy ? 'rgba(255,0,0,0.1)' : 'rgba(255,255,255,0.1)');
},
showItemMenu() {
this.showDialogue("You have no usable essences in your bag!");
setTimeout(() => {
document.getElementById('dialogue-layer').classList.remove('active');
this.enemyTurn();
}, 1000);
},
showSwitchMenu() {
this.showDialogue("Your team is too small to switch currently!");
setTimeout(() => {
document.getElementById('dialogue-layer').classList.remove('active');
this.enemyTurn();
}, 1000);
}
};
// Boot
window.onload = () => game.init();
</script>
</body>
</html>
<!-- agent-meta {"model":"gemma-4-31b","provider":"cerebras","persona":"full","sessionId":"7a84424a-ff8e-47c3-8f37-0b86894aa2d4","tokensIn":76792,"tokensOut":13947,"tokensTotal":90739,"turns":5,"toolCalls":4,"failedToolCalls":2,"timestamp":"2026-07-14T16:15:22.226Z"} -->