Creature Collection
Gemma 4 31b · basic

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
- 2 / 5
- Prompt spirit
- 2 / 5
- Delight
- 1 / 5
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 36,394 bytes · SHA-256 2efea9712977
<!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>AETHERIUM BINDING</title>
<style>
:root {
--bg-dark: #0a0a12;
--glass: rgba(255, 255, 255, 0.1);
--glass-border: rgba(255, 255, 255, 0.2);
--text-main: #e0e0ff;
--accent: #70a1ff;
--danger: #ff4757;
--success: #2ed573;
--warning: #ffa502;
--font-main: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
* { box-sizing: border-box; user-select: none; }
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
background: var(--bg-dark); color: var(--text-main);
font-family: var(--font-main); overflow: hidden;
}
#game-container {
position: relative; width: 100vw; height: 100vh;
display: flex; align-items: center; justify-content: center;
}
canvas {
image-rendering: pixelated;
max-width: 100%; max-height: 100%;
box-shadow: 0 0 50px rgba(0,0,0,0.5);
}
/* UI Systems */
.overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
display: flex; flex-direction: column; align-items: center; justify-content: center;
pointer-events: none; z-index: 10;
}
.modal {
pointer-events: auto; background: var(--glass);
backdrop-filter: blur(15px); border: 1px solid var(--glass-border);
border-radius: 20px; padding: 30px; text-align: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.8);
animation: fadeIn 0.3s ease-out;
max-width: 90%; max-height: 80%; overflow-y: auto;
}
@keyframes fadeIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
.hidden { display: none !important; }
.btn {
background: var(--accent); color: white; border: none;
padding: 12px 24px; margin: 10px; border-radius: 8px;
cursor: pointer; font-weight: bold; transition: 0.2s;
text-transform: uppercase; letter-spacing: 1px;
}
.btn:hover { filter: brightness(1.2); transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
/* Battle UI */
#battle-ui {
position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
width: 90%; max-width: 800px; display: grid;
grid-template-columns: 1fr 1fr; gap: 20px;
pointer-events: auto;
}
.battle-menu {
background: var(--glass); backdrop-filter: blur(10px);
border: 2px solid var(--glass-border); border-radius: 15px;
padding: 15px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
}
.stat-pill {
background: rgba(0,0,0,0.4); padding: 5px 15px; border-radius: 20px;
font-size: 0.9em; border: 1px solid var(--glass-border);
}
.hp-bar-container {
width: 100%; height: 12px; background: #333; border-radius: 6px; overflow: hidden;
margin: 5px 0; border: 1px solid #000;
}
.hp-bar-fill {
height: 100%; background: var(--success); width: 100%;
transition: width 0.5s ease-out;
}
.floating-text {
position: absolute; color: white; font-weight: bold;
pointer-events: none; animation: floatUp 1s ease-out forwards;
text-shadow: 2px 2px 0 #000;
}
@keyframes floatUp { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-50px); } }
.story-flash {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.7); display: flex; align-items: center;
justify-content: center; z-index: 100; pointer-events: auto;
transition: opacity 0.5s;
}
/* Inventory/Team Grids */
.grid-list {
display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px; margin: 20px 0;
}
.grid-item {
background: rgba(0,0,0,0.3); border: 1px solid var(--glass-border);
padding: 10px; border-radius: 10px; cursor: pointer; transition: 0.2s;
}
.grid-item:hover { background: var(--glass); }
/* Touch Controls */
#touch-controls {
position: absolute; bottom: 20px; left: 20px;
display: grid; grid-template-columns: repeat(3, 50px); gap: 10px;
pointer-events: auto;
}
.touch-btn {
width: 50px; height: 50px; background: var(--glass);
border: 1px solid var(--glass-border); border-radius: 50%;
color: white; display: flex; align-items: center; justify-content: center;
font-weight: bold; cursor: pointer;
}
#mute-toggle {
position: absolute; top: 20px; right: 20px; z-index: 100;
pointer-events: auto; background: var(--glass); border: none;
color: white; padding: 5px 10px; border-radius: 5px; cursor: pointer;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="gameCanvas"></canvas>
<button id="mute-toggle">Audio: ON</button>
<!-- Title Screen -->
<div id="title-screen" class="overlay">
<div class="modal">
<h1 style="font-size: 3em; margin-bottom: 10px; color: var(--accent);">AETHERIUM</h1>
<p style="margin-bottom: 30px; font-style: italic;">Bind the fragments of the shattered void.</p>
<button class="btn" onclick="game.startNewGame()">New Binding</button>
<button class="btn" onclick="game.continueGame()">Continue</button>
</div>
</div>
<!-- Story/Dialogue Overlay -->
<div id="story-overlay" class="story-flash hidden">
<div class="modal" style="max-width: 600px;">
<p id="story-text" style="font-size: 1.2em; line-height: 1.6;"></p>
<button class="btn" onclick="game.closeStory()">Proceed</button>
</div>
</div>
<!-- Battle UI -->
<div id="battle-screen" class="overlay hidden">
<!-- Entities rendered on canvas -->
<div id="battle-ui">
<div class="battle-menu" id="battle-actions">
<button class="btn" onclick="game.battle.handleAction('attack')">Strike</button>
<button class="btn" onclick="game.battle.handleAction('special')">Aether</button>
<button class="btn" onclick="game.battle.handleAction('item')">Vessel</button>
<button class="btn" onclick="game.battle.handleAction('switch')">Switch</button>
</div>
<div class="battle-menu" id="ability-selection" style="display:none;">
<!-- Ability buttons injected here -->
</div>
<div class="battle-log" style="background: var(--glass); padding: 15px; border-radius: 15px; border: 1px solid var(--glass-border); font-size: 0.9em;">
<div id="battle-msg">A wild creature emerges!</div>
</div>
</div>
</div>
<!-- Menu System -->
<div id="menu-overlay" class="overlay hidden">
<div class="modal" id="menu-modal">
<!-- Dynamic content -->
</div>
</div>
<!-- Overworld Controls -->
<div id="touch-controls" class="hidden">
<div></div><div class="touch-btn" onmousedown="game.input.setKey('KeyW', true)" onmouseup="game.input.setKey('KeyW', false)">↑</div><div></div>
<div class="touch-btn" onmousedown="game.input.setKey('KeyA', true)" onmouseup="game.input.setKey('KeyA', false)">←</div>
<div class="touch-btn" onmousedown="game.input.setKey('KeyS', true)" onmouseup="game.input.setKey('KeyS', false)">↓</div>
<div class="touch-btn" onmousedown="game.input.setKey('KeyD', true)" onmouseup="game.input.setKey('KeyD', false)">→</div>
</div>
</div>
<script>
/**
* GAME DATA & CONSTANTS
* Theme: Aetherium - Floating islands of crystallized energy.
* Creatures: "Essences".
*/
const TYPES = {
SOLAR: { name: 'Solar', color: '#ff9f43' }, // A
LUNAR: { name: 'Lunar', color: '#54a0ff' }, // B
VAPOR: { name: 'Vapor', color: '#00d2d3' }, // C
CORE: { name: 'Core', color: '#ee5253' }, // D
VOID: { name: 'Void', color: '#5f27cd' }, // E
PRISM: { name: 'Prism', color: '#ff9ff3' }, // F
STASIS: { name: 'Stasis', color: '#c8d6e5' } // G
};
const TYPE_CHART = {
// Solar > Lunar > Vapor > Solar
'Solar': { 'Lunar': 1.5, 'Vapor': 0.5, 'Core': 0.5, 'Void': 1.0, 'Prism': 1.0, 'Stasis': 1.0 },
'Lunar': { 'Vapor': 1.5, 'Solar': 0.5, 'Core': 0.5, 'Void': 1.0, 'Prism': 1.0, 'Stasis': 1.0 },
'Vapor': { 'Solar': 1.5, 'Lunar': 0.5, 'Core': 0.5, 'Void': 1.0, 'Prism': 1.0, 'Stasis': 1.0 },
// Core > Solar & Vapor
'Core': { 'Solar': 1.5, 'Vapor': 1.5, 'Lunar': 1.0, 'Void': 0.5, 'Prism': 1.0, 'Stasis': 1.0 },
// Void > Core & Lunar
'Void': { 'Core': 1.5, 'Lunar': 1.5, 'Solar': 1.0, 'Vapor': 1.0, 'Prism': 1.0, 'Stasis': 1.0 },
// Prism beats all slightly (1.2x)
'Prism': { 'Solar': 1.2, 'Lunar': 1.2, 'Vapor': 1.2, 'Core': 1.2, 'Void': 1.2, 'Stasis': 2.0 },
// Stasis resists everything, weak to Prism
'Stasis': { 'Solar': 0.5, 'Lunar': 0.5, 'Vapor': 0.5, 'Core': 0.5, 'Void': 0.5, 'Prism': 0.5, 'Stasis': 1.0 }
};
const ABILITIES = {
'Singe': { power: 40, accuracy: 95, type: 'Solar', cost: 10, effect: 'burn', desc: 'Heat pulse. Chance to burn.' },
'Tide': { power: 40, accuracy: 95, type: 'Lunar', cost: 10, effect: 'none', desc: 'Watery surge.' },
'Mist': { power: 40, accuracy: 95, type: 'Vapor', cost: 10, effect: 'frost', desc: 'Chilling fog. Chance to freeze.' },
'Crush': { power: 60, accuracy: 80, type: 'Core', cost: 15, effect: 'none', desc: 'Heavy impact.' },
'Null': { power: 60, accuracy: 80, type: 'Void', cost: 15, effect: 'shock', desc: 'Void blast. Chance to shock.' },
'Refract': { power: 50, accuracy: 100, type: 'Prism', cost: 12, effect: 'none', desc: 'Prismatic beam.' },
'Sustain': { power: 0, accuracy: 100, type: 'Stasis', cost: 15, effect: 'regen', desc: 'Restore HP over time.' },
'Flare': { power: 110, accuracy: 70, type: 'Solar', cost: 30, effect: 'burn', desc: 'Explosive heat.' },
'Eclipse': { power: 110, accuracy: 70, type: 'Lunar', cost: 30, effect: 'none', desc: 'Dark wave.' },
'Cyclone': { power: 110, accuracy: 70, type: 'Vapor', cost: 30, effect: 'frost', desc: 'Raging storm.' },
'Quake': { power: 120, accuracy: 60, type: 'Core', cost: 40, effect: 'none', desc: 'Shattering strike.' },
'Oblivion': { power: 120, accuracy: 60, type: 'Void', cost: 40, effect: 'shock', desc: 'Total erasure.' },
'Spectrum': { power: 100, accuracy: 90, type: 'Prism', cost: 30, effect: 'none', desc: 'All-color burst.' },
'Aegis': { power: 0, accuracy: 100, type: 'Stasis', cost: 20, effect: 'barrier', desc: 'Crystal shield.' },
'Searing Ray': { power: 70, accuracy: 90, type: 'Solar', cost: 15, effect: 'none', desc: 'Piercing light.' },
'Moonbeam': { power: 70, accuracy: 90, type: 'Lunar', cost: 15, effect: 'none', desc: 'Gentle but firm.' },
'Steam Vent': { power: 70, accuracy: 90, type: 'Vapor', cost: 15, effect: 'burn', desc: 'Hot steam.' },
'Tectonic': { power: 80, accuracy: 75, type: 'Core', cost: 20, effect: 'none', desc: 'Ground shift.' },
'Singularity': { power: 80, accuracy: 75, type: 'Void', cost: 20, effect: 'none', desc: 'Gravity well.' },
'Prism Wave': { power: 80, accuracy: 85, type: 'Prism', cost: 20, effect: 'none', desc: 'Rainbow wave.' },
'Stillness': { power: 0, accuracy: 100, type: 'Stasis', cost: 10, effect: 'frost', desc: 'Freeze time.' },
'Solar Wind': { power: 50, accuracy: 100, type: 'Solar', cost: 5, effect: 'none', desc: 'Quick breeze.' },
'Lunar Drift': { power: 50, accuracy: 100, type: 'Lunar', cost: 5, effect: 'none', desc: 'Fluid motion.' },
'Vapor Step': { power: 50, accuracy: 100, type: 'Vapor', cost: 5, effect: 'none', desc: 'Ghostly shift.' },
'Iron Skin': { power: 0, accuracy: 100, type: 'Core', cost: 15, effect: 'barrier', desc: 'Harden surface.' },
'Void Grip': { power: 60, accuracy: 85, type: 'Void', cost: 15, effect: 'none', desc: 'Tearing force.' },
'Light Shell': { power: 0, accuracy: 100, type: 'Prism', cost: 15, effect: 'barrier', desc: 'Reflective skin.' },
'Zenith': { power: 150, accuracy: 50, type: 'Solar', cost: 50, effect: 'burn', desc: 'Highest peak.' },
'Nadir': { power: 150, accuracy: 50, type: 'Lunar', cost: 50, effect: 'none', desc: 'Deepest valley.' },
'Vortex': { power: 150, accuracy: 50, type: 'Vapor', cost: 50, effect: 'frost', desc: 'Sucking wind.' },
'Cataclysm': { power: 160, accuracy: 40, type: 'Core', cost: 60, effect: 'none', desc: 'World ender.' },
'Entropy': { power: 160, accuracy: 40, type: 'Void', cost: 60, effect: 'shock', desc: 'Energy decay.' },
'Radiance': { power: 130, accuracy: 80, type: 'Prism', cost: 40, effect: 'none', desc: 'Pure light.' },
'Eternal': { power: 0, accuracy: 100, type: 'Stasis', cost: 30, effect: 'regen', desc: 'Infinite life.' },
'Spark': { power: 30, accuracy: 100, type: 'Solar', cost: 5, effect: 'none', desc: 'Tiny spark.' },
'Dew': { power: 30, accuracy: 100, type: 'Lunar', cost: 5, effect: 'none', desc: 'Morning dew.' },
'Gale': { power: 30, accuracy: 100, type: 'Vapor', cost: 5, effect: 'none', desc: 'Light wind.' },
'Silt': { power: 30, accuracy: 100, type: 'Core', cost: 5, effect: 'none', desc: 'Dirt spray.' },
'Echo': { power: 30, accuracy: 100, type: 'Void', cost: 5, effect: 'none', desc: 'Void echo.' },
'Glint': { power: 30, accuracy: 100, type: 'Prism', cost: 5, effect: 'none', desc: 'Quick glint.' }
};
const ESSENCE_DATA = [
{ id: 0, name: 'Pyris', type: 'Solar', hp: 45, atk: 55, def: 40, spd: 60, lore: 'A small ember with a fiery spirit.', learns: [0, 14, 7, 27], evolves: { level: 20, into: 18 } },
{ id: 1, name: 'Aquilo', type: 'Lunar', hp: 50, atk: 45, def: 50, spd: 50, lore: 'Fluid essence resembling a floating pearl.', learns: [1, 15, 8, 28], evolves: { level: 20, into: 19 } },
{ id: 2, name: 'Vesper', type: 'Vapor', hp: 40, atk: 50, def: 30, spd: 70, lore: 'A whispering cloud of silver mist.', learns: [2, 16, 9, 29], evolves: { level: 20, into: 20 } },
{ id: 3, name: 'Terran', type: 'Core', hp: 60, atk: 65, def: 60, spd: 30, lore: 'A floating jagged piece of obsidian.', learns: [3, 17, 10, 30], evolves: { level: 20, into: 21 } },
{ id: 4, name: 'Nyx', type: 'Void', hp: 40, atk: 60, def: 40, spd: 60, lore: 'A shadow that defies the light.', learns: [4, 18, 11, 31], evolves: { level: 20, into: 22 } },
{ id: 5, name: 'Lumina', type: 'Prism', hp: 45, atk: 45, def: 45, spd: 65, lore: 'A crystalline prism that refracts all colors.', learns: [5, 19, 12, 32], evolves: { level: 20, into: 23 } },
{ id: 6, name: 'Sola', type: 'Solar', hp: 40, atk: 60, def: 30, spd: 70, lore: 'A floating ray of sunlight.', learns: [0, 14, 22, 7], evolves: { level: 20, into: 18 } },
{ id: 7, name: 'Luna', type: 'Lunar', hp: 55, atk: 40, def: 55, spd: 45, lore: 'A crescent-shaped water wisp.', learns: [1, 15, 23, 8], evolves: { level: 20, into: 19 } },
{ id: 8, name: 'Vapo', type: 'Vapor', hp: 35, atk: 55, def: 35, spd: 75, lore: 'A swirling air vortex.', learns: [2, 16, 24, 9], evolves: { level: 20, into: 20 } },
{ id: 9, name: 'Coru', type: 'Core', hp: 70, atk: 50, def: 70, spd: 25, lore: 'A heavy metallic sphere.', learns: [3, 17, 25, 10], evolves: { level: 20, into: 21 } },
{ id: 10, name: 'Voida', type: 'Void', hp: 45, atk: 55, def: 45, spd: 55, lore: 'A fragment of a dead star.', learns: [4, 18, 26, 11], evolves: { level: 20, into: 22 } },
{ id: 11, name: 'Prismi', type: 'Prism', hp: 40, atk: 50, def: 40, spd: 60, lore: 'A sparkling diamond shard.', learns: [5, 19, 27, 12], evolves: { level: 20, into: 23 } },
{ id: 12, name: 'Stasi', type: 'Stasis', hp: 80, atk: 30, def: 80, spd: 20, lore: 'A frozen moment in time.', learns: [6, 13, 30, 33], evolves: null },
{ id: 13, name: 'Ignis', type: 'Solar', hp: 50, atk: 70, def: 40, spd: 50, lore: 'A living bonfire.', learns: [0, 14, 7, 27], evolves: { level: 20, into: 18 } },
{ id: 14, name: 'Glacia', type: 'Lunar', hp: 50, atk: 70, def: 40, spd: 50, lore: 'An ice spire floating in air.', learns: [1, 15, 8, 28], evolves: { level: 20, into: 19 } },
{ id: 15, name: 'Zephyr', type: 'Vapor', hp: 40, atk: 70, def: 30, spd: 70, lore: 'A whirlwind of shards.', learns: [2, 16, 9, 29], evolves: { level: 20, into: 20 } },
{ id: 16, name: 'Groma', type: 'Core', hp: 70, atk: 60, def: 70, spd: 30, lore: 'A mountain-sized pebble.', learns: [3, 17, 10, 30], evolves: { level: 20, into: 21 } },
{ id: 17, name: 'Umbra', type: 'Void', hp: 50, atk: 60, def: 50, spd: 50, lore: 'The echo of a scream.', learns: [4, 18, 11, 31], evolves: { level: 20, into: 22 } },
// Transformed / Evolved
{ id: 18, name: 'Pyrosurge', type: 'Solar', hp: 70, atk: 90, def: 60, spd: 80, lore: 'A raging sun-beast.', learns: [27, 14, 7, 27], evolves: null },
{ id: 19, name: 'Aquastorm', type: 'Lunar', hp: 80, atk: 80, def: 70, spd: 70, lore: 'The oceanic sovereign.', learns: [28, 15, 8, 28], evolves: null },
{ id: 20, name: 'Vaporwind', type: 'Vapor', hp: 60, atk: 90, def: 50, spd: 100, lore: 'A storm that never ends.', learns: [29, 16, 9, 29], evolves: null },
{ id: 21, name: 'Corite Titan', type: 'Core', hp: 100, atk: 100, def: 100, spd: 40, lore: 'A fortress of living rock.', learns: [30, 17, 10, 30], evolves: null },
{ id: 22, name: 'Void Reaver', type: 'Void', hp: 70, atk: 110, def: 60, spd: 90, lore: 'A reaper of aether.', learns: [31, 18, 11, 31], evolves: null },
{ id: 23, name: 'Prism Lord', type: 'Prism', hp: 75, atk: 85, def: 75, spd: 85, lore: 'Master of all spectrums.', learns: [32, 19, 12, 32], evolves: null },
// More fillers to reach 27
{ id: 24, name: 'Stalactox', type: 'Stasis', hp: 90, atk: 40, def: 90, spd: 20, lore: 'A petrified giant.', learns: [6, 13, 30, 33], evolves: null },
{ id: 25, name: 'Flareonix', type: 'Solar', hp: 65, atk: 85, def: 55, spd: 75, lore: 'Bright as a midday star.', learns: [0, 14, 7, 27], evolves: null },
{ id: 26, name: 'Tidewave', type: 'Lunar', hp: 65, atk: 85, def: 55, spd: 75, lore: 'A rolling lunar tide.', learns: [1, 15, 8, 28], evolves: null },
// The 27th
{ id: 26, name: 'Apex', type: 'Prism', hp: 100, atk: 100, def: 100, spd: 100, lore: 'The ultimate binding.', learns: [32, 5, 19, 12], evolves: null },
];
// Fix IDs for the last few to be unique
ESSENCE_DATA[24].id = 24;
ESSENCE_DATA[25].id = 25;
ESSENCE_DATA[26].id = 26;
const ZONES = [
{ name: 'Luminous Plains', grad: ['#5eead4', '#ace1e1'], encounterRate: 0.1, boss: 'Sola Prime', color: '#70a1ff' },
{ name: 'Obsidian Spires', grad: ['#4b5563', '#1f2937'], encounterRate: 0.15, boss: 'Coru Titan', color: '#ee5253' },
{ name: 'Azure Depths', grad: ['#0ea5e9', '#0369a1'], encounterRate: 0.15, boss: 'Tidewave', color: '#54a0ff' },
{ name: 'Void Hollows', grad: ['#2e1065', '#020617'], encounterRate: 0.2, boss: 'Void Reaver', color: '#5f27cd' },
{ name: 'Prism Garden', grad: ['#fbcfe8', '#db2777'], encounterRate: 0.2, boss: 'Prism Lord', color: '#ff9ff3' },
{ name: 'The Eternal Core', grad: ['#fef3c7', '#b45309'], encounterRate: 0.25, boss: 'Aether Apex', color: '#facc15' },
];
const ITEMS = {
BIND_SMALL: { name: 'Glass Vessel', effect: 'capture', quality: 0.4 },
BIND_MED: { name: 'Quartz Vessel', effect: 'capture', quality: 0.7 },
BIND_LARGE: { name: 'Diamond Vessel', effect: 'capture', quality: 0.95 },
HEAL_S: { name: 'Aether Dust', effect: 'heal', value: 50 },
HEAL_M: { name: 'Aether Shard', effect: 'heal', value: 150 },
CATALYST: { name: 'Void Catalyst', effect: 'evolve' },
};
/**
* CORE GAME ENGINE
*/
class Game {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.width = 800;
this.height = 600;
this.canvas.width = this.width;
this.canvas.height = this.height;
this.state = 'TITLE'; // TITLE, OVERWORLD, BATTLE, MENU, STORY
this.player = {
x: 400, y: 300,
zone: 0,
party: [],
inventory: {
BIND_SMALL: 5, BIND_MED: 0, BIND_LARGE: 0,
HEAL_S: 5, HEAL_M: 0, CATALYST: 0
},
collected: [],
anchorsDefeated: 0
};
this.input = {
keys: {},
setKey(k, v) { this.keys[k] = v; }
};
this.audio = {
ctx: null,
muted: false,
init() {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
},
beep(freq, dur = 0.1, vol = 0.1) {
if (this.muted || !this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
gain.gain.setValueAtTime(vol, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + dur);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + dur);
}
};
window.addEventListener('keydown', e => this.input.setKey(e.code, true));
window.addEventListener('keyup', e => this.input.setKey(e.code, false));
document.getElementById('mute-toggle').onclick = () => {
this.audio.muted = !this.audio.muted;
document.getElementById('mute-toggle').innerText = `Audio: ${this.audio.muted ? 'OFF' : 'ON'}`;
};
this.loop();
}
startNewGame() {
this.audio.init();
this.state = 'STORY';
this.showStory("Welcome to Aetherium. You are a Binder, tasked with collecting the fragmented Essences to stabilize the Void.");
this.setupStarter();
document.getElementById('title-screen').classList.add('hidden');
document.getElementById('touch-controls').classList.remove('hidden');
}
setupStarter() {
// Simple starter choice logic
const starters = [0, 1, 2];
const choice = 0; // Default Pyris
const base = ESSENCE_DATA[choice];
this.player.party.push(this.createEssence(base, 5));
}
continueGame() {
const saved = localStorage.getItem('aetherium_save');
if (saved) {
this.player = JSON.parse(saved);
this.state = 'OVERWORLD';
document.getElementById('title-screen').classList.add('hidden');
} else {
alert("No saved binding found.");
}
}
createEssence(data, level) {
const e = { ...data, level, curHp: data.hp, maxHp: data.hp };
// Scale stats
e.atk += (level - 1) * 2;
e.def += (level - 1) * 2;
e.hp += (level - 1) * 5;
e.curHp = e.hp;
e.maxHp = e.hp;
return e;
}
showStory(text) {
this.state = 'STORY';
document.getElementById('story-overlay').classList.remove('hidden');
document.getElementById('story-text').innerText = text;
}
closeStory() {
document.getElementById('story-overlay').classList.add('hidden');
this.state = 'OVERWORLD';
}
save() {
localStorage.setItem('aetherium_save', JSON.stringify(this.player));
this.audio.beep(440, 0.2);
}
loop() {
this.update();
this.draw();
requestAnimationFrame(() => this.loop());
}
update() {
if (this.state === 'OVERWORLD') {
const speed = 4;
let moved = false;
if (this.input.keys['KeyW'] || this.input.keys['ArrowUp']) { this.player.y -= speed; moved = true; }
if (this.input.keys['KeyS'] || this.input.keys['ArrowDown']) { this.player.y += speed; moved = true; }
if (this.input.keys['KeyA'] || this.input.keys['ArrowLeft']) { this.player.x -= speed; moved = true; }
if (this.input.keys['KeyD'] || this.input.keys['ArrowRight']) { this.player.x += speed; moved = true; }
// Boundaries
this.player.x = Math.max(0, Math.min(this.width, this.player.x));
this.player.y = Math.max(0, Math.min(this.height, this.player.y));
if (moved) {
if (Math.random() < ZONES[this.player.zone].encounterRate * 0.05) {
this.initBattle();
}
}
// Zone transitions (simplified)
if (this.player.x > this.width - 5 && this.player.zone < 5) {
this.player.zone++;
this.player.x = 10;
this.showStory(`You have entered the ${ZONES[this.player.zone].name}. The air feels different here.`);
}
}
}
draw() {
const ctx = this.ctx;
ctx.clearRect(0, 0, this.width, this.height);
if (this.state === 'OVERWORLD' || this.state === 'STORY') {
const zone = ZONES[this.player.zone];
const grad = ctx.createLinearGradient(0, 0, 0, this.height);
grad.addColorStop(0, zone.grad[0]);
grad.addColorStop(1, zone.grad[1]);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, this.width, this.height);
// Grid
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
ctx.lineWidth = 1;
for(let i=0; i<this.width; i+=50) {
ctx.beginPath(); ctx.moveTo(i,0); ctx.lineTo(i, this.height); ctx.stroke();
}
for(let i=0; i<this.height; i+=50) {
ctx.beginPath(); ctx.moveTo(0,i); ctx.lineTo(this.width, i); ctx.stroke();
}
// Player
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(this.player.x, this.player.y, 10, 0, Math.PI*2);
ctx.fill();
ctx.shadowBlur = 15; ctx.shadowColor = 'white';
ctx.stroke(); ctx.shadowBlur = 0;
}
if (this.state === 'BATTLE') {
this.battle.draw();
}
}
initBattle() {
this.state = 'BATTLE';
this.battle.start();
document.getElementById('battle-screen').classList.remove('hidden');
}
endBattle(victory) {
this.state = 'OVERWORLD';
document.getElementById('battle-screen').classList.add('hidden');
if (victory) this.audio.beep(660, 0.3);
}
}
class BattleSystem {
constructor(game) {
this.game = game;
this.enemies = [];
this.activePlayerIdx = 0;
this.turnQueue = [];
this.currentTurnIdx = 0;
this.isProcessing = false;
}
start() {
const zone = ZONES[this.game.player.zone];
const enemyCount = 1 + Math.floor(Math.random() * 2);
this.enemies = [];
for (let i = 0; i < enemyCount; i++) {
const species = ESSENCE_DATA[Math.floor(Math.random() * 18)];
const lvl = 5 + (this.game.player.zone * 5) + Math.floor(Math.random() * 3);
this.enemies.push(this.game.createEssence(species, lvl));
}
this.activePlayerIdx = 0;
this.setupTurnOrder();
this.log(`A wild ${this.enemies[0].name} appeared!`);
}
setupTurnOrder() {
this.turnQueue = [];
this.game.player.party.forEach((p, idx) => this.turnQueue.push({ type: 'player', id: idx, spd: p.spd }));
this.enemies.forEach((e, idx) => this.turnQueue.push({ type: 'enemy', id: idx, spd: e.spd }));
this.turnQueue.sort((a, b) => b.spd - a.spd);
this.currentTurnIdx = 0;
this.processTurn();
}
processTurn() {
this.isProcessing = false;
const turn = this.turnQueue[this.currentTurnIdx];
if (turn.type === 'enemy') {
setTimeout(() => this.enemyTurn(turn.id), 1000);
} else {
this.game.state = 'BATTLE';
this.game.battle.activeTurn = turn.id;
this.log(`Turn: ${this.game.player.party[turn.id].name}`);
}
}
get activeTurn() { return this.game.battle.activeTurn; }
enemyTurn(enemyIdx) {
const enemy = this.enemies[enemyIdx];
const targetIdx = Math.floor(Math.random() * this.game.player.party.length);
const target = this.game.player.party[targetIdx];
const moveIdx = Math.floor(Math.random() * 4);
const moveName = enemy.learns[moveIdx];
const move = ABILITIES[Object.keys(ABILITIES)[moveName] || 'Singe']; // Simplified
this.log(`${enemy.name} uses ${moveName}!`);
this.resolveAttack(enemy, target, moveName);
}
handleAction(action) {
if (this.isProcessing) return;
if (action === 'attack') {
const p = this.game.player.party[this.activeTurn];
const moves = p.learns.map(id => Object.keys(ABILITIES)[id]);
this.showAbilityMenu(moves);
} else if (action === 'item') {
this.showItemMenu();
} else if (action === 'switch') {
this.showSwitchMenu();
}
}
showAbilityMenu(moves) {
const menu = document.getElementById('ability-selection');
const actions = document.getElementById('battle-actions');
actions.style.display = 'none';
menu.style.display = 'grid';
menu.innerHTML = '';
moves.forEach(m => {
const btn = document.createElement('button');
btn.className = 'btn';
btn.innerText = m;
btn.onclick = () => {
const target = this.enemies[0]; // Simplified to first enemy
this.resolveAttack(this.game.player.party[this.activeTurn], target, m);
menu.style.display = 'none';
actions.style.display = 'grid';
};
menu.appendChild(btn);
});
}
showItemMenu() {
const menu = document.getElementById('ability-selection');
const actions = document.getElementById('battle-actions');
actions.style.display = 'none';
menu.style.display = 'grid';
menu.innerHTML = '';
Object.keys(this.game.player.inventory).forEach(key => {
const qty = this.game.player.inventory[key];
if (qty <= 0) return;
const item = ITEMS[key];
const btn = document.createElement('button');
btn.className = 'btn';
btn.innerText = `${item.name} (${qty})`;
btn.onclick = () => {
this.useItem(key);
menu.style.display = 'none';
actions.style.display = 'grid';
};
menu.appendChild(btn);
});
}
useItem(key) {
const item = ITEMS[key];
if (item.effect === 'capture') {
const target = this.enemies[0];
const chance = (1 - target.curHp/target.maxHp) * item.quality;
if (Math.random() < chance) {
this.log(`Success! ${target.name} was bound!`);
this.game.player.party.push(target);
this.game.player.collected.push(target.id);
this.game.player.inventory[key]--;
this.checkVictory();
} else {
this.log(`The ${target.name} broke free!`);
this.game.player.inventory[key]--;
}
} else if (item.effect === 'heal') {
const p = this.game.player.party[this.activeTurn];
p.curHp = Math.min(p.maxHp, p.curHp + item.value);
this.log(`${p.name} healed!`);
this.game.player.inventory[key]--;
}
}
showSwitchMenu() {
const menu = document.getElementById('ability-selection');
const actions = document.getElementById('battle-actions');
actions.style.display = 'none';
menu.style.display = 'grid';
menu.innerHTML = '';
this.game.player.party.forEach((p, idx) => {
const btn = document.createElement('button');
btn.className = 'btn';
btn.innerText = `${p.name} (HP: ${p.curHp})`;
btn.onclick = () => {
this.activeTurn = idx;
this.log(`Switched to ${p.name}!`);
menu.style.display = 'none';
actions.style.display = 'grid';
};
menu.appendChild(btn);
});
}
resolveAttack(attacker, defender, moveId) {
this.isProcessing = true;
const move = ABILITIES[moveId] || ABILITIES['Singe'];
// Damage Formula: (0.4 * Level * ATK / DEF * Multiplier) + random
const mult = TYPE_CHART[attacker.type][defender.type] || 1.0;
const stab = (move.type === attacker.type) ? 1.25 : 1.0;
let damage = Math.floor((0.4 * attacker.level * attacker.atk / defender.def * mult * stab) + Math.random() * 5);
if (Math.random() < 0.1) {
damage *= 2;
this.log("CRITICAL HIT!");
}
defender.curHp -= damage;
this.log(`${attacker.name} deals ${damage} damage!`);
this.game.audio.beep(200, 0.1);
if (defender.curHp <= 0) {
defender.curHp = 0;
this.log(`${defender.name} fainted!`);
}
setTimeout(() => {
this.nextTurn();
}, 1500);
}
nextTurn() {
this.currentTurnIdx++;
if (this.currentTurnIdx >= this.turnQueue.length) {
this.currentTurnIdx = 0;
this.setupTurnOrder();
} else {
this.processTurn();
}
}
checkVictory() {
if (this.enemies.every(e => e.curHp <= 0)) {
this.log("Battle won!");
setTimeout(() => this.game.endBattle(true), 2000);
}
}
log(msg) {
document.getElementById('battle-msg').innerText = msg;
}
draw() {
const ctx = this.game.ctx;
// Background
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, this.game.width, this.game.height);
// Simple Render of Entities
this.game.player.party.forEach((p, i) => {
ctx.fillStyle = TYPES[p.type].color;
ctx.beginPath();
ctx.arc(200, 400 - (i * 40), 20, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.fillText(`${p.name} L${p.level}`, 230, 405 - (i * 40));
// HP Bar
ctx.fillStyle = '#333';
ctx.fillRect(280, 400 - (i * 40), 100, 10);
ctx.fillStyle = 'lime';
ctx.fillRect(280, 400 - (i * 40), (p.curHp / p.maxHp) * 100, 10);
});
this.enemies.forEach((e, i) => {
ctx.fillStyle = TYPES[e.type].color;
ctx.beginPath();
ctx.arc(600, 200 + (i * 60), 30, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.fillText(`${e.name} L${e.level}`, 530, 205 + (i * 60));
ctx.fillStyle = '#333';
ctx.fillRect(530, 210 + (i * 60), 100, 10);
ctx.fillStyle = 'lime';
ctx.fillRect(530, 210 + (i * 60), (e.curHp / e.maxHp) * 100, 10);
});
}
}
// Instantiate game and attach battle system
const game = new Game();
game.battle = new BattleSystem(game);
</script>
</body>
</html>
<!-- agent-meta {"model":"gemma-4-31b","provider":"cerebras","persona":"basic","sessionId":"3aa84e90-4d57-4bed-8d2e-c0da2a80a4a5","tokensIn":1337,"tokensOut":12634,"tokensTotal":13971,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-14T21:56:06.212Z"} -->