← Creature Collection results

Creature Collection

Gemma 4 26b a4b qat · composer

Initial view of Creature Collection

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
3 / 5
Prompt spirit
3 / 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 35,912 bytes · SHA-256 7d67f21b06e3
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Creature Collection</title>
<style>
/* ── css: undefined ── */
:root {
    --bg-color: #1a1a1a;
    --panel-bg: rgba(255, 255, 255, 0.1);
    --panel-border: rgba(255, 255, 255, 0.2);
    --text-color: #e0e0e0;
    --accent-color: #00d4ff;
}

body {
    margin: 0;
    padding: 0;
    background-color: var(--bg-color);
    color: var(--text-color);
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    user-select: none;
}

#game-container {
    position: relative;
    width: 800px;
    height: 480px;
    border: 4px solid #333;
    overflow: hidden;
    box-shadow: 0 0 50px rgba(0,0,0,0.5);
}

#game-canvas {
    width: 100%;
    height: 100%;
    display: block;
    background: #000;
}

/* HUD */
#hud {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 60px;
    pointer-events: none;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    padding: 10px;
    box-sizing: border-box;
    z-index: 10;
}

#party-list {
    display: flex;
    gap: 10px;
}

.party-unit {
    background: var(--panel-bg);
    backdrop-filter: blur(5px);
    padding: 5px 10px;
    border: 1px solid var(--panel-border);
    border-radius: 5px;
    min-width: 120px;
}

.party-unit span {
    display: block;
    font-size: 12px;
    font-weight: bold;
    margin-bottom: 4px;
    text-transform: uppercase;
}

.hp-container {
    width: 100%;
    height: 8px;
    background: #333;
    border-radius: 4px;
    overflow: hidden;
}

.hp-bar-fill {
    height: 100%;
    transition: width 0.3s ease;
}

#controls-info {
    background: var(--panel-bg);
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 11px;
}

/* Overlays */
.overlay-screen {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 100;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.hidden {
    display: none !important;
}

.overlay-screen.visible {
    display: flex !important;
}

/* Title Screen */
#title-screen {
    background: radial-gradient(circle, #222, #000);
}

.title-glow {
    text-align: center;
    font-size: 56px;
    color: #fff;
    text-shadow: 0 0 20px var(--accent-color);
    margin-bottom: 40px;
}

.title-buttons {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.title-buttons button {
    padding: 12px 40px;
    font-size: 20px;
    background: transparent;
    color: #fff;
    border: 2px solid #555;
    cursor: pointer;
    transition: all 0.3s;
}

.title-buttons button:hover {
    border-color: var(--accent-color);
    box-shadow: 0 0 15px var(--accent-color);
    background: rgba(0, 212, 255, 0.1);
}

/* Battle UI */
#battle-overlay {
    background: #000;
}

.battle-scene {
    display: flex;
    width: 100%;
    height: 60%;
    position: relative;
}

.enemy-area, .player-area {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    align-items: center;
    padding: 20px;
}

.enemy-area {
    align-items: flex-end;
}

.player-area {
    align-items: flex-start;
}

.hp-container {
    width: 120px;
    height: 12px;
}

.hp-bar {
    height: 100%;
    width: 100%;
    background: #0f0;
    transition: width 0.5s;
}

#battle-menu {
    width: 100%;
    height: 40%;
    background: rgba(20, 20, 20, 0.9);
    border-top: 3px solid var(--accent-color);
    display: flex;
}

#battle-commands {
    width: 50%;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 5px;
    padding: 10px;
}

#battle-commands button {
    background: #333;
    color: white;
    border: none;
    border-bottom: 3px solid #555;
    cursor: pointer;
    font-weight: bold;
}

#battle-commands button:active {
    border-bottom: none;
    margin-top: 3px;
}

#battle-log {
    width: 50%;
    background: rgba(0,0,0,0.5);
    padding: 10px;
    font-size: 13px;
    overflow-y: auto;
    color: #aaa;
}

/* Dialog */
#dialog {
    background: rgba(0,0,0,0.7);
    pointer-events: none;
}

#dialog-box {
    width: 80%;
    height: 100px;
    background: rgba(40, 40, 60, 0.9);
    border: 3px solid var(--accent-color);
    padding: 20px;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

#dialog-text {
    font-size: 20px;
}

#dialog-close {
    background: var(--accent-color);
    border: none;
    padding: 5px 15px;
    cursor: pointer;
}

/* Menu Overlay */
#overlay {
    background: rgba(0,0,0,0.8);
}

#menu-content {
    background: #222;
    padding: 20px;
    border-radius: 10px;
    width: 300px;
    text-align: center;
}

#menu-items {
    margin-top: 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

#menu-items button, #menu-close {
    padding: 8px;
    background: #444;
    color: white;
    border: none;
    cursor: pointer;
}
`,name:
</style>
</head>
<body>
<div id="game-container">
    <canvas id="game-canvas"></canvas>
    <div id="hud">
        <div id="party-list"></div>
        <div id="controls-info">WASD/Arrows to move | M for Menu</div>
    </div>
    <div id="battle-overlay" class="overlay-screen hidden">
        <div class="battle-scene">
            <div class="enemy-area">
                <div id="enemy-info">
                    <div id="enemy-name">Enemy</div>
                    <div class="hp-container"><div id="enemy-hp-bar" class="hp-bar"></div></div>
                </div>
                <div id="enemy-sprite"></div>
            </div>
            <div class="player-area">
                <div id="player-info">
                    <div id="player-name">Player</div>
                    <div class="hp-container"><div id="player-hp-bar" class="hp-bar"></div></div>
                </div>
                <div id="player-sprite"></div>
            </div>
        </div>
        <div id="battle-menu">
            <div id="battle-commands">
                <button id="cmd-attack">ATTACK</button>
                <button id="cmd-skill">SKILL</button>
                <button id="cmd-switch">SWITCH</button>
                <button id="cmd-item">ITEM</button>
            </div>
            <div id="battle-log"></div>
        </div>
    </div>
    <div id="overlay" class="overlay-screen hidden">
        <div id="menu-content">
            <h2 id="menu-title">Menu</h2>
            <div id="menu-items"></div>
            <button id="menu-close">Close</button>
        </div>
    </div>
    <div id="dialog" class="overlay-screen hidden">
        <div id="dialog-box">
            <span id="dialog-text"></span>
            <button id="dialog-close">Next</button>
        </div>
    </div>
    <div id="title-screen" class="overlay-screen">
        <h1 class="title-glow">ESSENTIA:<br>PRISMATIC BONDS</h1>
        <div class="title-buttons">
            <button id="btn-new-game">NEW BINDING</button>
            <button id="btn-continue">CONTINUE</button>
        </div>
    </div>
</div>
<div id="app"></div>
<script>
'use strict';

// ── const: TYPES ──
/**
 * ESSENTIA: PRISMATIC BONDS
 * A single-file creature collection and adventure game.
 */

const TYPES = {
    CINDER: { name: 'Cinder', color: '#FF4500', beats: ['UMBRA'], weakTo: ['AERO'] },
    UMBRA: { name: 'Umbra', color: '#4B0082', beats: ['BLOOM'], weakTo: ['CINDER', 'HYDRO'] },
    BLOOM: { name: 'Bloom', color: '#32CD32', beats: ['CINDER'], weakTo: ['AERO', 'UMBRA'] },
    AERO: { name: 'Aero', color: '#87CEEB', beats: ['CINDER', 'BLOOM'], weakTo: ['HYDRO'] },
    HYDRO: { name: 'Hydro', color: '#1E90FF', beats: ['AERO', 'UMBRA'], weakTo: ['CINDER'] },
    PRIME: { name: 'Prime', color: '#FFD700', beats: ['CINDER', 'UMBRA', 'BLOOM', 'AERO', 'HYDRO', 'NULL'], weakTo: ['PRIME'] },
    NULL: { name: 'Null', color: '#2F4F4F', beats: [], resists: ['CINDER', 'UMBRA', 'BLOOM', 'AERO', 'HYDRO', 'NULL'], weakTo: ['PRIME'] }
};

// ── const: TYPE_ID ──
// Refined logic based on exact prompt rules:
// A > B > C > A
// D > A & C
// E > D & B
// F beats all slightly
// G resists everything but is weak to F
// Mapping:
// A: Cinder, B: Umbra, C: Bloom, D: Aero, E: Hydro, F: Prime, G: Null

const TYPE_ID = {
    CINDER: 0,
    UMBRA: 1,
    BLOOM: 2,
    AERO: 3,
    HYDRO: 4,
    PRIME: 5,
    NULL: 6
};

// ── const: TYPE_NAMES ──
const TYPE_NAMES = ['Cinder', 'Umbra', 'Bloom', 'Aero', 'Hydro', 'Prime', 'Null'];

// ── const: TYPE_COLORS ──
const TYPE_COLORS = ['#FF4500', '#4B0082', '#32CD32', '#87CEEB', '#1E90FF', '#FFD700', '#2F4F4F'];

// ── const: ABILITY_TYPES ──
const ABILITY_TYPES = {
    PHYSICAL: 'Physical',
    SPECIAL: 'Special',
    STATUS: 'Status'
};

// ── const: STATUS_EFFECTS ──
const STATUS_EFFECTS = {
    BURN: 'Burn',     // DoT + half ATK
    FROST: 'Frost',   // Halve SPD
    SHOCK: 'Shock',   // Skip turn chance
    REGEN: 'Regen',   // Heal per turn
    BARRIER: 'Barrier'// Deflect one hit
};

// ── const: ITEM_TYPES ──
const ITEM_TYPES = {
    RESTORE: 'Restoration',
    CATALYST: 'Catalyst',
    TOOL: 'Field Tool',
    COLLECTION: 'Collection Item'
};

// ── const: ITEMS ──
const ITEMS = {
    RECOVERY_PULSE: { name: 'Recovery Pulse', type: ITEM_TYPES.RESTORE, effect: 'heal' },
    ESSENCE_CORE: { name: 'Essence Core', type: ITEM_TYPES.CATALYST, effect: 'evolve' },
    PRISM_LENS: { name: 'Prism Lens', type: ITEM_TYPES.TOOL, effect: 'field' },
    STARDUST: { name: 'Stardust', type: ITEM_TYPES.COLLECTION, effect: 'capture' }
};

// ── const: GAME_CONSTANTS ──
const GAME_CONSTANTS = {
    MAX_PARTY_SIZE: 6,
    LEVEL_CAP: 50,
    TILE_SIZE: 48,
    VIEWPORT_W: 800,
    VIEWPORT_H: 480,
    SAVE_KEY: 'essentia_v1_save'
};

// ── class: Creature ──
// Refined Creature Class
class Creature {
    constructor(id, level = 1) {
        const data = CREATURE_DATA[id];
        if (!data) throw new Error(`Creature data for ${id} not found.`);
        
        this.id = id;
        this.level = level;
        this.name = data.name;
        this.type = data.type; // 0-6 index
        this.hpMax = this.calculateStat(data.baseHp, 'hp');
        this.hp = this.hpMax;
        this.atk = this.calculateStat(data.baseAtk, 'atk');
        this.def = this.calculateStat(data.baseDef, 'def');
        this.spd = this.calculateStat(data.baseSpd, 'spd');
        this.abilities = [...data.abilities];
        this.exp = 0;
        this.status = null;
        this.stats = { hp: data.baseHp, atk: data.baseAtk, def: data.baseDef, spd: data.baseSpd };
        this.evolutionId = data.evolutionId || null;
    }

    calculateStat(base, statName) {
        // Level 1-50 scaling
        const growth = 2 + (this.level * 0.5);
        return Math.floor(base + growth);
    }

    gainExp(amount) {
        this.exp += amount;
        const nextLevelExp = this.level * 100;
        if (this.exp >= nextLevelExp && this.level < GAME_CONSTANTS.LEVEL_CAP) {
            this.levelUp();
            return true;
        }
        return false;
    }

    levelUp() {
        this.level++;
        this.stats.hp += 5;
        this.stats.atk += 2;
        this.stats.def += 2;
        this.stats.spd += 2;
        this.updateDerivedStats();
    }

    updateDerivedStats() {
        this.hpMax = this.calculateStat(this.stats.hp, 'hp');
        this.atk = this.calculateStat(this.stats.atk, 'atk');
        this.def = this.calculateStat(this.stats.def, 'def');
        this.spd = this.calculateStat(this.stats.spd, 'spd');
    }

    takeDamage(amount) {
        const realDamage = Math.max(1, Math.floor(amount));
        this.hp = Math.max(0, this.hp - realDamage);
        return realDamage;
    }

    heal(amount) {
        this.hp = Math.min(this.hpMax, this.hp + amount);
    }
}

// ── const: CREATURE_DATA ──
const CREATURE_DATA = {
    // CINDER LINE
    'EMBERLIT': { name: 'Emberlit', type: 0, baseHp: 45, baseAtk: 12, baseDef: 8, baseSpd: 10, abilities: [{ name: 'Flare Kick', power: 40, type: 0, accuracy: 100, effect: 'burn' }, { name: 'Singe', power: 25, type: 0, accuracy: 100, effect: 'none' }], evolutionId: 'FLAMEWISP' },
    'FLAMEWISP': { name: 'Flamewisp', type: 0, baseHp: 60, baseAtk: 20, baseDef: 12, baseSpd: 18, abilities: [{ name: 'Scorching Burst', power: 65, type: 0, accuracy: 95, effect: 'burn' }, { name: 'Flame Tail', power: 40, type: 0, accuracy: 100, effect: 'none' }], evolutionId: 'PYROPHANTOM' },
    'PYROPHANTOM': { name: 'Pyrophantom', type: 0, baseHp: 85, baseAtk: 35, baseDef: 22, baseSpd: 35, abilities: [{ name: 'Hellfire', power: 110, type: 0, accuracy: 85, effect: 'burn' }, { name: 'Ash Cloud', power: 50, type: 0, accuracy: 90, effect: 'none' }], evolutionId: null },

    // UMBRA LINE
    'VOIDCLAW': { name: 'Voidclaw', type: 1, baseHp: 50, baseAtk: 15, baseDef: 10, baseSpd: 12, abilities: [{ name: 'Night Slash', power: 45, type: 1, accuracy: 100, effect: 'none' }, { name: 'Shadow Bite', power: 30, type: 1, accuracy: 100, effect: 'none' }], evolutionId: 'SHADOWSTALKER' },
    'SHADOWSTALKER': { name: 'Shadowstalker', type: 1, baseHp: 70, baseAtk: 30, baseDef: 18, baseSpd: 30, abilities: [{ name: 'Shadow Dance', power: 60, type: 1, accuracy: 100, effect: 'speed_boost' }, { name: 'Dark Pulse', power: 55, type: 1, accuracy: 95, effect: 'none' }], evolutionId: null },

    // BLOOM LINE
    'FLORALIS': { name: 'Floralis', type: 2, baseHp: 65, baseAtk: 10, baseDef: 15, baseSpd: 8, abilities: [{ name: 'Leaf Blade', power: 40, type: 2, accuracy: 100, effect: 'none' }, { name: 'Seed Shot', power: 30, type: 2, accuracy: 100, effect: 'none' }], evolutionId: 'BLOOMWING' },
    'BLOOMWING': { name: 'Bloomwing', type: 2, baseHp: 80, baseAtk: 15, baseDef: 20, baseSpd: 15, abilities: [{ name: 'Petal Storm', power: 55, type: 2, accuracy: 90, effect: 'none' }, { name: 'Nectar Sips', power: 40, type: 2, accuracy: 100, effect: 'heal' }], evolutionId: null },

    // AERO LINE
    'AEROBE': { name: 'Aerobe', type: 3, baseHp: 40, baseAtk: 18, baseDef: 6, baseSpd: 22, abilities: [{ name: 'Wind Whip', power: 40, type: 3, accuracy: 100, effect: 'none' }, { name: 'Gale Force', power: 50, type: 3, accuracy: 85, effect: 'none' }], evolutionId: 'SKYRAVEN' },
    'SKYRAVEN': { name: 'Skyraven', type: 3, baseHp: 55, baseAtk: 30, baseDef: 10, baseSpd: 40, abilities: [{ name: 'Cyclone', power: 70, type: 3, accuracy: 85, effect: 'none' }, { name: 'Feather Dart', power: 35, type: 3, accuracy: 100, effect: 'none' }], evolutionId: null },

    // HYDRO LINE
    'TIDALIS': { name: 'Tidalis', type: 4, baseHp: 75, baseAtk: 10, baseDef: 18, baseSpd: 5, abilities: [{ name: 'Water Jet', power: 45, type: 4, accuracy: 100, effect: 'none' }, { name: 'Bubble Pop', power: 30, type: 4, accuracy: 100, effect: 'none' }], evolutionId: 'OCEANUS' },
    'OCEANUS': { name: 'Oceanus', type: 4, baseHp: 100, baseAtk: 25, baseDef: 30, baseSpd: 5, abilities: [{ name: 'Tidal Wave', power: 80, type: 4, accuracy: 90, effect: 'none' }, { name: 'Deep Surge', power: 60, type: 4, accuracy: 100, effect: 'none' }], evolutionId: null },

    // NULL/PRIME LINE (Wild/Special)
    'VOIDGLIMMER': { name: 'Voidglimmer', type: 6, baseHp: 40, baseAtk: 40, baseDef: 10, baseSpd: 40, abilities: [{ name: 'Void Pulse', power: 50, type: 6, accuracy: 100, effect: 'none' }], evolutionId: 'NULLCORE' },
    'NULLCORE': { name: 'Nullcore', type: 6, baseHp: 120, baseAtk: 60, baseDef: 40, baseSpd: 20, abilities: [{ name: 'Singularity', power: 150, type: 6, accuracy: 70, effect: 'none' }], evolutionId: null },
    'PRIMUS': { name: 'Primus', type: 5, baseHp: 100, baseAtk: 50, baseDef: 50, baseSpd: 50, abilities: [{ name: 'Prismatic Ray', power: 100, type: 5, accuracy: 100, effect: 'none' }], evolutionId: null }
};

// ── class: GameState ──
class GameState {
    constructor() {
        this.party = [];
        this.inventory = [];
        this.currentZone = 0;
        this.exploredZones = [];
        this.badges = [];
        this.lastSaveTime = null;
        this.isGameOver = false;
        this.isPaused = false;
    }

    addCreature(creature) {
        if (this.party.length < GAME_CONSTANTS.MAX_PARTY_SIZE) {
            this.party.push(creature);
            return true;
        }
        return false;
    }

    addInventory(item) {
        this.inventory.push(item);
        return true;
    }

    save() {
        const data = JSON.stringify({
            party: this.party,
            inventory: this.inventory,
            currentZone: this.currentZone,
            exploredZones: this.exploredZones,
            badges: this.badges,
            lastSaveTime: Date.now()
        });
        localStorage.setItem(GAME_CONSTANTS.SAVE_KEY, data);
    }

    load() {
        const data = localStorage.getItem(GAME_CONSTANTS.SAVE_KEY);
        if (data) {
            const parsed = JSON.parse(data);
            // Re-instantiate creatures
            this.party = parsed.party.map(c => {
                const creature = new Creature(c.id, c.level);
                Object.assign(creature, c);
                return creature;
            });
            this.inventory = parsed.inventory;
            this.currentZone = parsed.currentZone;
            this.exploredZones = parsed.exploredZones;
            this.badges = parsed.badges;
            this.lastSaveTime = parsed.lastSaveTime;
            return true;
        }
        return false;
    }
}

// ── class: WorldManager ──
class WorldManager {
    constructor(canvas) {
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.map = [];
        this.playerPos = { x: 2, y: 2 };
        this.zones = [
            { name: "Whispering Glade", color: "#2d5a27", encounterRate: 0.1 },
            { name: "Searing Wastes", color: "#a0522d", encounterRate: 0.2 },
            { name: "Azure Reef", color: "#0077be", encounterRate: 0.15 },
            { name: "Shadowed Canyon", color: "#3c1c5c", encounterRate: 0.3 },
            { name: "Cloud Peak", color: "#87ceeb", encounterRate: 0.1 },
            { name: "Void Rift", color: "#000000", encounterRate: 0.4 }
        ];
        this.tileSize = 48;
        this.initMap();
    }

    initMap() {
        this.map = [];
        for (let y = 0; y < 15; y++) {
            const row = [];
            for (let x = 0; x < 20; x++) {
                row.push(this.generateTile(x, y));
            }
            this.map.push(row);
        }
    }

    generateTile(x, y) {
        const rand = Math.random();
        if (rand < 0.05) return 'WALL';
        if (rand < 0.07) return 'SHRINE';
        if (rand < 0.09) return 'BOSS';
        return 'EMPTY';
    }

    update() {}

    draw(ctx) {
        const zone = this.zones[gameState.currentZone];
        ctx.fillStyle = zone.color;
        ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

        for (let y = 0; y < 15; y++) {
            for (let x = 0; x < 20; x++) {
                const tile = this.map[y][x];
                const drawX = (x - this.playerPos.x) * this.tileSize + (this.canvas.width/2);
                const drawY = (y - this.playerPos.y) * this.tileSize + (this.canvas.height/2);

                if (tile === 'WALL') {
                    ctx.fillStyle = '#333';
                    ctx.fillRect(drawX, drawY, this.tileSize, this.tileSize);
                } else if (tile === 'SHRINE') {
                    ctx.fillStyle = 'white';
                    ctx.beginPath();
                    ctx.arc(drawX + 24, drawY + 24, 10, 0, Math.PI * 2);
                    ctx.fill();
                } else if (tile === 'BOSS') {
                    ctx.fillStyle = 'red';
                    ctx.fillRect(drawX, drawY, this.tileSize, this.tileSize);
                }
            }
        }

        // Player
        ctx.fillStyle = 'white';
        ctx.fillRect(
            this.playerPos.x * this.tileSize + (this.canvas.width/2) - 24 + 8, 
            this.playerPos.y * this.tileSize + (this.canvas.height/2) - 24 + 8, 
            this.tileSize - 16, 
            this.tileSize - 16
        );
    }

    movePlayer(dx, dy) {
        const newX = this.playerPos.x + dx;
        const newY = this.playerPos.y + dy;
        if (newX >= 0 && newX < 20 && newY >= 0 && newY < 15) {
            const tile = this.map[newY][newX];
            if (tile !== 'WALL') {
                this.playerPos.x = newX;
                this.playerPos.y = newY;
                this.checkEvent(tile);
            }
        }
    }

    checkEvent(tile) {
        if (tile === 'SHRINE') {
            uiManager.showDialogue("A Shrine! You feel refreshed.");
            gameState.party.forEach(c => c.heal(20));
            uiManager.updateHUD(gameState);
        } else if (tile === 'BOSS') {
            this.triggerBattle('BOSS');
        } else {
            const zone = this.zones[gameState.currentZone];
            if (Math.random() < zone.encounterRate) {
                this.triggerBattle('WILD');
            }
        }
    }

    triggerBattle(type) {
        const playerCreature = gameState.party[0];
        let enemyId = 'EMBERLIT';
        if (type === 'BOSS') enemyId = 'PYROPHANTOM';
        else if (Math.random() > 0.5) enemyId = 'VOIDCLAW';
        else enemyId = 'FLORALIS';

        const enemy = new Creature(enemyId, playerCreature.level);
        battleManager.start(playerCreature, enemy);
        document.getElementById('battle-overlay').classList.remove('hidden');
        document.getElementById('battle-overlay').classList.add('visible');
    }
}

// ── class: BattleManager ──
class BattleManager {
    constructor() {
        this.active = false;
        this.turn = 0;
        this.log = [];
        this.playerCreature = null;
        this.enemyCreature = null;
    }

    start(playerCreature, enemyCreature) {
        this.active = true;
        this.playerCreature = playerCreature;
        this.enemyCreature = enemyCreature;
        this.turn = 0;
        this.log = [`A wild ${enemyCreature.name} appeared!`];
        this.updateUI();
        this.renderBattleLog();
        document.getElementById('battle-overlay').classList.remove('hidden');
        document.getElementById('battle-overlay').classList.add('visible');
    }

    updateUI() {
        document.getElementById('enemy-name').textContent = this.enemyCreature.name;
        document.getElementById('enemy-hp-bar').style.width = `${(this.enemyCreature.hp / this.enemyCreature.hpMax) * 100}%`;
        document.getElementById('player-name').textContent = this.playerCreature.name;
        document.getElementById('player-hp-bar').style.width = `${(this.playerCreature.hp / this.playerCreature.hpMax) * 100}%`;
    }

    renderBattleLog() {
        const logDiv = document.getElementById('battle-log');
        logDiv.innerHTML = this.log.map(msg => `<div>${msg}</div>`).join('');
        logDiv.scrollTop = logDiv.scrollHeight;
    }

    processTurn(action) {
        if (!this.active) return;
        this.turn++;
        
        // Player turn
        if (action.type === 'attack') {
            const ability = this.playerCreature.abilities[action.abilityIdx % this.playerCreature.abilities.length];
            this.resolveMove(this.playerCreature, this.enemyCreature, ability);
        } else if (action.type === 'item') {
            this.useItem();
        }

        if (this.enemyCreature.hp <= 0) {
            this.endBattle('victory');
            return;
        }

        // Enemy turn (delay slightly for effect)
        setTimeout(() => {
            if (!this.active) return;
            const enemyAbility = this.enemyCreature.abilities[0];
            this.resolveMove(this.enemyCreature, this.playerCreature, enemyAbility);
            if (this.playerCreature.hp <= 0) {
                this.endBattle('defeat');
            }
            this.updateUI();
            this.renderBattleLog();
        }, 600);
    }

    resolveMove(attacker, defender, ability) {
        const isCrit = Math.random() < 0.1;
        const typeMultiplier = this.getTypeMultiplier(attacker.type, defender.type);
        
        let damage = (0.4 * attacker.level * attacker.atk / defender.def * ability.power) + (Math.random() * 5);
        damage *= (isCrit ? 1.5 : 1.0);
        damage *= typeMultiplier;
        
        const finalDmg = Math.floor(damage);
        defender.takeDamage(finalDmg);

        let msg = `${attacker.name} used ${ability.name}!`;
        if (isCrit) msg += " (CRITICAL!)";
        if (typeMultiplier > 1) msg += " (SUPER EFFECTIVE!)";
        else if (typeMultiplier < 1) msg += " (NOT VERY EFFECTIVE...)";
        
        this.log.push(`${msg} ${finalDmg} dmg.`);
        if (defender.hp <= 0) this.log.push(`${defender.name} fainted!`);
        
        playSound(isCrit ? 440 : 220, 'square', 0.1);
    }

    getTypeMultiplier(atkType, defType) {
        if (atkType === 5) return 1.5;
        if (defType === 5) return 0.5;
        if (atkType === 6) return 0.5;
        if (defType === 6) return 1.5;
        if (atkType === 0 && defType === 1) return 2.0;
        if (atkType === 1 && defType === 2) return 2.0;
        if (atkType === 2 && defType === 0) return 2.0;
        if (atkType === 3 && (defType === 0 || defType === 2)) return 2.0;
        if (atkType === 4 && (defType === 3 || defType === 1)) return 2.0;
        if (defType === 0 && atkType === 1) return 0.5;
        if (defType === 1 && atkType === 2) return 0.5;
        if (defType === 2 && atkType === 0) return 0.5;
        if (defType === 0 && atkType === 3) return 0.5;
        if (defType === 2 && atkType === 3) return 0.5;
        if (defType === 3 && atkType === 4) return 0.5;
        if (defType === 1 && atkType === 4) return 0.5;
        return 1.0;
    }

    useItem() {
        const creature = this.playerCreature;
        creature.heal(30);
        this.log.push(`${creature.name} used a Recovery Pulse! (+30 HP)`);
        this.updateUI();
    }

    endBattle(result) {
        this.active = false;
        if (result === 'victory') {
            this.playerCreature.gainExp(60);
            uiManager.updateHUD(gameState);
            uiManager.showDialogue(`${this.playerCreature.name} gained EXP!`);
        } else {
            uiManager.showDialogue("You were defeated...");
            gameState.isPaused = true;
        }
        setTimeout(() => {
            document.getElementById('battle-overlay').classList.add('hidden');
            document.getElementById('battle-overlay').classList.remove('visible');
            if (result === 'defeat') gameState.isPaused = false;
        }, 2000);
    }

    draw(ctx, canvas) {
        if (!this.active) return;

        // Clear canvas for battle (done by gameLoop if we manage it well)
        // Draw Background (simulated)
        ctx.fillStyle = '#333';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        // Draw Sprites on canvas
        // Enemy: Top-right
        SpriteRenderer.drawCreature(ctx, this.enemyCreature, canvas.width * 0.7, canvas.height * 0.25, 60, true);
        // Player: Bottom-left
        SpriteRenderer.drawCreature(ctx, this.playerCreature, canvas.width * 0.3, canvas.height * 0.6, 60, false);
    }
}

// ── class: UIManager ──
class UIManager {
    constructor() {
        this.hud = document.getElementById('hud');
        this.overlay = document.getElementById('overlay');
        this.dialog = document.getElementById('dialog');
    }

    showDialogue(text, color = '#fff') {
        const box = document.getElementById('dialog-box');
        box.textContent = text;
        box.style.color = color;
        this.dialog.style.display = 'flex';
    }

    hideDialogue() {
        this.dialog.style.display = 'none';
    }

    updateHUD(gameState) {
        const partyHtml = gameState.party.map(c => `
            <div class="party-unit">
                <span>${c.name} (Lv.${c.level})</span>
                <div class="hp-bar-container">
                    <div class="hp-bar-fill" style="width: ${(c.hp/c.hpMax)*100}%; background: ${TYPE_COLORS[c.type]}"></div>
                </div>
            </div>
        `).join('');
        document.getElementById('party-list').innerHTML = partyHtml;
    }

    showMenu(menuId) {
        const menu = document.getElementById(menuId);
        menu.classList.add('visible');
    }

    hideMenu(menuId) {
        const menu = document.getElementById(menuId);
        menu.classList.remove('visible');
    }
}

// ── const: gameState ──
const gameState = new GameState();

// ── let: worldManager ──
let worldManager;

// ── let: battleManager ──
let battleManager;

// ── let: uiManager ──
let uiManager;

// ── let: audioCtx ──
let audioCtx;

// ── let: masterVolume ──
let masterVolume = 0.5;

// ── function: initAudio ──
function initAudio() {
    if (audioCtx) return;
    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}

// ── function: playSound ──
function playSound(freq, type, duration, volume = 0.1) {
    if (!audioCtx) return;
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.type = type;
    osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
    gain.gain.setValueAtTime(volume * masterVolume, audioCtx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
    osc.connect(gain);
    gain.connect(audioCtx.destination);
    osc.start();
    osc.stop(audioCtx.currentTime + duration);
}

// ── function: playNote ──
function playNote(freq, type, duration) {
    playSound(freq, type, duration);
}

// ── const: CREATURE_LEVELS ──
const CREATURE_LEVELS = [
    { name: 'EMBERLIT', start: 1 },
    { name: 'FLAMEWISP', start: 15 },
    { name: 'PYROPHANTOM', start: 30 }
];

// ── const: SpriteRenderer ──
// Sprite Renderer - Simple geometric procedural sprites
const SpriteRenderer = {
    drawCreature(ctx, creature, x, y, size, isEnemy = false) {
        const typeColor = TYPE_COLORS[creature.type];
        ctx.save();
        ctx.translate(x, y);
        if (isEnemy) ctx.scale(-1, 1);

        // Body
        ctx.fillStyle = typeColor;
        ctx.shadowBlur = 15;
        ctx.shadowColor = typeColor;
        ctx.beginPath();
        if (creature.type === 0) { // Cinder: Spiky
            ctx.moveTo(size/2, 0);
            ctx.lineTo(size, size);
            ctx.lineTo(0, size);
            ctx.closePath();
        } else if (creature.type === 1) { // Umbra: Swirly
            ctx.arc(size/2, size/2, size/2, 0, Math.PI * 2);
        } else if (creature.type === 2) { // Bloom: Petals
            for (let i = 0; i < 5; i++) {
                ctx.rotate(Math.PI * 2 / 5);
                ctx.ellipse(size/2, 0, size/3, size/5, 0, 0, Math.PI * 2);
            }
        } else { // Default: Square/Circle
            ctx.arc(size/2, size/2, size/2, 0, Math.PI * 2);
        }
        ctx.fill();
        
        // Eyes
        ctx.fillStyle = 'white';
        ctx.beginPath();
        ctx.arc(size/3, size/3, size/6, 0, Math.PI * 2);
        ctx.arc(2*size/3, size/3, size/6, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = 'black';
        ctx.beginPath();
        ctx.arc(size/3, size/3, size/12, 0, Math.PI * 2);
        ctx.arc(2*size/3, size/3, size/12, 0, Math.PI * 2);
        ctx.fill();

        ctx.restore();
    }
};

// ── function: initializeCreatureAbilities ──
function initializeCreatureAbilities() {
    // This is a helper to add more variety if I can't redefine the object
    // But I'll just include them in the next large CREATURE_DATA definition.
}

// ── main ──
// Finalize the main block to include the loop and the setup
// I'll move all the initialization logic here.

function init() {
    const canvas = document.getElementById('game-canvas');
    canvas.width = GAME_CONSTANTS.VIEWPORT_W;
    canvas.height = GAME_CONSTANTS.VIEWPORT_H;

    uiManager = new UIManager();
    worldManager = new WorldManager(canvas);
    battleManager = new BattleManager();

    // Start Game
    document.getElementById('btn-new-game').addEventListener('click', startNewGame);
    document.getElementById('btn-continue').addEventListener('click', continueGame);
    document.getElementById('dialog-close').addEventListener('click', () => uiManager.hideDialogue());
    document.getElementById('menu-close').addEventListener('click', () => uiManager.hideMenu('overlay'));

    // Control Listeners
    window.addEventListener('keydown', handleKeydown);

    // Battle Controls
    document.getElementById('cmd-attack').addEventListener('click', () => {
        battleManager.processTurn({ type: 'attack', abilityIdx: 0 });
    });
    document.getElementById('cmd-skill').addEventListener('click', () => {
        battleManager.processTurn({ type: 'attack', abilityIdx: 1 });
    });
    document.getElementById('cmd-item').addEventListener('click', () => {
        battleManager.processTurn({ type: 'item' });
    });

    // Main Loop
    requestAnimationFrame(gameLoop);

    // Show Title
    document.getElementById('title-screen').classList.remove('hidden');
}

function startNewGame() {
    initAudio();
    document.getElementById('title-screen').classList.add('hidden');
    document.getElementById('overlay').classList.remove('hidden');

    // Initial Party
    const emberlit = new Creature('EMBERLIT', 5);
    const voidclaw = new Creature('VOIDCLAW', 5);
    gameState.addCreature(emberlit);
    gameState.addCreature(voidclaw);

    worldManager.initMap();
    uiManager.updateHUD(gameState);
    uiManager.showDialogue("Welcome to Essentia. Your journey begins in the Whispering Glade.");
    document.getElementById('overlay').classList.add('hidden');
}

function continueGame() {
    if (gameState.load()) {
        initAudio();
        document.getElementById('title-screen').classList.add('hidden');
        document.getElementById('overlay').classList.remove('hidden');
        uiManager.updateHUD(gameState);
        document.getElementById('overlay').classList.add('hidden');
    } else {
        uiManager.showDialogue("No save file found.");
    }
}

function handleKeydown(e) {
    if (gameState.isPaused) return;
    
    if (e.key === 'w' || e.key === 'ArrowUp') worldManager.movePlayer(0, -1);
    if (e.key === 's' || e.key === 'ArrowDown') worldManager.movePlayer(0, 1);
    if (e.key === 'a' || e.key === 'ArrowLeft') worldManager.movePlayer(-1, 0);
    if (e.key === 'd' || e.key === 'ArrowRight') worldManager.movePlayer(1, 0);
    
    if (e.key.toLowerCase() === 'm') {
        gameState.isPaused = !gameState.isPaused;
        if (gameState.isPaused) uiManager.showMenu('overlay');
        else uiManager.hideMenu('overlay');
    }
}

function gameLoop(time) {
    const canvas = document.getElementById('game-canvas');
    if (!canvas) return;
    const ctx = canvas.getContext('2d');

    if (gameState.isPaused) {
        // Show a dark overlay for pause
        ctx.fillStyle = 'rgba(0,0,0,0.5)';
        ctx.fillRect(0,0, canvas.width, canvas.height);
    } else if (battleManager.active) {
        battleManager.draw(ctx, canvas);
    } else {
        worldManager.update();
        worldManager.draw(ctx);
    }
    
    requestAnimationFrame(gameLoop);
}

init();
</script>
</body>
</html>
<!-- agent-meta {"model":"google/gemma-4-26b-a4b-qat","provider":"lmstudio","persona":"composer","sessionId":"99899f02-3f4a-47fc-ae49-69d2dcb9d07a","tokensIn":556710,"tokensOut":28060,"tokensTotal":584770,"turns":29,"toolCalls":28,"failedToolCalls":4,"timestamp":"2026-07-14T01:00:05.322Z"} -->