Creature Collection
Gemma 4 12b qat · composer

Browser check: Artifact runtime error. This is a technical check, not a quality review.
Reviews & generation details
Reviews
Human review · Jul 17, 2026 · rubric 1
Historical path-based review. Association with these exact bytes is unverified.
The reviewer marked this result as broken. It remains in the archive.
- Visual design
- Unscored
- UX / feel
- Unscored
- Works
- Unscored
- Prompt spirit
- Unscored
- Delight
- Unscored
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 33,721 bytes · SHA-256 be993fdd0267
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Vitreous Resonance</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<style>
/* ── css: core_styles ── */
:root {
--primary: #a2d2ff;
--secondary: #ffafcc;
--accent: #ffcdb9;
--bg: #0f172a;
--surface: rgba(30, 41, 59, 0.8);
--text: #f8fafc;
--border: rgba(255, 255, 255, 0.1);
--font-main: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
--font-header: 'Georgia', serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg);
color: var(--text);
font-family: var(--font-main);
overflow: hidden;
height: 100vh;
width: 100vw;
}
#app {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
canvas {
display: block;
image-rendering: pixelated;
box-shadow: 0 0 50px rgba(0,0,0,0.5);
}
/* UI Overlays */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: radial-gradient(circle, rgba(15, 23, 42, 0.2) 0%, rgba(15, 23, 42, 0.9) 100%);
z-index: 100;
backdrop-filter: blur(10px);
}
.menu-container {
background: var(--surface);
padding: 2rem;
border-radius: 1rem;
border: 1px solid var(--border);
text-align: center;
max-width: 80%;
max-height: 80%;
overflow-y: auto;
}
h1 {
font-family: var(--font-header);
font-size: 4rem;
margin-bottom: 2rem;
text-shadow: 0 0 20px var(--primary);
}
button {
background: var(--primary);
border: none;
padding: 0.8rem 2rem;
margin: 0.5rem;
border-radius: 0.5rem;
color: var(--bg);
font-weight: bold;
cursor: pointer;
transition: transform 0.2s, background 0.2s;
font-size: 1.2rem;
}
button:hover {
transform: scale(1.05);
background: var(--secondary);
}
button:disabled {
background: #64748b;
cursor: not-allowed;
}
/* HUD */
#hud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.hud-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.status-bar {
background: var(--surface);
padding: 10px 20px;
border-radius: 10px;
border: 1px solid var(--border);
}
#battle-ui {
position: absolute;
bottom: 0;
width: 100%;
height: 30%;
background: rgba(15, 23, 42, 0.9);
border-top: 2px solid var(--primary);
display: none;
padding: 20px;
box-sizing: border-box;
pointer-events: auto;
flex-direction: column;
justify-content: space-between;
}
.battle-party {
display: flex;
justify-content: space-around;
margin-bottom: 20px;
}
.creature-slot {
text-align: center;
width: 120px;
}
.hp-bar-outer {
width: 100%;
height: 8px;
background: #444;
border-radius: 4px;
overflow: hidden;
margin-top: 5px;
}
.hp-bar-inner {
height: 100%;
background: #4ade80;
transition: width 0.3s;
}
.action-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
max-width: 600px;
margin: 0 auto;
}
.dialogue-box {
background: var(--surface);
padding: 15px;
border-radius: 10px;
margin-bottom: 10px;
border: 1px solid var(--border);
min-height: 60px;
font-size: 1.1rem;
}
/* Animations */
@keyframes shake {
0% { transform: translate(1px, 1px) rotate(0deg); }
20% { transform: translate(-3px, 0px) rotate(-1deg); }
40% { transform: translate(3px, 2px) rotate(1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
80% { transform: translate(3px, -1px) rotate(1deg); }
100% { transform: translate(1px, 2px) rotate(0deg); }
}
.shake {
animation: shake 0.2s ease-in-out;
}
.floating-text {
position: absolute;
pointer-events: none;
animation: floatUp 1s forwards;
font-weight: bold;
}
@keyframes floatUp {
0% { transform: translateY(0); opacity: 1; }
100% { transform: translateY(-50px); opacity: 0; }
}
/* ── css: ui_polish ── */
.pulse {
animation: pulse-animation 2s infinite;
}
@keyframes pulse-animation {
0% { opacity: 0.8; }
50% { opacity: 1; }
100% { opacity: 0.8; }
}
.pause-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 200;
padding: 10px;
background: var(--surface);
border: 1px solid var(--border);
color: white;
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<canvas id="game-canvas"></canvas>
<div id="hud" style="display: none;">
<div class="hud-top">
<div class="status-bar" id="hud-zone-info">Zone: ---</div>
<div class="status-bar" id="hud-party-info">Party: 0/6</div>
</div>
<div id="battle-ui">
<div id="battle-log" class="dialogue-box">Welcome to the Resonance.</div>
<div class="battle-party" id="battle-party-display"></div>
<div class="action-grid" id="action-grid"></div>
</div>
</div>
<div id="ui-layer"></div>
</div>
<script>
'use strict';
// ── const: TYPES ──
const TYPES = {
A: { name: "Aether", color: "#a2d2ff" },
B: { name: "Blight", color: "#88cc88" },
C: { name: "Cinder", color: "#ff8844" },
D: { name: "Dusk", color: "#aa4488" },
E: { name: "Echo", color: "#ffcc00" },
F: { name: "Flux", color: "#ffffff" },
G: { name: "Glass", color: "#cc99ff" }
};
// ── const: TYPE_CHART ──
const TYPE_CHART = {
A: { beats: ["C"], weak: ["D"] }, // Type A > Type C; Type D > Type A
B: { beats: ["A"], weak: ["E"] }, // Type B > Type A; Type E > Type B
C: { beats: ["B"], weak: ["A"] }, // Type C > Type B; Type A > Type C
D: { beats: ["A", "C"], weak: ["E"] }, // Type D > A & C; Type E > D
E: { beats: ["D", "B"], weak: [] }, // Type E > D & B
F: { beats: ["all", 1.1], weak: [] }, // Type F beats all slightly
G: { beats: [], weak: ["F"] } // Type G resists all but weak to F
};
// ── function: getMultiplier ──
// Logic for "beats all slightly" or "resists everything"
function getMultiplier(attackerType, defenderType) {
if (attackerType === "F") return 1.1;
if (attackerType === "G") return 0.5; // Resists everything
const chart = TYPE_CHART[attackerType];
if (chart.beats.includes(defenderType)) return 1.5;
if (chart.beats.includes("all") && attackerType === "F") return 1.1;
// Check if defender is weak to attacker
const defChart = TYPE_CHART[defenderType];
if (defChart.weak.includes(attackerType)) return 1.5;
return 1.0;
}
// ── const: ABILITIES ──
const ABILITIES = [
{ name: "Vitreous Strike", type: "Cinder", power: 40, acc: 0.9, effect: "none", desc: "A sharp physical blow." },
{ name: "Aetheric Veil", type: "Aether", power: 20, acc: 0.8, effect: "barrier", desc: "Creates a temporary barrier." },
{ name: "Blight Bloom", type: "Blight", power: 35, acc: 0.85, effect: "burn", desc: "Spreads decaying spores." },
{ name: "Frost Pulse", type: "Echo", power: 30, acc: 0.8, effect: "frost", desc: "Slows the target's movements." },
{ name: "Static Surge", type: "Flux", power: 45, acc: 0.7, effect: "shock", desc: "A high-voltage discharge." },
{ name: "Lumina Heal", type: "Aether", power: 50, acc: 0.9, effect: "heal", desc: "Restores HP to an ally." },
{ name: "Shatter", type: "Glass", power: 60, acc: 0.7, effect: "multi-hit", desc: "Hits three times for less damage." },
{ name: "Echoing Shout", type: "Echo", power: 25, acc: 1.0, effect: "speed", desc: "Increases party speed." },
{ name: "Void Rip", type: "Dusk", power: 55, acc: 0.75, effect: "none", desc: "Tears through the fabric of space." },
{ name: "Molten Core", type: "Cinder", power: 40, acc: 0.8, effect: "burn", desc: "Heats the target's blood." },
{ name: "Prism Ray", type: "Glass", power: 35, acc: 0.85, effect: "none", desc: "A beam of focused light." },
{ name: "Root Bind", type: "Blight", power: 30, acc: 0.8, effect: "frost", desc: "Entangles the target in vines." },
{ name: "Spark Jump", type: "Flux", power: 20, acc: 0.95, effect: "speed", desc: "Quick leap with electricity." },
{ name: "Gravity Well", type: "Dusk", power: 45, acc: 0.7, effect: "slow", desc: "Heavy weight crushes the target." },
{ name: "Crystal Shield", type: "Glass", power: 25, acc: 0.9, effect: "barrier", desc: "Hardens the skin into glass." },
{ name: "Flare", type: "Cinder", power: 50, acc: 0.8, effect: "burn", desc: "A sudden burst of heat." },
{ name: "Mist Veil", type: "Aether", power: 15, acc: 1.0, effect: "heal", desc: "Mends minor wounds with mist." },
{ name: "Corruption", type: "Blight", power: 50, acc: 0.7, effect: "burn", desc: "Injects rotting essence." },
{ name: "Shockwave", type: "Flux", power: 40, acc: 0.85, effect: "shock", desc: "A blast of kinetic energy." },
{ name: "Mirror Image", type: "Echo", power: 20, acc: 0.8, effect: "barrier", desc: "Creates a decoy." },
{ name: "Stone Skin", type: "Glass", power: 30, acc: 0.9, effect: "defense", desc: "Increases defense for 2 turns." },
{ name: "Tectonic Shift", type: "Dusk", power: 60, acc: 0.6, effect: "multi-hit", desc: "Earthquakes strike the ground." },
{ name: "Solar Flare", type: "Cinder", power: 70, acc: 0.6, effect: "burn", desc: "Blinding heat from above." },
{ name: "Nature's Wrath", type: "Blight", power: 65, acc: 0.65, effect: "slow", desc: "Overwhelming growth." },
{ name: "Luminous Dance", type: "Aether", power: 45, acc: 0.85, effect: "speed", desc: "Agile movement in light." },
{ name: "Darkness Dive", type: "Dusk", power: 55, acc: 0.8, effect: "none", desc: "Strikes from the shadows." },
{ name: "Refraction", type: "Glass", power: 30, acc: 0.9, effect: "multi-hit", desc: "Light splits into multiple rays." },
{ name: "Thunderclap", type: "Flux", power: 40, acc: 0.8, effect: "shock", desc: "A deafening crack of thunder." },
{ name: "Spore Cloud", type: "Blight", power: 35, acc: 0.9, effect: "burn", desc: "Clouds the area with dust." },
{ name: "Wisp Guide", type: "Echo", power: 25, acc: 1.0, effect: "heal", desc: "Small wisps mend wounds." },
{ name: "Quake", type: "Dusk", power: 50, acc: 0.75, effect: "slow", desc: "Rumbles the earth." },
{ name: "Ignite", type: "Cinder", power: 30, acc: 0.9, effect: "burn", desc: "Sets the target's clothes on fire." },
{ name: "Ethereal Link", type: "Aether", power: 40, acc: 0.8, effect: "regen", desc: "Connects souls to heal." },
{ name: "Shatterpoint", type: "Glass", power: 80, acc: 0.5, effect: "multi-hit", desc: "Destroys structural integrity." },
{ name: "Void Touch", type: "Dusk", power: 50, acc: 0.75, effect: "none", desc: "Absorbs life energy." },
{ name: "Storm Call", type: "Flux", power: 60, acc: 0.6, effect: "shock", desc: "Summons a miniature storm." },
{ name: "Wild Growth", type: "Blight", power: 55, acc: 0.7, effect: "slow", desc: "Overruns the enemy." },
{ name: "Sunbeam", type: "Aether", power: 45, acc: 0.9, effect: "heal", desc: "A warm ray of healing." },
{ name: "Obsidian Fang", type: "Glass", power: 55, acc: 0.8, effect: "none", desc: "Piercing bite." },
{ name: "Cinder Burst", type: "Cinder", power: 40, acc: 0.85, effect: "burn", desc: "Explosive heat." },
{ name: "Shadow Step", type: "Dusk", power: 35, acc: 0.95, effect: "speed", desc: "Teleports behind the foe." },
{ name: "Static Discharge", type: "Flux", power: 30, acc: 1.0, effect: "shock", desc: "Constant buzzing electricity." },
{ name: "Echo Strike", type: "Echo", power: 40, acc: 0.8, effect: "multi-hit", desc: "Strike repeats in time." },
{ name: "Verdant Reach", type: "Blight", power: 45, acc: 0.8, effect: "slow", desc: "Vines grab the target." },
{ name: "Prism Shield", type: "Glass", power: 30, acc: 0.9, effect: "barrier", desc: "Reflects light into armor." },
{ name: "Nova", type: "Flux", power: 100, acc: 0.4, effect: "none", desc: "Massive explosion." }
];
// ── class: Creature ──
class Creature {
constructor(data) {
this.id = Math.random().toString(36).substr(2, 9);
this.name = data.name;
this.lore = data.lore;
this.type = data.type;
this.level = data.level || 1;
this.maxHp = data.hp || 100;
this.hp = data.hp || 100;
this.maxAtk = data.atk || 10;
this.maxDef = data.def || 8;
this.maxSpd = data.spd || 5;
this.currentAtk = this.maxAtk;
this.currentDef = this.maxDef;
this.currentSpd = this.maxSpd;
this.abilities = data.abilities.map(a => ({ ...a, pp: 100 }));
this.exp = 0;
this.isTransformed = false;
this.statusEffects = [];
this.barrier = false;
}
get stats() {
return {
atk: Math.floor(this.maxAtk * (1 + (this.level - 1) * 0.1)),
def: Math.floor(this.maxDef * (1 + (this.level - 1) * 0.08)),
spd: Math.floor(this.maxSpd * (1 + (this.level - 1) * 0.05))
};
}
takeDamage(amount) {
if (this.barrier) {
this.barrier = false;
return 0;
}
let damage = Math.max(1, Math.floor(amount * (1 - (this.stats.def / (this.stats.def + 20)))));
this.hp = Math.max(0, this.hp - damage);
return damage;
}
applyStatus(effectName) {
// Simple status logic
if (effectName === "burn") this.statusEffects.push({ type: "burn", duration: 3 });
if (effectName === "frost") this.statusEffects.push({ type: "frost", duration: 2 });
if (effectName === "shock") this.statusEffects.push({ type: "shock", duration: 1 });
if (effectName === "barrier") this.barrier = true;
if (effectName === "regen") this.statusEffects.push({ type: "regen", duration: 3 });
}
updateStatus() {
this.statusEffects = this.statusEffects.filter(s => {
s.duration--;
return s.duration > 0;
});
}
}
// ── const: BASE_CREATURES ──
const BASE_CREATURES = [
{ name: "Lume", lore: "A flickering spirit of the first dawn.", type: "Aether", hp: 110, atk: 12, def: 10, spd: 14, abilities: [0, 1, 4, 5] },
{ name: "Mire", lore: "A sludge-born entity that feeds on decay.", type: "Blight", hp: 130, atk: 10, def: 15, spd: 8, abilities: [2, 11, 12, 18] },
{ name: "Pyre", lore: "A living ember that dances in the drafts.", type: "Cinder", hp: 90, atk: 18, def: 8, spd: 12, abilities: [0, 6, 15, 39] },
{ name: "Shade", lore: "A silhouette that moves between thoughts.", type: "Dusk", hp: 100, atk: 15, def: 12, spd: 16, abilities: [8, 13, 25, 35] },
{ name: "Vibe", lore: "A resonant pulse in the air.", type: "Echo", hp: 85, atk: 10, def: 8, spd: 22, abilities: [3, 7, 19, 40] },
{ name: "Spark", lore: "A jittery spark of pure energy.", type: "Flux", hp: 70, atk: 14, def: 6, spd: 25, abilities: [4, 12, 20, 30] },
{ name: "Shard", lore: "A jagged piece of the world's core.", type: "Glass", hp: 120, atk: 16, def: 20, spd: 9, abilities: [6, 10, 14, 36] },
{ name: "Glint", lore: "A tiny shard of reflective light.", type: "Glass", hp: 80, atk: 12, def: 15, spd: 14, abilities: [10, 28, 32, 36] },
{ name: "Drift", lore: "A cloud of wandering memories.", type: "Echo", hp: 100, atk: 11, def: 11, spd: 15, abilities: [3, 18, 21, 38] },
{ name: "Bloom", lore: "A flower that grows in whispers.", type: "Blight", hp: 115, atk: 9, def: 14, spd: 11, abilities: [2, 11, 31, 41] },
{ name: "Cinderfall", lore: "Falling ash that never hits the ground.", type: "Cinder", hp: 95, atk: 14, def: 10, spd: 13, abilities: [0, 15, 23, 26] },
{ name: "Aetheris", lore: "A majestic bird of pure light.", type: "Aether", hp: 120, atk: 15, def: 12, spd: 18, abilities: [1, 5, 22, 37] },
{ name: "Null", lore: "A void that consumes all sound.", type: "Dusk", hp: 140, atk: 18, def: 18, spd: 7, abilities: [8, 13, 35, 40] },
{ name: "Fluxian", lore: "A creature made of shifting frequencies.", type: "Flux", hp: 80, atk: 16, def: 10, spd: 24, abilities: [4, 20, 30, 39] },
{ name: "Vitrum", lore: "The foundation of the crystal gardens.", type: "Glass", hp: 150, atk: 14, def: 25, spd: 6, abilities: [6, 10, 14, 26] },
{ name: "Scorch", lore: "A serpent of living magma.", type: "Cinder", hp: 110, atk: 20, def: 12, spd: 10, abilities: [0, 6, 15, 39] },
{ name: "Mistral", lore: "The wind that carries the first secrets.", type: "Echo", hp: 80, atk: 12, def: 9, spd: 26, abilities: [3, 7, 19, 38] },
{ name: "Wilder", lore: "A beast of unbridled growth.", type: "Blight", hp: 135, atk: 19, def: 14, spd: 9, abilities: [2, 11, 18, 31] },
{ name: "Zenith", lore: "The pinnacle of resonant grace.", type: "Aether", hp: 110, atk: 18, def: 15, spd: 20, abilities: [1, 5, 22, 37] }
];
// ── const: TRANSFORMATIONS ──
const TRANSFORMATIONS = [
{ name: "Lume-Exalted", base: "Lume", hp: 160, atk: 25, def: 20, spd: 25, reqLevel: 20, catalyst: "Echo Shard" },
{ name: "Mire-Corrupted", base: "Mire", hp: 200, atk: 30, def: 35, spd: 5, reqLevel: 25, catalyst: "Void Essence" },
{ name: "Pyre-Infernal", base: "Pyre", hp: 130, atk: 40, def: 15, spd: 18, reqLevel: 20, catalyst: "Cinder Core" },
{ name: "Shade-Ethereal", base: "Shade", hp: 120, atk: 25, def: 25, spd: 30, reqLevel: 25, catalyst: "Aether Shard" },
{ name: "Vibe-Resonant", base: "Vibe", hp: 100, atk: 20, def: 20, spd: 40, reqLevel: 20, catalyst: "Resonance Key" },
{ name: "Spark-Overload", base: "Spark", hp: 90, atk: 45, def: 10, spd: 50, reqLevel: 25, catalyst: "Flux Core" },
{ name: "Shard-Prismatic", base: "Shard", hp: 180, atk: 30, def: 40, spd: 10, reqLevel: 30, catalyst: "Prism Shard" },
{ name: "Glint-Radiant", base: "Glint", hp: 140, atk: 25, def: 30, spd: 25, reqLevel: 25, catalyst: "Lumina Shard" },
{ name: "Drift-Haunting", base: "Drift", hp: 150, atk: 22, def: 22, spd: 22, reqLevel: 25, catalyst: "Void Essence" }
];
// ── class: GameEngine ──
class GameEngine {
constructor() {
this.state = {
currentZone: 0,
party: [],
inventory: [],
enemies: [],
battle: null,
mapPos: { x: 2, y: 2 },
storyProgress: 0,
flags: {
bossesDefeated: 0,
unlockedZones: [0],
collectedCreatures: []
},
gameActive: false,
selectedCreatureIdx: 0,
captureProgress: 0,
isCapturing: false,
pause: false
};
this.zones = [
{ name: "The Whispering Glade", bg: "linear-gradient(to bottom, #1e293b, #334155)", color: "#a2d2ff", rate: 0.1, story: "The air here hums with a faint, melodic frequency." },
{ name: "The Scorched Basin", bg: "linear-gradient(to bottom, #450a0a, #7f1d1d)", color: "#ff8844", rate: 0.15, story: "Heat waves distort the horizon, smelling of ancient ash." },
{ name: "The Sunken Mire", bg: "linear-gradient(to bottom, #064e3b, #065f46)", color: "#88cc88", rate: 0.18, story: "Thick moss coats everything, damp and heavy with life." },
{ name: "The Void Rift", bg: "linear-gradient(to bottom, #2e1065, #4c1d95)", color: "#aa4488", rate: 0.2, story: "A tear in reality where shadows bleed into color." },
{ name: "The Crystal Spires", bg: "linear-gradient(to bottom, #581c87, #86198f)", color: "#cc99ff", rate: 0.22, story: "Tall structures of glass reach toward the heavens." },
{ name: "The Core Nexus", bg: "linear-gradient(to bottom, #0f172a, #1e293b)", color: "#ffffff", rate: 0.25, story: "The heartbeat of the world, pulsing with white light." }
];
this.init();
}
init() {
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;
PARTICLE_SYSTEM.init();
this.renderOverworld();
this.setupInput();
this.startLoop();
this.showIntro();
}
saveGame() {
const saveData = {
currentZone: this.state.currentZone,
party: this.state.party.map(p => ({
name: p.name,
maxHp: p.maxHp,
hp: p.hp,
maxAtk: p.maxAtk,
maxDef: p.maxDef,
maxSpd: p.maxSpd,
level: p.level,
exp: p.exp,
abilities: p.abilities.map(a => ({...a, pp: a.pp})),
isTransformed: p.isTransformed,
statusEffects: p.statusEffects
})),
inventory: this.state.inventory,
flags: this.state.flags,
mapPos: this.state.mapPos
};
localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
this.showNotification("Game Saved");
}
loadGame() {
const data = localStorage.getItem(SAVE_KEY);
if (!data) return;
const parsed = JSON.parse(data);
this.state.currentZone = parsed.currentZone;
this.state.party = parsed.party.map(p => {
const c = new Creature(p);
c.hp = p.hp;
c.exp = p.exp;
c.isTransformed = p.isTransformed;
c.statusEffects = p.statusEffects;
return c;
});
this.state.inventory = parsed.inventory;
this.state.flags = parsed.flags;
this.state.mapPos = parsed.mapPos;
this.updateHUD();
this.renderOverworld();
this.showNotification("Game Loaded");
}
showNotification(text) {
const ui = document.getElementById('ui-layer');
ui.innerHTML = `<div class="overlay" style="background: transparent; pointer-events: none;">
<div class="status-bar" style="margin-top: 20px; font-size: 2rem;">${text}</div>
</div>`;
setTimeout(() => { ui.innerHTML = ''; }, 2000);
}
showIntro() {
const ui = document.getElementById('ui-layer');
ui.innerHTML = `
<div class="overlay">
<div class="menu-container">
<h1 id="game-title">VITREOUS RESONANCE</h1>
<p>The world hums in frequencies. You are the Tuner.</p>
<div style="display:flex; justify-content:center; margin-top:20px;">
<button id="btn-new">New Binding</button>
<button id="btn-load">Continue</button>
</div>
</div>
</div>
`;
document.getElementById('btn-new').onclick = () => {
this.state.party = [new Creature(BASE_CREATURES[0]), new Creature(BASE_CREATURES[1]), new Creature(BASE_CREATURES[2])];
this.state.gameActive = true;
this.updateHUD();
ui.innerHTML = '';
};
document.getElementById('btn-load').onclick = () => {
this.loadGame();
if (this.state.party.length > 0) {
this.state.gameActive = true;
ui.innerHTML = '';
}
};
}
updateHUD() {
document.getElementById('hud-zone-info').innerText = `Zone: ${this.zones[this.state.currentZone].name}`;
document.getElementById('hud-party-info').innerText = `Party: ${this.state.party.length}/6`;
}
setupInput() {
window.addEventListener('keydown', (e) => {
if (this.state.battle || !this.state.gameActive || this.state.pause) return;
const move = { w: [0, -1], s: [0, 1], a: [-1, 0], d: [1, 0] };
let step = null;
if (e.key === 'w' || e.key === 'ArrowUp') step = move.w;
if (e.key === 's' || e.key === 'ArrowDown') step = move.s;
if (e.key === 'a' || e.key === 'ArrowLeft') step = move.a;
if (e.key === 'd' || e.key === 'ArrowRight') step = move.d;
if (e.key === 'p' || e.key === 'Escape') {
this.state.pause = !this.state.pause;
if (this.state.pause) this.togglePauseMenu();
return;
}
if (step) {
this.state.mapPos.x = Math.max(0, Math.min(19, this.state.mapPos.x + step[0]));
this.state.mapPos.y = Math.max(0, Math.min(19, this.state.mapPos.y + step[1]));
this.handleMapEvent();
this.renderOverworld();
}
});
// Capture Button
const captureBtn = document.createElement('button');
captureBtn.id = "capture-btn";
captureBtn.innerText = "CAPTURE";
captureBtn.style.cssText = "position:absolute; bottom: 20px; right: 20px; padding: 15px; background: #ffcc00; font-weight: bold; display:none; border: 2px solid white; border-radius: 10px; z-index: 1000; box-shadow: 0 4px 15px rgba(0,0,0,0.5);";
document.getElementById('battle-ui').appendChild(captureBtn);
captureBtn.onclick = () => this.handleCapture();
}
togglePauseMenu() {
const ui = document.getElementById('ui-layer');
if (this.state.pause) {
ui.innerHTML = `
<div class="overlay">
<div class="menu-container">
<h1 style="font-size: 2rem;">PAUSED</h1>
<p>Press P to resume.</p>
<button id="btn-resume">Resume</button>
</div>
</div>`;
document.getElementById('btn-resume').onclick = () => {
this.state.pause = false;
ui.innerHTML = '';
};
} else {
ui.innerHTML = '';
}
}
handleMapEvent() {
const { x, y } = this.state.mapPos;
if (Math.random() < this.zones[this.state.currentZone].rate) {
this.startBattle();
}
if (x === 10 && y === 10) {
this.state.party.forEach(p => p.hp = p.maxHp);
this.updateHUD();
this.showNotification("Restored at the Shrine");
}
// Zone Guardian Check (at 18, 18)
if (x === 18 && y === 18) {
this.startBossBattle();
}
}
startBossBattle() {
const bossData = {
name: "Guardian of the " + this.zones[this.state.currentZone].name,
lore: "A monumental entity of pure resonance.",
type: "Flux",
hp: 500,
atk: 40,
def: 30,
spd: 10,
abilities: [4, 19, 30, 40]
};
this.state.battle = {
enemies: [new Creature(bossData)],
log: "A Guardian of the Resonance appears!",
isBoss: true
};
document.getElementById('hud').style.display = 'flex';
document.getElementById('battle-ui').style.display = 'flex';
this.renderBattleUI();
}
startBattle() {
this.state.battle = {
enemies: [new Creature({...BASE_CREATURES[Math.floor(Math.random() * 18)]})],
turn: 0,
log: "A wild creature appears!",
isBoss: false
};
document.getElementById('hud').style.display = 'flex';
document.getElementById('battle-ui').style.display = 'flex';
this.renderBattleUI();
}
renderOverworld() {
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const zone = this.zones[this.state.currentZone];
const grad = ctx.createLinearGradient(0, 0, 0, 600);
grad.addColorStop(0, '#0f172a');
grad.addColorStop(1, '#1e293b');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 800, 600);
ctx.fillStyle = zone.color + "11";
ctx.fillRect(0, 0, 800, 600);
ctx.strokeStyle = 'rgba(255,255,255,0.03)';
for (let i = 0; i < 20; i++) {
ctx.beginPath(); ctx.moveTo(i * 40, 0); ctx.lineTo(i * 40, 600); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, i * 40); ctx.lineTo(800, i * 40); ctx.stroke();
}
ctx.fillStyle = 'white';
ctx.shadowBlur = 15;
ctx.shadowColor = 'white';
ctx.beginPath();
ctx.arc(this.state.mapPos.x * 40 + 20, this.state.mapPos.y * 40 + 20, 10, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
PARTICLE_SYSTEM.draw(ctx);
}
renderBattleUI() {
const log = document.getElementById('battle-log');
log.innerText = this.state.battle.log;
const partyDisplay = document.getElementById('battle-party-display');
partyDisplay.innerHTML = '';
this.state.party.forEach((p, i) => {
const slot = document.createElement('div');
slot.className = 'creature-slot';
const hpPct = Math.max(0, (p.hp / p.maxHp) * 100);
slot.innerHTML = `
<div style="font-weight:bold">${p.name}</div>
<div class="hp-bar-outer"><div class="hp-bar-inner" style="width: ${hpPct}%"></div></div>
<div style="font-size:0.8rem">Lv ${p.level}</div>
`;
partyDisplay.appendChild(slot);
});
const enemy = this.state.battle.enemies[0];
const enemyDisplay = document.createElement('div');
enemyDisplay.style.cssText = "margin: 20px auto; text-align: center; border: 1px solid var(--primary); padding: 10px; border-radius: 10px;";
enemyDisplay.innerHTML = `
<div style="font-weight:bold; font-size: 1.5rem;">${enemy.name}</div>
<div class="hp-bar-outer" style="width: 300px; margin: 10px auto;"><div class="hp-bar-inner" style="width: ${(enemy.hp / enemy.maxHp) * 100}%"></div></div>
<div style="font-size:0.9rem;">${enemy.hp} / ${enemy.maxHp} HP</div>
`;
partyDisplay.prepend(enemyDisplay);
const grid = document.getElementById('action-grid');
grid.innerHTML = '';
const active = this.state.party[0];
if (active && active.hp > 0) {
active.abilities.forEach((a, i) => {
const btn = document.createElement('button');
btn.innerText = a.name;
btn.onclick = () => this.resolveMove(i);
grid.appendChild(btn);
});
}
const capBtn = document.getElementById('capture-btn');
capBtn.style.display = this.state.battle.isBoss ? 'none' : 'block';
}
resolveMove(abilityIdx) {
const active = this.state.party[0];
const ability = active.abilities[abilityIdx];
const enemy = this.state.battle.enemies[0];
if (!active || !enemy || active.hp <= 0) return;
const mult = getMultiplier(active.type, enemy.type);
const baseDmg = (0.4 * active.level * active.stats.atk / enemy.stats.def * ability.power);
const finalDmg = Math.floor(baseDmg * mult + (Math.random() * 5));
const taken = enemy.takeDamage(finalDmg);
document.getElementById('battle-log').innerText = `${active.name} uses ${ability.name}! Deals ${taken} damage.`;
if (enemy.hp <= 0) {
document.getElementById('battle-log').innerText = `${enemy.name} was defeated!`;
if (this.state.battle.isBoss) {
this.state.flags.bossesDefeated++;
this.state.currentZone = (this.state.currentZone + 1) % 6;
this.state.battle = null;
document.getElementById('battle-ui').style.display = 'none';
document.getElementById('hud').style.display = 'none';
this.state.gameActive = true;
this.renderOverworld();
this.saveGame();
} else {
this.state.battle.enemies.shift();
if (this.state.battle.enemies.length === 0) {
this.state.battle = null;
document.getElementById('battle-ui').style.display = 'none';
document.getElementById('hud').style.display = 'none';
this.state.gameActive = true;
this.renderOverworld();
this.saveGame();
} else {
this.renderBattleUI();
}
}
} else {
setTimeout(() => {
const e = this.state.battle.enemies[0];
const eDmg = Math.floor(e.stats.atk * 0.8);
active.hp -= eDmg;
document.getElementById('battle-log').innerText = `${e.name} attacks back for ${eDmg}!`;
this.renderBattleUI();
}, 800);
}
}
handleCapture() {
if (this.state.isCapturing) return;
this.state.isCapturing = true;
const enemy = this.state.battle.enemies[0];
const captureBtn = document.getElementById('capture-btn');
let progress = 0;
const interval = setInterval(() => {
if (!this.state.isCapturing || !enemy || enemy.hp <= 0) {
clearInterval(interval);
this.state.isCapturing = false;
return;
}
progress += 2;
captureBtn.innerText = `CAPTURING: ${Math.floor(progress)}%`;
if (progress >= 100) {
clearInterval(interval);
this.state.isCapturing = false;
if (Math.random() > 0.5) {
this.state.party.push(new Creature({...enemy}));
document.getElementById('battle-log').innerText = `Success! ${enemy.name} joined your party!`;
this.state.battle.enemies.shift();
setTimeout(() => this.renderBattleUI(), 1500);
} else {
document.getElementById('battle-log').innerText = `Capture failed!`;
progress = 0;
captureBtn.innerText = "CAPTURE";
}
}
}, 50);
}
startLoop() {
const loop = () => {
if (!this.state.pause) {
PARTICLE_SYSTEM.update();
if (this.state.battle) {
// Handle some animations or small updates here if needed
}
}
requestAnimationFrame(loop);
};
loop();
}
}
// ── const: game ──
const game = new GameEngine();
// ── const: SAVE_KEY ──
const SAVE_KEY = "vitreous_resonance_save";
// ── const: PARTICLE_SYSTEM ──
const PARTICLE_SYSTEM = {
particles: [],
init() {
this.particles = [];
for (let i = 0; i < 80; i++) {
this.particles.push({
x: Math.random() * 800,
y: Math.random() * 600,
vx: (Math.random() - 0.5) * 0.6,
vy: (Math.random() - 0.5) * 0.6,
size: Math.random() * 2 + 1,
alpha: Math.random(),
life: 100 + Math.random() * 100
});
}
},
update() {
this.particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
p.life--;
if (p.life <= 0) {
p.life = 100 + Math.random() * 100;
p.x = Math.random() * 800;
p.y = Math.random() * 600;
}
});
},
draw(ctx) {
ctx.fillStyle = "rgba(255, 255, 255, 0.2)";
this.particles.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
}
};
// ── main ──
// The main block handles the initial render and updates
// In this simple setup, GameEngine.init() handles it.
// However, we need to ensure the DOM is ready.
window.onload = () => {
game.init();
};
</script>
</body>
</html>
<!-- agent-meta {"model":"google/gemma-4-12b-qat","provider":"lmstudio","persona":"composer","sessionId":"55d34ab3-f51a-4d44-97ab-a01c15773f52","tokensIn":208121,"tokensOut":28755,"tokensTotal":236876,"turns":14,"toolCalls":13,"failedToolCalls":3,"timestamp":"2026-07-13T23:52:36.317Z"} -->