← Creature Collection results

Creature Collection

GPT 5.6 Sol · typescript

Initial view of Creature Collection

Browser check: Artifact runtime error. This is a technical check, not a quality review.

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

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 53,754 bytes · SHA-256 cb93cc5df070
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Veylbound: Echoes of the Lumenwild</title>
<style>
/* ── css: foundation ── */
*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#071018;color:#eefbf7;font-family:ui-rounded,"Segoe UI",system-ui,sans-serif}button{font:inherit;color:inherit;cursor:pointer}button:focus-visible{outline:2px solid #8dffe0;outline-offset:3px}:root{--mint:#8dffe0;--violet:#a98dff;--ink:#071018;--glass:rgba(10,25,34,.78);--line:rgba(185,255,235,.18)}#app{position:relative;width:100%;height:100%;isolation:isolate}#game{position:absolute;inset:0;width:100%;height:100%;display:block}#grain{position:absolute;inset:0;pointer-events:none;opacity:.18;mix-blend-mode:overlay;background-image:repeating-linear-gradient(0deg,transparent 0 3px,rgba(255,255,255,.025) 4px);z-index:2}.hidden{display:none!important}.screen{display:none;position:absolute;inset:0;z-index:20;align-items:center;justify-content:center}.screen.active{display:flex}button{border:1px solid var(--line);background:rgba(17,40,50,.72);border-radius:12px;padding:11px 18px;transition:.2s transform,.2s background,.2s border}button:hover{transform:translateY(-2px);background:rgba(34,69,77,.85);border-color:rgba(141,255,224,.45)}button.primary{color:#07211d;background:linear-gradient(135deg,var(--mint),#b9ffd5);border:0;font-weight:800;box-shadow:0 8px 34px rgba(86,255,209,.22)}.eyebrow{font-size:11px;letter-spacing:.32em;color:var(--mint);font-weight:800;text-transform:uppercase}.micro{font-size:11px;color:#97aaa7;letter-spacing:.08em}
/* ── css: screens ── */
#titleScreen{background:radial-gradient(circle at 50% 45%,rgba(47,120,119,.26),transparent 38%),linear-gradient(145deg,#050b14,#0d2226 52%,#111126)}.aurora,.aurora:before,.aurora:after{position:absolute;content:"";width:58vw;height:58vw;border:1px solid rgba(141,255,224,.13);border-radius:41% 59% 47% 53%;animation:orbit 18s linear infinite}.aurora:before{width:80%;height:80%;left:10%;top:10%;animation-duration:12s;border-color:rgba(169,141,255,.16)}.aurora:after{width:55%;height:55%;left:22%;top:22%;animation-direction:reverse}.title-wrap{text-align:center;z-index:1}.title-sigil{font-size:70px;color:var(--mint);filter:drop-shadow(0 0 20px var(--mint));animation:pulse 3s ease-in-out infinite}.title-wrap h1{margin:2px;font-size:clamp(52px,9vw,112px);line-height:.8;letter-spacing:-.07em;font-weight:300}.title-wrap h1 span{font-weight:900;color:#caffee}.tagline{color:#b9c9c6;font-size:16px}.title-actions{display:flex;gap:10px;justify-content:center;margin:30px}.title-actions button{min-width:150px}.panel{position:relative;background:linear-gradient(145deg,rgba(12,31,39,.94),rgba(12,18,35,.92));border:1px solid var(--line);border-radius:24px;padding:30px;box-shadow:0 24px 100px #0009,0 0 70px rgba(91,255,216,.06);backdrop-filter:blur(18px);max-height:90vh;overflow:auto}.panel.wide{width:min(1000px,92vw);text-align:center}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:22px}.starter-card{padding:20px;text-align:left;border:1px solid var(--line);border-radius:18px;background:rgba(255,255,255,.035);transition:.25s}.starter-card:hover{transform:translateY(-7px);border-color:var(--accent);box-shadow:0 18px 45px #0007}.creature-orb{height:145px;border-radius:50%;margin:auto;max-width:145px;background:radial-gradient(circle at 38% 32%,#fff,var(--accent) 8%,color-mix(in srgb,var(--accent),#09121b 55%) 34%,transparent 70%);filter:drop-shadow(0 15px 20px #0008)}.starter-card h3{font-size:24px;margin:8px 0 2px}.affinity{color:var(--accent);font-size:12px;text-transform:uppercase;letter-spacing:.16em}@keyframes orbit{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.65;transform:scale(.94)}}
/* ── css: hudmenus ── */
#hud{position:absolute;z-index:8;left:18px;right:18px;top:16px;display:flex;align-items:center;justify-content:space-between;pointer-events:none}.brand,.objectives,.hud-actions{display:flex;align-items:center;gap:10px;background:var(--glass);border:1px solid var(--line);border-radius:14px;padding:9px 13px;backdrop-filter:blur(12px);pointer-events:auto}.brand .sigil{font-size:24px;color:var(--mint)}.brand b{display:block;font-size:12px;letter-spacing:.15em}.brand small{display:block;color:#9eb3ae}.objectives{font-size:12px}.objectives span+span{border-left:1px solid var(--line);padding-left:10px}.hud-actions button{padding:7px 10px;font-size:12px}.modal{position:absolute;inset:0;z-index:30;display:flex;align-items:center;justify-content:center;background:rgba(1,8,14,.7);backdrop-filter:blur(8px)}.menu{width:min(850px,94vw);height:min(650px,88vh)}.menu-head{display:flex;justify-content:space-between;align-items:center}.menu-head h2{font-size:30px;margin:0}.menu-head button{font-size:22px;padding:3px 12px}#tabs{display:flex;gap:8px;margin:20px 0;border-bottom:1px solid var(--line);padding-bottom:12px}#tabs .active{background:#8dffe0;color:#06251f}.team-grid,.index-grid,.bag-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px}.mini-card{padding:12px;border:1px solid var(--line);border-radius:13px;background:#ffffff08}.mini-card h3{margin:0 0 5px}.bar{height:7px;background:#071117;border-radius:10px;overflow:hidden}.bar i{display:block;height:100%;background:linear-gradient(90deg,#55e59f,#a5ffd7);transition:width .6s}.locked{filter:grayscale(1);opacity:.36}#dialogue{background:radial-gradient(circle,var(--vig,#193d43),rgba(0,4,8,.9))}.dialogue-box{position:absolute;bottom:8%;width:min(760px,90vw);padding:25px;border:1px solid var(--line);border-radius:18px;background:rgba(6,17,25,.92);box-shadow:0 30px 100px #000}.dialogue-box #speaker{color:var(--mint);font-size:11px;letter-spacing:.25em;text-transform:uppercase}.dialogue-box #dialogueText{font-size:19px;line-height:1.55;margin:10px 0 18px}#toast{position:absolute;z-index:50;left:50%;bottom:80px;transform:translate(-50%,20px);opacity:0;background:#07151de8;border:1px solid var(--line);border-radius:12px;padding:12px 20px;transition:.3s;pointer-events:none}#toast.show{opacity:1;transform:translate(-50%,0)}#footer{position:absolute;bottom:16px;left:50%;transform:translateX(-50%);z-index:7;display:flex;gap:30px;background:var(--glass);border:1px solid var(--line);border-radius:12px;padding:8px 16px;color:#aebfbc;font-size:12px}
/* ── css: battle ── */
#battleUI{position:absolute;inset:0;z-index:9;pointer-events:none}.combatants{position:absolute;display:flex;gap:10px}.enemies{right:5%;top:13%}.ally{left:5%;bottom:30%}.combat-card{width:210px;padding:13px;background:rgba(5,15,24,.88);border:1px solid var(--line);border-radius:14px;box-shadow:0 15px 45px #0007}.combat-card.active{border-color:var(--mint);box-shadow:0 0 24px rgba(141,255,224,.2)}.combat-card strong{display:flex;justify-content:space-between}.hp-row{font-size:10px;color:#9eb4b0;margin-top:7px}.hp{height:9px;background:#061016;border-radius:8px;overflow:hidden;margin-top:4px}.hp i{display:block;height:100%;background:linear-gradient(90deg,#48db8d,#a6ffcb);transition:width .45s}.hp i.low{background:linear-gradient(90deg,#ff5c77,#ff9c75)}#battleLog{position:absolute;top:8%;left:50%;transform:translateX(-50%);background:#061019c9;border:1px solid var(--line);border-radius:10px;padding:9px 18px;color:#d9ebe7;font-size:13px;max-width:45%;text-align:center}#movePanel{pointer-events:auto;position:absolute;right:4%;bottom:5%;width:min(590px,64vw);display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:13px;background:rgba(5,14,22,.91);border:1px solid var(--line);border-radius:17px;backdrop-filter:blur(15px)}.move{position:relative;text-align:left;padding:12px 14px}.move b,.move small{display:block}.move small{color:#a7bbb7;margin-top:3px}.move em{position:absolute;right:10px;top:10px;font-style:normal;font-size:10px;color:var(--mint)}.utility-row{grid-column:1/-1;display:flex;gap:8px}.utility-row button{flex:1}.capture{width:min(520px,90vw);text-align:center}#rhythm{height:30px;position:relative;background:#071018;border:1px solid var(--line);border-radius:30px;margin:25px 0;overflow:hidden}#sweet{position:absolute;left:42%;width:16%;height:100%;background:linear-gradient(90deg,transparent,#8dffe099,transparent)}#needle{position:absolute;width:4px;height:100%;background:white;box-shadow:0 0 12px var(--mint);left:0}.damage-float{position:absolute;font-weight:900;font-size:26px;color:#fff;animation:floatUp .85s forwards;text-shadow:0 3px 10px #000}.shake{animation:shake .25s}.flash{animation:flash .35s}@keyframes floatUp{to{transform:translateY(-70px) scale(1.2);opacity:0}}@keyframes shake{25%{transform:translateX(-7px)}75%{transform:translateX(7px)}}@keyframes flash{50%{filter:brightness(2)}}
/* ── css: responsive ── */
#touch{position:absolute;z-index:10;left:18px;bottom:18px;width:150px;height:150px;grid-template:repeat(3,1fr)/repeat(3,1fr);gap:5px}#touch button{padding:0;background:#081923b5;backdrop-filter:blur(6px)}#touch [data-dir=up]{grid-column:2}#touch [data-dir=left]{grid-row:2;grid-column:1}#touch [data-dir=down]{grid-row:2;grid-column:2}#touch [data-dir=right]{grid-row:2;grid-column:3}@media(pointer:coarse),(max-width:700px){#touch:not(.hidden){display:grid!important}.cards{grid-template-columns:1fr}.panel.wide{height:90vh}.starter-card{display:grid;grid-template-columns:100px 1fr;column-gap:15px}.creature-orb{grid-row:1/5;height:90px}.objectives{display:none}.brand small{max-width:100px;white-space:nowrap;overflow:hidden}.hud-actions #btnSave{display:none}#footer{display:none!important}.enemies{top:17%;right:2%}.combat-card{width:155px;font-size:11px}.ally{left:2%;bottom:40%}#movePanel{width:96%;right:2%;bottom:2%;grid-template-columns:1fr 1fr}.move{padding:9px}.title-wrap h1{font-size:58px}}
</style>
</head>
<body>
<main id="app"><canvas id="game" aria-label="Veylbound game canvas"></canvas><div id="grain"></div><header id="hud" class="hidden"><div class="brand"><span class="sigil">◇</span><div><b>VEYLBOUND</b><small id="zoneName">Morrowfen Verge</small></div></div><div class="objectives"><span id="anchorCount">Anchors 0/4</span><span id="bindCount">Kindred 1/8</span></div><div class="hud-actions"><button id="btnMute" aria-label="Mute">♫</button><button id="btnSave">Save</button><button id="btnMenu">Codex</button></div></header><section id="titleScreen" class="screen active"><div class="aurora"></div><div class="title-wrap"><div class="title-sigil">◇</div><p class="eyebrow">A LUMENWILD CHRONICLE</p><h1>VEYL<span>BOUND</span></h1><p class="tagline">Where wandering lights remember your name.</p><div class="title-actions"><button id="newGame" class="primary">New Binding</button><button id="continueGame">Continue</button></div><p class="micro">Move with WASD / arrows · touch controls supported</p></div></section><section id="starterScreen" class="screen"><div class="panel wide"><p class="eyebrow">THE FIRST RESONANCE</p><h2>Choose a kindred light</h2><p>Only one answers now. Others may be found beyond the hush.</p><div id="starterCards" class="cards"></div></div></section><div id="dialogue" class="modal hidden"><div class="vignette"></div><div class="dialogue-box"><p id="speaker"></p><div id="dialogueText"></div><button id="dialogueNext">Continue</button></div></div><div id="battleUI" class="hidden"><div id="enemyCards" class="combatants enemies"></div><div id="battleLog">The wild stirs...</div><div id="allyCard" class="combatants ally"></div><div id="movePanel"></div></div><div id="captureModal" class="modal hidden"><div class="panel capture"><p class="eyebrow">RESONANCE WINDOW</p><h2>Bind the wandering echo</h2><div id="rhythm"><div id="sweet"></div><div id="needle"></div></div><p>Tap when the needle crosses the luminous band.</p><button id="rhythmTap" class="primary">Attune</button></div></div><div id="menuModal" class="modal hidden"><div class="panel menu"><div class="menu-head"><h2>Wayfarer Codex</h2><button id="closeMenu">×</button></div><nav id="tabs"><button data-tab="team" class="active">Team</button><button data-tab="bag">Satchel</button><button data-tab="codex">Index</button><button data-tab="map">Map</button></nav><div id="menuContent"></div></div></div><div id="toast"></div><div id="touch" class="hidden"><button data-dir="up">▲</button><button data-dir="left">◀</button><button data-dir="down">▼</button><button data-dir="right">▶</button></div><footer id="footer" class="hidden"><span id="hint">Follow the pale path to the story beacon.</span><span id="levelReadout">Lv. 5</span></footer></main>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./data":"src/data.ts","./state":"src/state.ts","./battle":"src/battle.ts","./audio":"src/audio.ts"},"src/state.ts":{"./data":"src/data.ts"},"src/render.ts":{"./state":"src/state.ts","./data":"src/data.ts"},"src/battle.ts":{"./data":"src/data.ts","./state":"src/state.ts","./render":"src/render.ts","./audio":"src/audio.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
const data_1 = require("./data");
const state_1 = require("./state");
const battle_1 = require("./battle");
const audio_1 = require("./audio");
const $ = (s) => document.querySelector(s);
const title = $('#titleScreen'), starter = $('#starterScreen'), hud = $('#hud'), footer = $('#footer'), touch = $('#touch'), toast = $('#toast');
let active = true, lastMove = 0;
const showToast = (s) => { toast.textContent = s; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 1800); };
const updateHud = () => { $('#zoneName').textContent = data_1.zones[state_1.state.zone].name; $('#anchorCount').textContent = `Anchors ${state_1.state.anchors}/4`; $('#bindCount').textContent = `Kindred ${new Set(state_1.state.caught).size}/8`; $('#levelReadout').textContent = `Lv. ${state_1.state.team[0]?.level || 1}`; const goals = ['Walk with WASD or the touch compass.', 'Reach the violet STORY beacon.', 'Weaken a wild echo, then choose Bind.', 'Defeat four Anchor guardians.', 'Gather eight unique kindred.', 'The Meridian is open. Free roam deepens.']; $('#hint').textContent = goals[Math.min(state_1.state.tutorial, 5)]; };
const dialog = (speaker, text, color = '#193d43', done = () => { }) => { active = false; const d = $('#dialogue'); d.style.setProperty('--vig', color); $('#speaker').textContent = speaker; $('#dialogueText').textContent = text; d.classList.remove('hidden'); $('#dialogueNext').onclick = () => { d.classList.add('hidden'); active = true; done(); }; };
const startWorld = () => { title.classList.remove('active'); starter.classList.remove('active'); hud.classList.remove('hidden'); footer.classList.remove('hidden'); touch.classList.remove('hidden'); active = true; (0, audio_1.startAmbience)(); updateHud(); };
const newGame = () => { (0, state_1.setState)((0, state_1.freshState)()); title.classList.remove('active'); starter.classList.add('active'); renderStarters(); };
const renderStarters = () => { $('#starterCards').innerHTML = [0, 1, 2].map(id => { const s = data_1.species[id]; return `<button class="starter-card" data-id="${id}" style="--accent:${s.color}"><div class="creature-orb"></div><div><span class="affinity">${s.aff.join(' · ')}</span><h3>${s.name}</h3><p>${s.lore}</p><small>HP ${s.stats[0]} · ATK ${s.stats[1]} · DEF ${s.stats[2]} · SPD ${s.stats[3]}</small></div></button>`; }).join(''); document.querySelectorAll('.starter-card').forEach(c => c.onclick = () => chooseStarter(Number(c.dataset.id))); };
const chooseStarter = (id) => { state_1.state.team = [(0, state_1.makeUnit)(id, 5)]; state_1.state.caught = [id]; state_1.state.seen = [id]; (0, state_1.save)(); startWorld(); dialog('Ilyra, Keeper of Quiet Roads', `This is ${data_1.species[id].name}. Not a possession—a promise. Move toward the violet beacon. The wild will test whether your rhythms agree.`, '#173e3e', () => { state_1.state.tutorial = 1; updateHud(); }); };
const move = (dx, dy) => { const now = performance.now(); if (!active || !state_1.state.started || now - lastMove < 110 || !$('#battleUI').classList.contains('hidden'))
    return; lastMove = now; state_1.state.x = Math.max(0, Math.min(23, state_1.state.x + dx)); state_1.state.y = Math.max(0, Math.min(17, state_1.state.y + dy)); state_1.state.steps++; (0, audio_1.sfx)('step'); interact(); if (state_1.state.steps % 12 === 0)
    (0, state_1.save)(); };
const near = (x, y) => Math.abs(state_1.state.x - x) + Math.abs(state_1.state.y - y) <= 0;
const interact = () => { if (near(5, 8)) {
    (0, state_1.healAll)();
    state_1.state.lastShrine = { x: 5, y: 8, zone: state_1.state.zone };
    (0, state_1.save)();
    showToast('Shrine restored the chorus · progress saved');
    return;
} if (near(12, 5)) {
    storyNode();
    return;
} if (near(20, 9)) {
    gate();
    return;
} const z = data_1.zones[state_1.state.zone]; if (Math.random() < z.rate * (state_1.state.postgame ? 1.3 : 1)) {
    active = false;
    const count = Math.random() < .22 ? 2 : 1, ids = Array.from({ length: count }, () => z.wild[Math.floor(Math.random() * z.wild.length)]), lv = 3 + state_1.state.zone * 4 + Math.floor(Math.random() * 4) + (state_1.state.postgame ? 8 : 0);
    ids.forEach(i => { if (!state_1.state.seen.includes(i))
        state_1.state.seen.push(i); });
    (0, battle_1.startBattle)(ids, ids.map(() => lv), false, () => { active = true; if (state_1.state.tutorial < 3) {
        state_1.state.tutorial = 3;
        dialog('Ilyra', `A binding is easiest when an echo is weakened. In battle, choose Bind and tap while the needle crosses the luminous band.`, '#23465b');
    } checkGoals(); updateHud(); });
} };
const storyNode = () => { if (state_1.state.story > state_1.state.zone) {
    showToast('The story beacon rests in silence.');
    return;
} const lines = [['Ilyra', 'The Anchors once kept every wandering light in one shared rhythm. Something taught them solitude. Find the guardian at the amber gate.'], ['Coal Cantor', 'Flame is not hunger. It is memory refusing the cold. Prove your chorus can carry both.'], ['The Reflection Without Face', 'You have gathered lives, not trophies. Every bond changes both voices. Is that why the Meridian fears you?'], ['Old Verdigris', 'Four roots hold the world-song. Three strain. One floats beyond the sky. Go—wake what gravity forgot.'], ['Orrery Maw', 'The Anchors answer you. The Pale Meridian opens for a chorus of eight, and no fewer.'], ['Ilyra', 'The wider wild remembers your steps. Wander now; echoes will rise stronger, and your bindings may yet transform.']]; const l = lines[state_1.state.zone]; dialog(l[0], l[1], data_1.zones[state_1.state.zone].gradient[0], () => { state_1.state.story = Math.max(state_1.state.story, state_1.state.zone + 1); if (state_1.state.tutorial === 1)
    state_1.state.tutorial = 2; (0, state_1.save)(); updateHud(); }); };
const gate = () => { if (state_1.state.story <= state_1.state.zone) {
    showToast('The gate is mute. Visit the story beacon first.');
    return;
} if (state_1.state.zone < 4 && state_1.state.anchors <= state_1.state.zone) {
    active = false;
    const bossIds = [15, 7, 16, 9, 5], id = bossIds[state_1.state.zone];
    (0, battle_1.startBattle)([id], [9 + state_1.state.zone * 6], true, (won) => { if (!won) {
        active = true;
        updateHud();
        return;
    } state_1.state.anchors = Math.min(4, state_1.state.anchors + 1); state_1.state.unlocked = Math.max(state_1.state.unlocked, state_1.state.zone + 1); state_1.state.zone++; state_1.state.x = 5; state_1.state.y = 8; active = true; permanentBonus(); dialog('Anchor Resonance', `The ${data_1.zones[state_1.state.zone - 1].guardian} yields. Every bound kindred gains a lasting measure of vitality. ${data_1.zones[state_1.state.zone].name} opens.`, '#533d62'); (0, state_1.save)(); updateHud(); });
    return;
} if (state_1.state.zone === 4 && state_1.state.anchors >= 4) {
    if (new Set(state_1.state.caught).size < 8) {
        showToast(`The Meridian asks for 8 unique voices (${new Set(state_1.state.caught).size}/8).`);
        return;
    }
    state_1.state.zone = 5;
    state_1.state.unlocked = 5;
    state_1.state.x = 5;
    state_1.state.y = 8;
    state_1.state.postgame = true;
    state_1.state.tutorial = 5;
    dialog('The Pale Meridian', 'Four Anchors and eight voices braid into a path no single will could open. The Lumenwild expands. Its stronger echoes now welcome your return.', '#56506b');
    (0, state_1.save)();
    updateHud();
    return;
} if (state_1.state.zone < state_1.state.unlocked) {
    state_1.state.zone++;
    state_1.state.x = 5;
    state_1.state.y = 8;
    updateHud();
}
else
    showToast('The way beyond has not opened.'); };
const permanentBonus = () => state_1.state.team.concat(state_1.state.reserve).forEach(u => { u.maxHp += 4; u.hp = u.maxHp; u.atk += 1; u.def += 1; });
const checkGoals = () => { if (new Set(state_1.state.caught).size >= 8 && state_1.state.anchors >= 4) {
    state_1.state.postgame = true;
    state_1.state.tutorial = 5;
    showToast('Final objective complete · Meridian free roam awakened');
} };
const renderMenu = (tab) => { const c = $('#menuContent'); if (tab === 'team') {
    c.innerHTML = `<div class="team-grid">${state_1.state.team.map((u, i) => { const s = data_1.species[u.sid], pct = u.hp / u.maxHp * 100; return `<div class="mini-card"><h3>${s.name} <small>Lv.${u.level}</small></h3><span class="affinity">${s.aff.join(' · ')}</span><p>${u.hp}/${u.maxHp} HP</p><div class="bar"><i style="width:${pct}%"></i></div><p>${u.moveIds.map(id => data_1.moves[id].name).join(' · ')}</p>${s.transform !== undefined ? `<button data-transform="${i}">Transform</button>` : ''}</div>`; }).join('')}</div>`;
    c.querySelectorAll('[data-transform]').forEach(b => b.onclick = () => transform(Number(b.dataset.transform)));
}
else if (tab === 'bag')
    c.innerHTML = `<div class="bag-grid">${Object.entries(state_1.state.inventory).map(([n, v]) => `<div class="mini-card"><h3>${n}</h3><p>Quantity ${v}</p><small>${n.includes('Loop') || n.includes('Coil') ? 'A crafted lattice for binding weakened echoes.' : n === 'Menddew' ? 'Restores 25 vitality outside battle.' : 'A rare catalyst for kindred transformation.'}</small></div>`).join('')}</div>`;
else if (tab === 'codex')
    c.innerHTML = `<p>${state_1.state.caught.length} bound · ${state_1.state.seen.length} witnessed · 27 total forms</p><div class="index-grid">${data_1.species.map(s => `<div class="mini-card ${state_1.state.seen.includes(s.id) || state_1.state.caught.includes(s.id) ? '' : 'locked'}"><h3>${state_1.state.seen.includes(s.id) || state_1.state.caught.includes(s.id) ? s.name : 'Unheard Echo'}</h3><span class="affinity">${state_1.state.caught.includes(s.id) ? s.aff.join(' · ') : '???'}</span><p>${state_1.state.caught.includes(s.id) ? s.lore : 'Its resonance has not entered your memory.'}</p></div>`).join('')}</div>`;
else
    c.innerHTML = `<div class="index-grid">${data_1.zones.map((z, i) => `<button class="mini-card ${i > state_1.state.unlocked ? 'locked' : ''}" data-zone="${i}" ${i > state_1.state.unlocked ? 'disabled' : ''}><h3>${z.name}</h3><p>${i === state_1.state.zone ? 'Current location' : i <= state_1.state.unlocked ? 'Travel from a shrine' : 'Anchor-sealed'}</p></button>`).join('')}</div>`; c.querySelectorAll('[data-zone]').forEach(b => b.onclick = () => { state_1.state.zone = Number(b.dataset.zone); state_1.state.x = 5; state_1.state.y = 8; $('#menuModal').classList.add('hidden'); active = true; updateHud(); }); };
const transform = (i) => { const u = state_1.state.team[i], s = data_1.species[u.sid]; if (s.transform === undefined)
    return; const catalyst = ['Sunshard', 'Tideglass', 'Verdant Knot'][u.sid % 3]; const req = 16 + u.sid % 7; if (u.level < req || !state_1.state.inventory[catalyst]) {
    showToast(`Requires level ${req} and ${catalyst}.`);
    return;
} state_1.state.inventory[catalyst]--; const oldHp = u.maxHp; const nu = (0, state_1.makeUnit)(s.transform, u.level); nu.uid = u.uid; nu.xp = u.xp; state_1.state.team[i] = nu; if (!state_1.state.caught.includes(nu.sid))
    state_1.state.caught.push(nu.sid); showToast(`${s.name} becomes ${data_1.species[nu.sid].name}!`); (0, audio_1.sfx)('capture'); renderMenu('team'); (0, state_1.save)(); };
const openMenu = () => { if (!active)
    return; active = false; $('#menuModal').classList.remove('hidden'); renderMenu('team'); };
addEventListener('keydown', e => { const k = e.key.toLowerCase(); if (k === 'w' || e.key === 'ArrowUp')
    move(0, -1); if (k === 's' || e.key === 'ArrowDown')
    move(0, 1); if (k === 'a' || e.key === 'ArrowLeft')
    move(-1, 0); if (k === 'd' || e.key === 'ArrowRight')
    move(1, 0); if (k === 'escape' && !$('#menuModal').classList.contains('hidden')) {
    $('#menuModal').classList.add('hidden');
    active = true;
} });
document.querySelectorAll('#touch [data-dir]').forEach(b => { const d = b.dataset.dir; b.onpointerdown = () => move(d === 'left' ? -1 : d === 'right' ? 1 : 0, d === 'up' ? -1 : d === 'down' ? 1 : 0); });
$('#newGame').onclick = newGame;
$('#continueGame').onclick = () => { if ((0, state_1.load)())
    startWorld();
else
    showToast('No binding has been saved yet.'); };
$('#btnMenu').onclick = openMenu;
$('#closeMenu').onclick = () => { $('#menuModal').classList.add('hidden'); active = true; };
$('#btnSave').onclick = () => { (0, state_1.save)(); showToast('Binding recorded.'); };
$('#btnMute').onclick = () => { $('#btnMute').textContent = (0, audio_1.toggleMute)() ? '♪' : '♫'; };
$('#rhythmTap').onclick = battle_1.resolveCapture;
document.querySelectorAll('#tabs [data-tab]').forEach(b => b.onclick = () => { document.querySelectorAll('#tabs button').forEach(x => x.classList.remove('active')); b.classList.add('active'); renderMenu(b.dataset.tab || 'team'); });
function start() { const has = !!localStorage.getItem('veylbound-save'); $('#continueGame').disabled = !has; renderStarters(); }
start();
};

// ── module: src/data.ts ──
__mods["src/data.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.multiplier = exports.zones = exports.species = exports.moves = exports.affinities = void 0;
exports.affinities = ['Embervein', 'Tidemind', 'Thornwake', 'Gloam', 'Prism', 'Aether', 'Husk'];
const moveNames = ['Cinder Murmur', 'Flare Rake', 'Singe Halo', 'Coalheart Rush', 'Pyre Spiral', 'Sunscar', 'Soot Veil', 'Brine Chime', 'Undertow Palm', 'Rime Current', 'Pelagic Mend', 'Glasswave', 'Dewpulse', 'Deepwell', 'Briar Knuckle', 'Pollen Dart', 'Rootward', 'Canopy Crash', 'Sap Renewal', 'Needle Waltz', 'Umbral Pin', 'Nightfold', 'Dusk Siphon', 'Hush Claw', 'Eclipse Step', 'Shade Bloom', 'Facet Ray', 'Chroma Skip', 'Mirror Ward', 'Iridescent Arc', 'Gleam Restore', 'Sevenfold', 'Void Hum', 'Astral Nudge', 'Comet Thread', 'Orbit Break', 'Quickening', 'Star Choir', 'Shell Toll', 'Pale Bastion', 'Marrow Echo', 'Dust Bite', 'Stillness', 'Stone Sleep', 'Null Crown'];
const effects = ['burn', 'none', 'barrier', 'priority', 'multi', 'burn', 'barrier', 'shock', 'none', 'frost', 'heal', 'multi', 'regen', 'heal', 'none', 'frost', 'barrier', 'multi', 'regen', 'speed', 'shock', 'barrier', 'heal', 'none', 'priority', 'regen', 'none', 'speed', 'barrier', 'multi', 'heal', 'frost', 'shock', 'none', 'multi', 'priority', 'speed', 'regen', 'none', 'barrier', 'shock', 'none', 'frost', 'heal', 'barrier'];
exports.moves = moveNames.map((name, i) => { const a = exports.affinities[Math.floor(i / 7)] || 'Husk'; const e = effects[i]; return { name, power: e === 'heal' || e === 'barrier' || e === 'regen' || e === 'speed' ? 0 : 12 + (i % 7) * 3, acc: 88 + (i % 4) * 4, aff: a, cost: 2 + (i % 4), effect: e, desc: e === 'none' ? 'A clean resonance strike.' : `May invoke ${e}.` }; });
const base = [
    ['Cindlet', ['Embervein'], [46, 15, 10, 13], 'It sleeps beneath warm stones and records every footfall as a tiny spark.', '#ff795e', 18], ['Mareel', ['Tidemind'], [52, 11, 13, 12], 'A ribbon-bodied listener that gathers unsaid thoughts in its silver fins.', '#66dffc', 19], ['Thrumble', ['Thornwake'], [55, 12, 15, 8], 'Moss grows in the quiet hollows of its back, glowing when rain approaches.', '#73e099', 20], ['Vespernip', ['Gloam'], [42, 16, 9, 16], 'Its many ears hear the moment between a question and its answer.', '#9b7be8', 21], ['Glintora', ['Prism'], [44, 14, 10, 17], 'It bends stray lumen into private constellations beneath its translucent hide.', '#ffc4f2', 22], ['Orryx', ['Aether'], [40, 13, 9, 20], 'A weightless grazer that steps on currents too fine for ordinary senses.', '#e6ffff', 23], ['Caskin', ['Husk'], [62, 10, 18, 6], 'Its shell bears blank runes which briefly fill near forgotten places.', '#c9bba2', 24], ['Flarrow', ['Embervein', 'Aether'], [43, 17, 8, 18], 'It launches itself between heated air pockets with impossible precision.', '#ff9b54', 25], ['Drizzel', ['Tidemind', 'Prism'], [48, 12, 12, 15], 'Droplets orbit its brow, each reflecting a different possible path.', '#62b9ff', 26], ['Gremroot', ['Thornwake', 'Husk'], [68, 12, 19, 5], 'Whole gardens travel slowly in the furrows left by its patient feet.', '#99c465'], ['Nocturnel', ['Gloam', 'Tidemind'], [50, 15, 11, 15], 'It drinks moonless reflections and returns them as soothing blue mist.', '#736de3'], ['Kaleidawn', ['Prism', 'Embervein'], [45, 17, 9, 17], 'The plates on its neck ring in colors rather than sound.', '#ff91bc'], ['Aeralume', ['Aether', 'Prism'], [43, 14, 8, 22], 'It knots the upper wind into luminous nests no storm can scatter.', '#bff8ff'], ['Dolmenwisp', ['Husk', 'Gloam'], [64, 11, 20, 7], 'A patient sentinel assembled by dust around a stubborn spark.', '#aa9eb8'], ['Bramblet', ['Thornwake', 'Embervein'], [56, 16, 12, 10], 'When threatened, its seed-crown bursts into harmless warm lanterns.', '#b7df64'], ['Siltwhorl', ['Tidemind', 'Husk'], [61, 12, 17, 8], 'It filters old songs from riverbeds and hums them backward at dusk.', '#70babb'], ['Mirthray', ['Prism', 'Gloam'], [47, 16, 9, 19], 'Its smile is a band of refracted lumen, visible even when it is not.', '#dc8aff'], ['Quillune', ['Aether', 'Thornwake'], [49, 15, 11, 18], 'Feathered seeds drift from its tail and point toward hidden water.', '#9fffd3']
];
exports.species = base.map((b, i) => ({ id: i, name: b[0], aff: b[1], stats: b[2], lore: b[3], color: b[4], learn: [(i % 6) * 7, (i % 6) * 7 + 1, (i % 6) * 7 + 2, (i % 6) * 7 + 3], transform: b[5], condition: b[5] ? `Reach level ${16 + i % 7} with a ${['Sunshard', 'Tideglass', 'Verdant Knot'][i % 3]}.` : undefined }));
const forms = [['Cindervault', 0, 'Embervein', 'A walking hearth whose flame keeps memories warm without consuming them.'], ['Marequill', 1, 'Tidemind', 'Its vast fins comb grief from water and leave bright calm in their wake.'], ['Grovebell', 2, 'Thornwake', 'Each step rings through roots miles away, warning small lives of danger.'], ['Vesperaith', 3, 'Gloam', 'It folds silence into wings and shelters sleepers beneath them.'], ['Glintarch', 4, 'Prism', 'Seven soft colors orbit its crown, each drawn from a promise kept.'], ['Orrystral', 5, 'Aether', 'It walks the seam of the sky and returns carrying unfamiliar starlight.'], ['Caskolith', 6, 'Husk', 'The runes on its mountain shell now speak only of endurance.'], ['Flarevant', 7, 'Aether', 'Its flight draws warm calligraphy that lingers until morning.'], ['Drizzoracle', 8, 'Prism', 'Every orbiting drop holds one true glimpse of the next rainfall.']];
forms.forEach((f, j) => exports.species.push({ id: 18 + j, name: f[0], aff: [f[2], base[f[1]][1][0]], stats: base[f[1]][2].map((x, k) => x + 10 + (k === 1 ? 4 : 0)), lore: f[3], color: base[f[1]][4], learn: [28 + j % 7, 35 + j % 7, 42 + j % 3, 31], condition: 'Transformed kindred.' }));
exports.zones = [
    { name: 'Morrowfen Verge', gradient: ['#102c31', '#102039'], rate: .09, story: 'The first Anchor hums beneath the fen. The Lumenwild remembers through those willing to listen.', guardian: 'Silt Regent', wild: [0, 1, 2, 8], particle: '#77ffe0' },
    { name: 'Cinderlace Rise', gradient: ['#4a1d2b', '#17172f'], rate: .12, story: 'Heat has braided the paths shut. Unbind the second Anchor before its echo turns to ash.', guardian: 'The Coal Cantor', wild: [0, 7, 11, 14], particle: '#ff805c' },
    { name: 'Mirrorglen', gradient: ['#243457', '#291c4a'], rate: .1, story: 'Every reflection shows a road abandoned. Choose the road that still chooses you.', guardian: 'Facet Warden', wild: [4, 8, 12, 16], particle: '#efa8ff' },
    { name: 'Rootsleep Basin', gradient: ['#173c2b', '#172319'], rate: .14, story: 'Roots clutch the third Anchor in a dream of stone. Wake it gently—or not at all.', guardian: 'Old Verdigris', wild: [2, 9, 14, 17], particle: '#93f27a' },
    { name: 'Aster Hush', gradient: ['#162b45', '#0d122d'], rate: .15, story: 'Above the cloudline, the fourth Anchor has forgotten gravity. Your chorus must call it home.', guardian: 'Orrery Maw', wild: [3, 5, 12, 17], particle: '#c3faff' },
    { name: 'Pale Meridian', gradient: ['#332d45', '#151522'], rate: .18, story: 'All four Anchors answer. The Meridian opens, not as an ending, but as a wider beginning.', guardian: 'Null Sovereign', wild: [6, 10, 13, 15, 16], particle: '#ece3cf' }
];
const multiplier = (atk, defs) => { let m = 1; for (const d of defs) {
    if (atk === 'Aether')
        m *= 1.12;
    else if (d === 'Husk')
        m *= .72;
    else if ((atk === 'Embervein' && d === 'Tidemind') || (atk === 'Tidemind' && d === 'Thornwake') || (atk === 'Thornwake' && d === 'Embervein') || (atk === 'Gloam' && (d === 'Embervein' || d === 'Thornwake')) || (atk === 'Prism' && (d === 'Gloam' || d === 'Tidemind')))
        m *= 1.55;
} return m; };
exports.multiplier = multiplier;
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.gainXp = exports.healAll = exports.load = exports.save = exports.setState = exports.state = exports.freshState = exports.makeUnit = void 0;
const data_1 = require("./data");
const makeUnit = (sid, level) => { const s = data_1.species[sid]; const scale = 1 + (level - 1) * .075; return { uid: `${sid}-${Date.now()}-${Math.random()}`, sid, level, xp: 0, maxHp: Math.floor(s.stats[0] * scale), hp: Math.floor(s.stats[0] * scale), atk: Math.floor(s.stats[1] * scale), def: Math.floor(s.stats[2] * scale), spd: Math.floor(s.stats[3] * scale), moveIds: s.learn.slice(0, 4), pp: s.learn.slice(0, 4).map(i => data_1.moves[i].cost * 4 + 8), status: [] }; };
exports.makeUnit = makeUnit;
const freshState = () => ({ started: true, x: 5, y: 8, zone: 0, unlocked: 0, anchors: 0, story: 0, team: [], reserve: [], seen: [], caught: [], inventory: { 'Glimmer Loop': 8, 'Harmonic Coil': 2, 'Sovereign Lattice': 0, 'Menddew': 4, 'Sunshard': 1, 'Tideglass': 1, 'Verdant Knot': 1 }, steps: 0, tutorial: 0, postgame: false, lastShrine: { x: 5, y: 8, zone: 0 } });
exports.freshState = freshState;
exports.state = (0, exports.freshState)();
const setState = (s) => { exports.state = s; };
exports.setState = setState;
const save = () => localStorage.setItem('veylbound-save', JSON.stringify(exports.state));
exports.save = save;
const load = () => { const raw = localStorage.getItem('veylbound-save'); if (!raw)
    return false; try {
    exports.state = JSON.parse(raw);
    return true;
}
catch {
    return false;
} };
exports.load = load;
const healAll = () => exports.state.team.forEach(u => { u.hp = u.maxHp; u.pp = u.moveIds.map(i => data_1.moves[i].cost * 4 + 8); u.status = []; });
exports.healAll = healAll;
const gainXp = (u, amount) => { u.xp += amount; let leveled = false; while (u.level < 50 && u.xp >= u.level * 18) {
    u.xp -= u.level * 18;
    u.level++;
    u.maxHp += Math.ceil(data_1.species[u.sid].stats[0] * .075);
    u.atk += Math.ceil(data_1.species[u.sid].stats[1] * .075);
    u.def += Math.ceil(data_1.species[u.sid].stats[2] * .075);
    u.spd += Math.ceil(data_1.species[u.sid].stats[3] * .075);
    u.hp = u.maxHp;
    leveled = true;
} return leveled; };
exports.gainXp = gainXp;
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sfx = exports.toggleMute = exports.startAmbience = exports.tone = void 0;
let ctx = null, muted = false, timer = 0;
const ensure = () => { ctx ?? (ctx = new AudioContext()); if (ctx.state === 'suspended')
    ctx.resume(); };
const tone = (freq, dur = .12, type = 'sine', gain = .035) => { if (muted)
    return; ensure(); const c = ctx; const o = c.createOscillator(), g = c.createGain(); o.type = type; o.frequency.value = freq; g.gain.setValueAtTime(gain, c.currentTime); g.gain.exponentialRampToValueAtTime(.0001, c.currentTime + dur); o.connect(g).connect(c.destination); o.start(); o.stop(c.currentTime + dur); };
exports.tone = tone;
const startAmbience = () => { ensure(); clearInterval(timer); let n = 0; timer = window.setInterval(() => { (0, exports.tone)([110, 138, 165, 220, 277][n++ % 5], 2.8, 'sine', .009); }, 2200); };
exports.startAmbience = startAmbience;
const toggleMute = () => { muted = !muted; return muted; };
exports.toggleMute = toggleMute;
const sfx = (kind) => { if (kind === 'hit')
    (0, exports.tone)(75, .15, 'sawtooth', .045); if (kind === 'good') {
    (0, exports.tone)(440, .18);
    setTimeout(() => (0, exports.tone)(660, .3), 100);
} if (kind === 'capture') {
    [330, 440, 550, 770].forEach((f, i) => setTimeout(() => (0, exports.tone)(f, .3), i * 100));
} if (kind === 'step')
    (0, exports.tone)(90, .04, 'triangle', .008); };
exports.sfx = sfx;
};

// ── module: src/render.ts ──
__mods["src/render.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.screenShake = exports.updateRenderEnemies = exports.setRenderMode = void 0;
const state_1 = require("./state");
const data_1 = require("./data");
const canvas = document.querySelector('#game');
const ctx = canvas.getContext('2d');
let w = 0, h = 0, dpr = 1, t = 0;
let mode = 'world';
let enemies = [];
const resize = () => { dpr = Math.min(2, devicePixelRatio || 1); w = innerWidth; h = innerHeight; canvas.width = w * dpr; canvas.height = h * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); };
addEventListener('resize', resize);
resize();
const orb = (x, y, r, color, seed) => { ctx.save(); ctx.translate(x, y); ctx.shadowBlur = 25; ctx.shadowColor = color; const g = ctx.createRadialGradient(-r * .25, -r * .3, 2, 0, 0, r); g.addColorStop(0, '#fff'); g.addColorStop(.12, color); g.addColorStop(.75, '#10202b'); g.addColorStop(1, 'transparent'); ctx.fillStyle = g; ctx.beginPath(); const lobes = 6 + seed % 4; for (let i = 0; i <= lobes; i++) {
    const a = i / lobes * Math.PI * 2;
    const rr = r * (.82 + .16 * Math.sin(i * 12.31 + seed));
    const px = Math.cos(a) * rr, py = Math.sin(a) * rr;
    if (i === 0)
        ctx.moveTo(px, py);
    else
        ctx.quadraticCurveTo(Math.cos(a - .35) * r * 1.12, Math.sin(a - .35) * r * 1.12, px, py);
} ctx.fill(); ctx.shadowBlur = 0; ctx.fillStyle = '#06131b'; ctx.beginPath(); ctx.arc(-r * .22, -r * .12, r * .07, 0, 7); ctx.arc(r * .22, -r * .12, r * .07, 0, 7); ctx.fill(); ctx.strokeStyle = '#ffffffaa'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(0, r * .08, r * .2, .2, Math.PI - .2); ctx.stroke(); ctx.restore(); };
const world = () => { const z = data_1.zones[state_1.state.zone]; const g = ctx.createLinearGradient(0, 0, w, h); g.addColorStop(0, z.gradient[0]); g.addColorStop(1, z.gradient[1]); ctx.fillStyle = g; ctx.fillRect(0, 0, w, h); const tile = Math.min(72, Math.max(48, w / 16)), ox = w / 2 - state_1.state.x * tile, oy = h / 2 - state_1.state.y * tile; ctx.save(); ctx.translate(ox, oy); for (let y = 0; y < 18; y++)
    for (let x = 0; x < 24; x++) {
        const n = Math.sin(x * 7.1 + y * 4.3 + state_1.state.zone) * .5 + .5;
        ctx.fillStyle = n > .52 ? '#ffffff08' : '#00000009';
        ctx.fillRect(x * tile + 1, y * tile + 1, tile - 2, tile - 2);
        if ((x * 13 + y * 17 + state_1.state.zone) % 29 === 0) {
            ctx.strokeStyle = z.particle + '22';
            ctx.beginPath();
            ctx.arc(x * tile + tile / 2, y * tile + tile / 2, tile * .28, 0, 7);
            ctx.stroke();
        }
    } const points = [{ x: 5, y: 8, k: 'shrine' }, { x: 12, y: 5, k: 'story' }, { x: 20, y: 9, k: 'gate' }]; points.forEach(p => { const x = p.x * tile + tile / 2, y = p.y * tile + tile / 2; ctx.shadowColor = p.k === 'shrine' ? '#8dffe0' : p.k === 'story' ? '#d0a0ff' : '#ffb471'; ctx.shadowBlur = 25; ctx.strokeStyle = ctx.shadowColor; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(x, y, 17 + Math.sin(t * .003) * 4, 0, 7); ctx.stroke(); ctx.fillStyle = ctx.shadowColor; ctx.font = '11px system-ui'; ctx.textAlign = 'center'; ctx.fillText(p.k.toUpperCase(), x, y + 36); ctx.shadowBlur = 0; }); ctx.restore(); const cx = w / 2, cy = h / 2; ctx.fillStyle = '#07131a'; ctx.beginPath(); ctx.arc(cx, cy, 18, 0, 7); ctx.fill(); ctx.strokeStyle = '#8dffe0'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(cx, cy, 13, 0, 7); ctx.stroke(); ctx.beginPath(); ctx.moveTo(cx, cy - 20); ctx.lineTo(cx + 7, cy - 31); ctx.lineTo(cx - 7, cy - 31); ctx.closePath(); ctx.fillStyle = '#8dffe0'; ctx.fill(); for (let i = 0; i < 34; i++) {
    const x = (i * 173 + t * .012 * (i % 3 + 1)) % (w + 40) - 20, y = (i * 97 + Math.sin(t * .001 + i) * 40) % h;
    ctx.globalAlpha = .15 + (i % 5) / 12;
    ctx.fillStyle = z.particle;
    ctx.fillRect(x, y, 2 + (i % 3), 2 + (i % 3));
} ctx.globalAlpha = 1; };
const battle = () => { const z = data_1.zones[state_1.state.zone]; const g = ctx.createLinearGradient(0, 0, w, h); g.addColorStop(0, z.gradient[0]); g.addColorStop(1, '#050a12'); ctx.fillStyle = g; ctx.fillRect(0, 0, w, h); ctx.strokeStyle = '#ffffff0c'; for (let i = 0; i < 12; i++) {
    ctx.beginPath();
    ctx.ellipse(w * .5, h * .72, i * 70, 20 + i * 9, 0, 0, 7);
    ctx.stroke();
} enemies.forEach((e, i) => orb(w * (.68 + i * .13), h * (.36 + i * .04), 55 - i * 5, data_1.species[e.sid].color, e.sid)); const ally = state_1.state.team.find(u => u.hp > 0); if (ally)
    orb(w * .25, h * .62, 75, data_1.species[ally.sid].color, ally.sid); for (let i = 0; i < 45; i++) {
    ctx.fillStyle = z.particle;
    ctx.globalAlpha = .1 + (i % 4) * .05;
    ctx.fillRect((i * 89 + t * .02) % w, (i * 51 + t * .008) % h, 2, 2);
} ctx.globalAlpha = 1; };
const loop = (time) => { t = time; mode === 'world' ? world() : battle(); requestAnimationFrame(loop); };
requestAnimationFrame(loop);
const setRenderMode = (m, es = []) => (mode = m, enemies = es);
exports.setRenderMode = setRenderMode;
const updateRenderEnemies = (es) => enemies = es;
exports.updateRenderEnemies = updateRenderEnemies;
const screenShake = () => { canvas.classList.remove('shake'); void canvas.offsetWidth; canvas.classList.add('shake'); };
exports.screenShake = screenShake;
};

// ── module: src/battle.ts ──
__mods["src/battle.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startBattle = exports.resolveCapture = void 0;
const data_1 = require("./data");
const state_1 = require("./state");
const render_1 = require("./render");
const audio_1 = require("./audio");
let foes = [], ally = 0, busy = false, onEnd = () => { }, needleStart = 0, needleAnim = 0;
const $ = (s) => document.querySelector(s);
const ui = $('#battleUI'), log = $('#battleLog'), enemyCards = $('#enemyCards'), allyCard = $('#allyCard'), panel = $('#movePanel');
const alive = () => state_1.state.team[ally];
const eAlive = () => foes.find(f => f.hp > 0);
const statusTick = (u) => { if (u.status.includes('burn'))
    u.hp = Math.max(0, u.hp - Math.max(2, Math.floor(u.maxHp * .06))); if (u.status.includes('regen'))
    u.hp = Math.min(u.maxHp, u.hp + Math.max(2, Math.floor(u.maxHp * .06))); };
const card = (u, enemy = false) => { const s = data_1.species[u.sid], pct = Math.max(0, u.hp / u.maxHp * 100); return `<div class="combat-card ${!enemy ? 'active' : ''}"><strong><span>${s.name}</span><span>Lv.${u.level}</span></strong><span class="affinity">${s.aff.join(' · ')}</span><div class="hp"><i class="${pct < 25 ? 'low' : ''}" style="width:${pct}%"></i></div><div class="hp-row">${u.hp} / ${u.maxHp} ${u.status.join(' ')}</div></div>`; };
const draw = () => { const a = alive(); enemyCards.innerHTML = foes.filter(f => f.hp > 0).map(f => card(f, true)).join(''); allyCard.innerHTML = a ? card(a) : ''; if (!a)
    return; panel.innerHTML = a.moveIds.map((id, i) => { const m = data_1.moves[id]; return `<button class="move" data-move="${i}" ${a.pp[i] < m.cost ? 'disabled' : ''}><b>${m.name}</b><small>${m.aff} · Pow ${m.power || '—'} · ${m.acc}%</small><em>${a.pp[i]} EN</em></button>`; }).join('') + `<div class="utility-row"><button id="switchBtn">Switch</button><button id="bagBtn">Bind</button><button id="fleeBtn">Withdraw</button></div>`; panel.querySelectorAll('[data-move]').forEach(b => b.onclick = () => turn(Number(b.dataset.move))); $('#switchBtn').onclick = switchUnit; $('#bagBtn').onclick = startCapture; $('#fleeBtn').onclick = () => finish(false, 'You withdraw safely into the hush.'); };
const floatDamage = (n, crit) => { const d = document.createElement('div'); d.className = 'damage-float'; d.textContent = `${crit ? 'CRIT · ' : ''}${n}`; d.style.cssText = `left:${65 + Math.random() * 15}%;top:${35 + Math.random() * 15}%`; ui.append(d); setTimeout(() => d.remove(), 900); };
const act = async (att, def, mi, enemy = false) => { const m = data_1.moves[att.moveIds[mi]]; if (!enemy)
    att.pp[mi] -= m.cost; log.textContent = `${data_1.species[att.sid].name} shapes ${m.name}!`; await wait(450); if (Math.random() * 100 > m.acc) {
    log.textContent = 'The resonance slips wide.';
    return;
} if (m.power) {
    const stab = data_1.species[att.sid].aff.includes(m.aff) ? 1.25 : 1;
    const mult = (0, data_1.multiplier)(m.aff, data_1.species[def.sid].aff);
    const crit = Math.random() < .09;
    const atk = att.status.includes('burn') ? att.atk * .5 : att.atk;
    const hits = m.effect === 'multi' ? 2 + Math.floor(Math.random() * 2) : 1;
    let total = 0;
    for (let i = 0; i < hits; i++) {
        const dmg = Math.max(1, Math.floor(.4 * att.level * atk / Math.max(1, def.def) * mult * stab * (crit ? 1.55 : 1) + Math.random() * 4));
        total += dmg;
    }
    if (def.status.includes('barrier')) {
        def.status = def.status.filter(x => x !== 'barrier');
        log.textContent = 'A pale barrier deflects the strike!';
    }
    else {
        def.hp = Math.max(0, def.hp - total);
        floatDamage(total, crit);
        (0, audio_1.sfx)('hit');
        (0, render_1.screenShake)();
        if (mult > 1.2) {
            document.querySelector('#game')?.classList.add('flash');
            setTimeout(() => document.querySelector('#game')?.classList.remove('flash'), 350);
            log.textContent = `A deep resonance! ${total} harm.`;
        }
    }
} if (def.hp > 0 && m.effect !== 'none' && Math.random() < .55) {
    if (m.effect === 'heal')
        att.hp = Math.min(att.maxHp, att.hp + Math.floor(att.maxHp * .25));
    else if (m.effect === 'speed')
        att.spd = Math.floor(att.spd * 1.25);
    else if (!['multi', 'priority'].includes(m.effect) && !def.status.includes(m.effect))
        def.status.push(m.effect);
} };
const wait = (n) => new Promise(r => setTimeout(r, n));
const turn = async (mi) => { if (busy)
    return; busy = true; const a = alive(), e = eAlive(); if (!a || !e) {
    busy = false;
    return;
} const em = Math.floor(Math.random() * e.moveIds.length), am = data_1.moves[a.moveIds[mi]], emv = data_1.moves[e.moveIds[em]]; let first = { u: a, d: e, mi, enemy: false }, second = { u: e, d: a, mi: em, enemy: true }; const as = a.spd * (a.status.includes('frost') ? .5 : 1) + (am.effect === 'priority' ? 999 : 0), es = e.spd * (e.status.includes('frost') ? .5 : 1) + (emv.effect === 'priority' ? 999 : 0); if (es > as)
    [first, second] = [second, first]; for (const x of [first, second]) {
    if (x.u.hp <= 0 || x.d.hp <= 0)
        continue;
    if (x.u.status.includes('shock') && Math.random() < .28) {
        log.textContent = `${data_1.species[x.u.sid].name} shudders and loses the beat.`;
        await wait(450);
    }
    else
        await act(x.u, x.d, x.mi, x.enemy);
    draw();
    await wait(450);
} statusTick(a); statusTick(e); if (foes.every(f => f.hp <= 0)) {
    victory();
    return;
} if (state_1.state.team.every(u => u.hp <= 0)) {
    finish(false, 'Your chorus dims. The shrine recalls you.');
    return;
} if (a.hp <= 0) {
    ally = state_1.state.team.findIndex(u => u.hp > 0);
    log.textContent = 'A new kindred enters the chorus.';
} (0, render_1.updateRenderEnemies)(foes); draw(); busy = false; };
const victory = () => { let xp = 0; foes.forEach(f => xp += Math.floor(f.level * 12 * (f.level / (alive()?.level || 1)))); state_1.state.team.filter(u => u.hp > 0).forEach(u => (0, state_1.gainXp)(u, Math.max(8, xp))); (0, audio_1.sfx)('good'); finish(true, `Victory. The chorus gains ${xp} resonance.`); };
const switchUnit = () => { if (busy)
    return; const choices = state_1.state.team.map((u, i) => u.hp > 0 && i !== ally ? `${i + 1}: ${data_1.species[u.sid].name} Lv.${u.level}` : '').filter(Boolean); if (!choices.length) {
    log.textContent = 'No other kindred can answer.';
    return;
} const n = Number(prompt(`Choose a kindred:\n${choices.join('\n')}`)) - 1; if (state_1.state.team[n]?.hp > 0) {
    ally = n;
    log.textContent = `${data_1.species[state_1.state.team[n].sid].name}, enter the weave!`;
    draw();
} };
const startCapture = () => { if (busy)
    return; const e = eAlive(); if (!e || !e.wild) {
    log.textContent = 'Anchor guardians cannot be bound.';
    return;
} if ((state_1.state.inventory['Glimmer Loop'] || 0) <= 0) {
    log.textContent = 'No Glimmer Loops remain.';
    return;
} busy = true; $('#captureModal').classList.remove('hidden'); needleStart = performance.now(); const needle = $('#needle'); const anim = () => { const p = ((performance.now() - needleStart) % 1600) / 1600; needle.style.left = `${(p < .5 ? p * 2 : 2 - p * 2) * 99}%`; needleAnim = requestAnimationFrame(anim); }; anim(); };
const resolveCapture = () => { cancelAnimationFrame(needleAnim); $('#captureModal').classList.add('hidden'); const p = ((performance.now() - needleStart) % 1600) / 1600, q = (p < .5 ? p * 2 : 2 - p * 2); const rhythm = Math.max(.15, 1 - Math.abs(q - .5) * 1.8), e = eAlive(); if (!e) {
    busy = false;
    return;
} state_1.state.inventory['Glimmer Loop']--; const hpFactor = 1 - e.hp / e.maxHp; const chance = .18 + hpFactor * .58 + rhythm * .28; log.textContent = `Attunement ${Math.floor(chance * 100)}%...`; setTimeout(() => { if (Math.random() < chance) {
    const u = (0, state_1.makeUnit)(e.sid, e.level);
    if (state_1.state.team.length < 6)
        state_1.state.team.push(u);
    else
        state_1.state.reserve.push(u);
    if (!state_1.state.caught.includes(e.sid))
        state_1.state.caught.push(e.sid);
    e.hp = 0;
    (0, audio_1.sfx)('capture');
    finish(true, `${data_1.species[e.sid].name} joins your chorus!`);
}
else {
    log.textContent = 'The echo breaks free!';
    busy = false;
    enemyRetaliate();
} }, 700); };
exports.resolveCapture = resolveCapture;
const enemyRetaliate = async () => { const e = eAlive(), a = alive(); if (e && a)
    await act(e, a, Math.floor(Math.random() * e.moveIds.length), true); draw(); busy = false; };
const finish = (won, msg) => { busy = true; log.textContent = msg; setTimeout(() => { ui.classList.add('hidden'); (0, render_1.setRenderMode)('world'); busy = false; if (!won && state_1.state.team.every(u => u.hp <= 0)) {
    state_1.state.team.forEach(u => u.hp = u.maxHp);
    state_1.state.x = state_1.state.lastShrine.x;
    state_1.state.y = state_1.state.lastShrine.y;
    state_1.state.zone = state_1.state.lastShrine.zone;
} (0, state_1.save)(); onEnd(won); }, 1200); };
const startBattle = (ids, levels, boss = false, done = () => { }) => { foes = ids.map((id, i) => Object.assign((0, state_1.makeUnit)(id, levels[i]), { wild: !boss })); ally = Math.max(0, state_1.state.team.findIndex(u => u.hp > 0)); onEnd = done; busy = false; ui.classList.remove('hidden'); (0, render_1.setRenderMode)('battle', foes); log.textContent = boss ? `${data_1.zones[state_1.state.zone].guardian} seals the way!` : `${foes.length > 1 ? 'A wild chorus gathers!' : 'A wandering echo appears!'}`; draw(); };
exports.startBattle = startBattle;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-sol","provider":"openrouter","persona":"typescript","sessionId":"a013c7df-e62e-4383-a120-ee4875c42f81","tokensIn":377638,"tokensOut":16387,"tokensTotal":394025,"cost":0.8152645,"turns":22,"toolCalls":29,"failedToolCalls":0,"timestamp":"2026-07-31T19:38:46.879Z"} -->