Creature Collection
Gemma 4 31b · composer

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
- 3 / 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 29,044 bytes · SHA-256 84f19463b464
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aetheria: The Resonance Binding</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=JetBrains+Mono:wght@500&display=swap">
<style>
/* ── css: layout ── */
:root {
--primary: #a29bfe;
--secondary: #6c5ce7;
--accent: #fd79a8;
--bg-dark: #2d3436;
--glass: rgba(255, 255, 255, 0.1);
--glass-border: rgba(255, 255, 255, 0.2);
--text-main: #dfe6e9;
--text-dim: #b2bec3;
--font-main: 'Inter', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; user-select: none; }
body {
background: #000;
color: var(--text-main);
font-family: var(--font-main);
overflow: hidden;
width: 100vw;
height: 100vh;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
#game-canvas {
position: absolute;
top: 0;
left: 0;
z-index: 1;
image-rendering: pixelated;
width: 100%;
height: 100%;
}
#ui-layer {
position: relative;
z-index: 10;
width: 100%;
height: 100%;
pointer-events: none;
}
.screen {
position: absolute;
inset: 0;
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
pointer-events: all;
background: transparent;
transition: opacity 0.5s ease;
}
.screen.active { display: flex; }
/* Title Screen */
.title-card {
text-align: center;
margin-bottom: 3rem;
animation: float 4s ease-in-out infinite;
}
.title-card h1 {
font-size: 5rem;
letter-spacing: 1rem;
color: #fff;
text-shadow: 0 0 20px var(--primary), 0 0 40px var(--secondary);
font-weight: 700;
}
.subtitle {
font-size: 1.2rem;
color: var(--text-dim);
letter-spacing: 0.3rem;
text-transform: uppercase;
}
.menu-actions {
display: flex;
flex-direction: column;
gap: 1rem;
width: 300px;
}
.menu-btn {
background: var(--glass);
border: 1px solid var(--glass-border);
color: #fff;
padding: 1rem;
font-family: var(--font-main);
font-size: 1.1rem;
cursor: pointer;
backdrop-filter: blur(10px);
transition: 0.3s;
border-radius: 4px;
}
.menu-btn:hover:not(:disabled) {
background: var(--secondary);
transform: scale(1.05);
}
.menu-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Dialogue */
.dialogue-box {
position: absolute;
bottom: 2rem;
width: 80%;
max-width: 800px;
background: rgba(0,0,0,0.8);
border: 2px solid var(--primary);
padding: 2rem;
border-radius: 8px;
min-height: 150px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.dialogue-text {
font-size: 1.2rem;
line-height: 1.6;
}
.dialogue-next {
text-align: right;
font-size: 0.9rem;
color: var(--primary);
animation: pulse 1.5s infinite;
}
/* HUD */
.hud {
position: absolute;
inset: 0;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 2rem;
}
.hud.hidden { display: none; }
.hud-top {
display: flex;
justify-content: space-between;
pointer-events: all;
}
.location-tag {
background: var(--glass);
padding: 0.5rem 1.5rem;
border-radius: 20px;
border: 1px solid var(--glass-border);
backdrop-filter: blur(5px);
font-weight: 700;
}
.hud-bottom {
display: flex;
justify-content: center;
gap: 1rem;
pointer-events: all;
}
.hud-icon {
width: 60px;
height: 60px;
border-radius: 50%;
background: var(--glass);
border: 1px solid var(--glass-border);
color: #fff;
font-size: 1.5rem;
cursor: pointer;
backdrop-filter: blur(10px);
transition: 0.2s;
}
.hud-icon:hover {
background: var(--secondary);
transform: translateY(-5px);
}
/* Battle UI */
.battle-arena {
position: relative;
width: 100%;
height: 60%;
display: flex;
justify-content: space-around;
align-items: center;
padding: 0 10%;
}
.battle-side {
position: relative;
width: 300px;
height: 300px;
}
.battle-controls {
position: absolute;
bottom: 0;
width: 100%;
height: 40%;
background: rgba(0,0,0,0.7);
border-top: 3px solid var(--primary);
display: flex;
padding: 2rem;
gap: 2rem;
}
.battle-menu {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
width: 400px;
}
.battle-nav {
background: var(--glass);
border: 1px solid var(--glass-border);
color: #fff;
padding: 1.5rem;
font-size: 1.2rem;
cursor: pointer;
border-radius: 4px;
transition: 0.2s;
}
.battle-nav:hover {
background: var(--primary);
}
.move-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
width: 400px;
}
.move-btn {
background: var(--glass);
border: 1px solid var(--glass-border);
color: #fff;
padding: 1.5rem;
text-align: left;
cursor: pointer;
border-radius: 4px;
transition: 0.2s;
display: flex;
flex-direction: column;
}
.move-btn:hover {
background: var(--secondary);
}
.move-btn .cost {
font-size: 0.8rem;
opacity: 0.7;
text-align: right;
}
.battle-log {
flex: 1;
background: rgba(0,0,0,0.4);
border: 1px solid var(--glass-border);
padding: 1rem;
overflow-y: auto;
font-family: var(--font-mono);
font-size: 1rem;
border-radius: 8px;
}
/* Unit Bars */
.unit-card {
position: absolute;
width: 200px;
background: var(--glass);
border: 1px solid var(--glass-border);
padding: 10px;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.enemy-card { top: 0; right: 0; }
.player-card { bottom: 0; left: 0; }
.unit-name {
font-weight: bold;
margin-bottom: 5px;
display: flex;
justify-content: space-between;
}
.hp-bar-bg {
background: #444;
height: 10px;
border-radius: 5px;
overflow: hidden;
}
.hp-bar-fill {
background: linear-gradient(90deg, #ff7675, #fdcb6e);
height: 100%;
transition: width 0.4s ease;
}
/* Modals */
.modal-overlay {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
pointer-events: all;
}
.modal-content {
background: rgba(45, 52, 54, 0.9);
border: 2px solid var(--primary);
width: 80%;
max-width: 600px;
max-height: 80vh;
border-radius: 12px;
padding: 2rem;
position: relative;
overflow-y: auto;
backdrop-filter: blur(20px);
}
.modal-close {
position: sticky;
bottom: 0;
width: 100%;
padding: 1rem;
margin-top: 1rem;
background: var(--secondary);
border: none;
color: #fff;
cursor: pointer;
border-radius: 4px;
}
/* Capture Game */
.capture-ring-container {
position: relative;
width: 300px;
height: 300px;
display: flex;
justify-content: center;
align-items: center;
}
.capture-ring {
position: absolute;
border: 4px solid #fff;
border-radius: 50%;
pointer-events: none;
}
.capture-target {
width: 50px;
height: 50px;
background: var(--accent);
border-radius: 50%;
box-shadow: 0 0 20px var(--accent);
}
.capture-hint {
position: absolute;
bottom: 20%;
font-size: 1.2rem;
color: #fff;
}
/* Audio Toggle */
.audio-toggle {
position: absolute;
top: 2rem;
right: 2rem;
z-index: 1000;
background: var(--glass);
border: 1px solid var(--glass-border);
padding: 0.5rem 1rem;
border-radius: 20px;
pointer-events: all;
cursor: pointer;
backdrop-filter: blur(10px);
}
/* Animations */
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.hidden { display: none !important; }
</style>
</head>
<body>
<div id="game-container">
<div id="ui-layer">
<!-- Main Menu -->
<div id="screen-menu" class="screen active">
<div class="title-card">
<h1>AETHERIA</h1>
<p class="subtitle">The Resonance Binding</p>
</div>
<div class="menu-actions">
<button id="btn-new-game" class="menu-btn">Begin New Binding</button>
<button id="btn-continue" class="menu-btn" disabled="">Continue Journey</button>
<button id="btn-options" class="menu-btn">Resonance Settings</button>
</div>
</div>
<!-- Dialogue/Cutscene Overlay -->
<div id="screen-dialogue" class="screen">
<div class="dialogue-box">
<p id="dialogue-text"></p>
<span class="dialogue-next">Press SPACE or Tap to continue →</span>
</div>
</div>
<!-- Overworld HUD -->
<div id="hud-overworld" class="hud hidden">
<div class="hud-top">
<div class="location-tag" id="location-name">The Whispering Hollows</div>
<div class="game-time" id="game-clock">00:00</div>
</div>
<div class="hud-bottom">
<button id="btn-open-bag" class="hud-icon">🎒</button>
<button id="btn-open-team" class="hud-icon">👥</button>
<button id="btn-open-index" class="hud-icon">📖</button>
<button id="btn-open-map" class="hud-icon">🗺️</button>
</div>
</div>
<!-- Battle UI -->
<div id="screen-battle" class="screen">
<div class="battle-arena">
<div id="enemy-side" class="battle-side"></div>
<div id="player-side" class="battle-side"></div>
</div>
<div class="battle-controls">
<div class="battle-menu">
<button id="battle-btn-attack" class="battle-nav">Resonate</button>
<button id="battle-btn-item" class="battle-nav">Essence</button>
<button id="battle-btn-switch" class="battle-nav">Shift</button>
<button id="battle-btn-run" class="battle-nav">Recede</button>
</div>
<div id="move-selection" class="move-grid hidden"></div>
<div id="battle-log" class="battle-log"></div>
</div>
</div>
<!-- Modal Menus (Glassmorphism) -->
<div id="modal-overlay" class="modal-overlay hidden">
<div class="modal-content">
<div id="modal-body"></div>
<button id="modal-close" class="modal-close">Close</button>
</div>
</div>
<!-- Capture Mini-game -->
<div id="screen-capture" class="screen">
<div class="capture-ring-container">
<div id="capture-ring-outer" class="capture-ring"></div>
<div id="capture-ring-inner" class="capture-ring"></div>
<div class="capture-target"></div>
</div>
<p class="capture-hint">Tap when the rings align!</p>
</div>
<!-- Audio Toggle -->
<div id="audio-toggle" class="audio-toggle">
<span id="audio-status">🔊</span>
</div>
</div>
<canvas id="game-canvas"></canvas>
</div>
<script>
'use strict';
// ── const: GAME_CONFIG ──
const GAME_CONFIG = {
TYPES: {
'Solar': { weakness: 'Lunar', strength: 'Flora', resist: 'Wind', color: '#f1c40f' },
'Lunar': { weakness: 'Flora', strength: 'Solar', resist: 'Wind', color: '#9b59b6' },
'Flora': { weakness: 'Solar', strength: 'Lunar', resist: 'Void', color: '#2ecc71' },
'Wind': { weakness: 'Void', strength: 'Solar', resist: 'Flora', color: '#3498db' },
'Void': { weakness: 'Shatter', strength: 'Wind', resist: 'Solar', color: '#2c3e50' },
'Shatter': { weakness: 'Aether', strength: 'Void', resist: 'Lunar', color: '#e74c3c' },
'Aether': { weakness: 'Shatter', strength: 'None', resist: 'All', color: '#ecf0f1' }
},
ZONES: [
{ id: 0, name: 'Azure Glade', color: 'linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d)', encounterRate: 0.08, boss: 'The Gilded Talon' },
{ id: 1, name: 'Echoing Spires', color: 'linear-gradient(135deg, #2c3e50, #4ca1af)', encounterRate: 0.1, boss: 'The Silent Gale' },
{ id: 2, name: 'Crimson Basin', color: 'linear-gradient(135deg, #870000, #190a05)', encounterRate: 0.12, boss: 'The Molten Core' },
{ id: 3, name: 'Obsidian Veins', color: 'linear-gradient(135deg, #000000, #434343)', encounterRate: 0.15, boss: 'The Void Weaver' },
{ id: 4, name: 'Luminous Reach', color: 'linear-gradient(135deg, #f9d423, #ff4e50)', encounterRate: 0.18, boss: 'The Prism Sentinel' },
{ id: 5, name: 'Zenith Apex', color: 'linear-gradient(135deg, #000428, #004e92)', encounterRate: 0.2, boss: 'The Eternal Harmony' }
]
};
// ── class: GameState ──
class GameState {
constructor() {
this.player = {
pos: { x: 5, y: 5 },
zone: 0,
party: [],
inventory: [],
index: [],
flags: {},
saveSlot: 0
};
this.currentScreen = 'menu';
this.battle = null;
this.dialogueQueue = [];
this.lastShrine = { x: 5, y: 5, zone: 0 };
this.audioEnabled = true;
}
save() {
localStorage.setItem('aetheria_save', JSON.stringify(this));
}
load() {
const saved = localStorage.getItem('aetheria_save');
if (saved) {
const data = JSON.parse(saved);
Object.assign(this, data);
return true;
}
return false;
}
}
// ── class: GameRenderer ──
class GameRenderer {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.resize();
window.addEventListener('resize', () => this.resize());
}
resize() {
this.canvas.width = window.innerWidth * window.devicePixelRatio;
this.canvas.height = window.innerHeight * window.devicePixelRatio;
this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
drawOverworld(state) {
const { x, y } = state.player.pos;
const zone = GAME_CONFIG.ZONES[state.player.zone];
// Background Gradient
const grad = this.ctx.createLinearGradient(0, 0, 0, window.innerHeight);
// Since CSS handles the page backdrop, we might just draw a tile grid or particles
const tileSize = 64;
this.ctx.strokeStyle = 'rgba(255,255,255,0.1)';
this.ctx.beginPath();
for(let i=0; i<window.innerWidth; i+=tileSize) {
this.ctx.moveTo(i, 0);
this.ctx.lineTo(i, window.innerHeight);
}
for(let i=0; i<window.innerHeight; i+=tileSize) {
this.ctx.moveTo(0, i);
this.ctx.lineTo(window.innerWidth, i);
}
this.ctx.stroke();
// Player
this.ctx.fillStyle = '#fff';
this.ctx.shadowBlur = 15;
this.ctx.shadowColor = varColor('--primary');
this.ctx.beginPath();
this.ctx.arc(
(window.innerWidth/2),
(window.innerHeight/2),
12, 0, Math.PI*2
);
this.ctx.fill();
this.ctx.shadowBlur = 0;
}
drawBattle(state) {
// Battle rendering is largely DOM-based for these specs
}
}
// ── function: varColor ──
function varColor(varName) {
return getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
}
// ── class: Creature ──
class Creature {
constructor(id, name, types, stats, abilities, lore) {
this.id = id;
this.name = name;
this.types = types;
this.baseStats = stats;
this.abilities = abilities;
this.lore = lore;
}
createInstance(level = 1) {
return {
name: this.name,
types: this.types,
level: level,
hp: this.calculateStat('hp', level),
maxHp: this.calculateStat('hp', level),
atk: this.calculateStat('atk', level),
def: this.calculateStat('def', level),
spd: this.calculateStat('spd', level),
abilities: this.abilities.map(a => ({ ...a })),
status: null,
exp: 0,
nextLevel: 100 * level
};
}
calculateStat(stat, level) {
const base = this.baseStats[stat];
if (stat === 'hp') return Math.floor(base * (level / 5 + 2));
return Math.floor(base + (level * 1.5));
}
}
// ── const: MOVE_DB ──
const MOVE_DB = {
'Solar Flash': { power: 40, acc: 95, type: 'Solar', cost: 10, effect: 'none' },
'Lunar Pulse': { power: 40, acc: 95, type: 'Lunar', cost: 10, effect: 'none' },
'Vine Whip': { power: 40, acc: 90, type: 'Flora', cost: 10, effect: 'none' },
'Zephyr Gust': { power: 40, acc: 90, type: 'Wind', cost: 10, effect: 'none' },
'Void Maw': { power: 60, acc: 80, type: 'Void', cost: 20, effect: 'none' },
'Crystal Edge': { power: 60, acc: 85, type: 'Shatter', cost: 20, effect: 'none' },
'Aether Breath': { power: 70, acc: 90, type: 'Aether', cost: 30, effect: 'none' },
'Nova Burst': { power: 110, acc: 75, type: 'Solar', cost: 40, effect: 'none' },
'Eclipse Veil': { power: 110, acc: 75, type: 'Lunar', cost: 40, effect: 'none' },
'Root Bind': { power: 40, acc: 80, type: 'Flora', cost: 15, effect: 'frost' },
'Shatter Step': { power: 50, acc: 100, type: 'Shatter', cost: 15, effect: 'priority' },
'Cosmic Heal': { power: 0, acc: 100, type: 'Aether', cost: 20, effect: 'heal' },
'Void Pulse': { power: 80, acc: 90, type: 'Void', cost: 30, effect: 'shock' },
'Gale Force': { power: 90, acc: 85, type: 'Wind', cost: 30, effect: 'none' },
'Sun Beam': { power: 120, acc: 70, type: 'Solar', cost: 50, effect: 'burn' },
'Moon Sliver': { power: 120, acc: 70, type: 'Lunar', cost: 50, effect: 'none' },
};
// ── const: CREATURE_DB ──
const CREATURE_DB = {
'Solaris': new Creature('Solaris', 'Solaris', ['Solar'], { hp: 50, atk: 12, def: 10, spd: 11 }, ['Solar Flash', 'Shatter Step'], 'A glimmering orb of perpetual dawn.'),
'Lunaris': new Creature('Lunaris', 'Lunaris', ['Lunar'], { hp: 55, atk: 11, def: 11, spd: 10 }, ['Lunar Pulse', 'Shatter Step'], 'A silent mirror of the night sky.'),
'Floris': new Creature('Floris', 'Floris', ['Flora'], { hp: 60, atk: 10, def: 12, spd: 9 }, ['Vine Whip', 'Shatter Step'], 'Rooted in the essence of the first forest.'),
'Ventus': new Creature('Ventus', 'Ventus', ['Wind'], { hp: 45, atk: 13, def: 8, spd: 15 }, ['Zephyr Gust', 'Shatter Step'], 'A whirlwind given consciousness.'),
'Voidon': new Creature('Voidon', 'Voidon', ['Void'], { hp: 70, atk: 14, def: 12, spd: 7 }, ['Void Maw', 'Shatter Step'], 'An entity from the spaces between stars.'),
'Krystos': new Creature('Krystos', 'Krystos', ['Shatter'], { hp: 65, atk: 15, def: 15, spd: 6 }, ['Crystal Edge', 'Shatter Step'], 'Living geometry of hardened silence.'),
'Aethon': new Creature('Aethon', 'Aethon', ['Aether'], { hp: 100, atk: 20, def: 20, spd: 20 }, ['Aether Breath', 'Cosmic Heal'], 'The primordial breath of the world.'),
};
// ── const: TRANSFORMATIONS ──
const TRANSFORMATIONS = {
'Solaris' : { level: 20, item: 'Solar Catalyst', result: 'Super Solaris' },
'Lunaris' : { level: 20, item: 'Lunar Catalyst', result: 'Super Lunaris' },
'Floris' : { level: 20, item: 'Flora Catalyst', result: 'Super Floris' },
};
// ── class: BattleEngine ──
class BattleEngine {
constructor(playerParty, enemyParty, onLog, onUpdate) {
this.playerParty = playerParty;
this.enemyParty = enemyParty;
this.onLog = onLog;
this.onUpdate = onUpdate;
this.turnQueue = [];
this.currentTurnUnit = null;
this.phase = 'init';
}
init() {
this.refreshTurnQueue();
this.nextTurn();
}
refreshTurnQueue() {
const all = [
...this.playerParty.map((u, i) => ({ unit: u, side: 'player', index: i })),
...this.enemyParty.map((u, i) => ({ unit: u, side: 'enemy', index: i }))
].filter(entry => entry.unit.hp > 0);
this.turnQueue = all.sort((a, b) => b.unit.spd - a.unit.spd);
}
nextTurn() {
if (this.turnQueue.length === 0) {
this.refreshTurnQueue();
if (this.turnQueue.length === 0) return;
}
this.currentTurnUnit = this.turnQueue.shift();
if (this.currentTurnUnit.unit.hp <= 0) {
this.nextTurn();
return;
}
if (this.currentTurnUnit.side === 'player') {
this.phase = 'player_turn';
this.onUpdate();
} else {
this.phase = 'enemy_turn';
this.processEnemyAI();
}
}
processEnemyAI() {
const target = this.playerParty.find(u => u.hp > 0);
if (!target) return;
const moves = this.currentTurnUnit.unit.abilities;
const moveName = moves[Math.floor(Math.random() * moves.length)];
this.executeMove(this.currentTurnUnit, target, moveName);
}
executeMove(attackerEntry, targetUnit, moveName) {
const move = MOVE_DB[moveName];
if (!move) return;
const attacker = attackerEntry.unit;
this.onLog(`${attacker.name} used ${moveName}!`);
if (Math.random() * 100 > move.acc) {
this.onLog(`It missed!`);
this.resolveTurn();
return;
}
let multiplier = 1;
const typeChart = GAME_CONFIG.TYPES;
const aType = attacker.types[0];
const tType = targetUnit.types[0];
if (typeChart[aType] && typeChart[aType].strength === tType) multiplier *= 2;
if (typeChart[aType] && typeChart[aType].weakness === tType) multiplier *= 0.5;
if (attacker.types.includes(move.type)) multiplier *= 1.25;
const damage = Math.floor((0.4 * attacker.level * attacker.atk / targetUnit.def * multiplier) + (Math.random() * 5));
targetUnit.hp = Math.max(0, targetUnit.hp - damage);
this.onLog(`${targetUnit.name} took ${damage} damage!`);
this.onUpdate();
this.resolveTurn();
}
resolveTurn() {
setTimeout(() => this.nextTurn(), 1000);
}
}
// ── main ──
const state = new GameState();
const canvas = document.getElementById('game-canvas');
const renderer = new GameRenderer(canvas);
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
const target = document.getElementById('screen-' + id);
if (target) target.classList.add('active');
state.currentScreen = id;
}
function addDialogue(text) {
state.dialogueQueue.push(text);
if (state.currentScreen !== 'dialogue') {
showScreen('dialogue');
}
updateDialogue();
}
function updateDialogue() {
const el = document.getElementById('dialogue-text');
if (state.dialogueQueue.length > 0) {
el.innerText = state.dialogueQueue[0];
} else {
if (state.currentScreen === 'dialogue') {
showScreen('menu');
}
}
}
function handleDialogueNext() {
if (state.currentScreen === 'dialogue') {
const current = state.dialogueQueue[0];
state.dialogueQueue.shift();
if (current === 'END_GAME_START') {
showScreen('overworld');
document.getElementById('hud-overworld').classList.remove('hidden');
document.body.style.background = GAME_CONFIG.ZONES[0].color;
} else {
updateDialogue();
}
}
}
function loop() {
renderer.clear();
if (state.currentScreen === 'overworld') {
renderer.drawOverworld(state);
}
requestAnimationFrame(loop);
}
window.addEventListener('keydown', (e) => {
if (state.currentScreen === 'dialogue') {
if (e.code === 'Space' || e.code === 'Enter') handleDialogueNext();
return;
}
if (state.currentScreen !== 'overworld') return;
let dx = 0, dy = 0;
if (e.key === 'w' || e.key === 'ArrowUp') dy = -1;
if (e.key === 's' || e.key === 'ArrowDown') dy = 1;
if (e.key === 'a' || e.key === 'ArrowLeft') dx = -1;
if (e.key === 'd' || e.key === 'ArrowRight') dx = 1;
if (dx !== 0 || dy !== 0) {
state.player.pos.x += dx;
state.player.pos.y += dy;
const zone = GAME_CONFIG.ZONES[state.player.zone];
if (Math.random() < zone.encounterRate) {
startBattle();
}
}
});
function startBattle() {
showScreen('battle');
const keys = Object.keys(CREATURE_DB);
const enemyC = CREATURE_DB[keys[Math.floor(Math.random() * keys.length)]].createInstance(Math.floor(Math.random() * 5) + 1);
const enemySide = document.getElementById('enemy-side');
enemySide.innerHTML = `
<div class="unit-card enemy-card">
<div class="unit-name"><span>${enemyC.name}</span><span>Lv.${enemyC.level}</span></div>
<div class="hp-bar-bg"><div id="enemy-hp-fill" class="hp-bar-fill" style="width: 100%"></div></div>
</div>
`;
const playerSide = document.getElementById('player-side');
const pC = state.player.party[0] || CREATURE_DB['Solaris'].createInstance(5);
playerSide.innerHTML = `
<div class="unit-card player-card">
<div class="unit-name"><span>${pC.name}</span><span>Lv.${pC.level}</span></div>
<div class="hp-bar-bg"><div id="player-hp-fill" class="hp-bar-fill" style="width: 100%"></div></div>
</div>
`;
const engine = new BattleEngine(
[pC],
[enemyC],
(msg) => {
const log = document.getElementById('battle-log');
log.innerHTML += `<div>${msg}</div>`;
log.scrollTop = log.scrollHeight;
},
() => {
const eFill = document.getElementById('enemy-hp-fill');
const pFill = document.getElementById('player-hp-fill');
if(eFill) eFill.style.width = (enemyC.hp / enemyC.maxHp * 100) + '%';
if(pFill) pFill.style.width = (pC.hp / pC.maxHp * 100) + '%';
if (enemyC.hp <= 0) {
setTimeout(() => {
alert('Victory!');
showScreen('overworld');
}, 1000);
}
if (pC.hp <= 0) {
setTimeout(() => {
alert('You were defeated!');
showScreen('overworld');
pC.hp = pC.maxHp; // Simple heal to avoid lock
}, 1000);
}
}
);
engine.init();
document.getElementById('battle-btn-attack').onclick = () => {
const moveSelection = document.getElementById('move-selection');
moveSelection.classList.remove('hidden');
moveSelection.innerHTML = '';
pC.abilities.forEach(moveName => {
const btn = document.createElement('button');
btn.className = 'move-btn';
btn.innerHTML = `<span>${moveName}</span><span class="cost">${MOVE_DB[moveName]?.cost || 0} E</span>`;
btn.onclick = () => {
engine.executeMove({ unit: pC, side: 'player' }, enemyC, moveName);
moveSelection.classList.add('hidden');
};
moveSelection.appendChild(btn);
});
};
document.getElementById('battle-btn-run').onclick = () => {
showScreen('overworld');
};
}
document.getElementById('btn-new-game').onclick = () => {
state.player.party = [CREATURE_DB['Solaris'].createInstance(5)];
addDialogue("Welcome to Aetheria...");
addDialogue("You have awakened as a Resonator, one who can bind with the spirits of this world.");
addDialogue("Use WASD to explore the glades and find creatures to bind.");
addDialogue("END_GAME_START");
};
document.getElementById('audio-toggle').onclick = () => {
state.audioEnabled = !state.audioEnabled;
document.getElementById('audio-status').innerText = state.audioEnabled ? '🔊' : '🔇';
};
loop();
</script>
</body>
</html>
<!-- agent-meta {"model":"gemma-4-31b","provider":"cerebras","persona":"composer","sessionId":"59031951-111a-4084-95d0-1210ba9453c0","tokensIn":58822,"tokensOut":14060,"tokensTotal":72882,"turns":4,"toolCalls":15,"failedToolCalls":2,"timestamp":"2026-07-14T16:14:07.861Z"} -->