← Creature Collection results

Creature Collection

GPT 5.6 Sol · full

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 41,674 bytes · SHA-256 86bf2f4abfa3
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Veyra: The Luminous Binding</title>
<style>
:root{--ink:#eaf9ff;--muted:#9bb8c8;--glass:rgba(8,18,35,.82);--line:rgba(171,239,255,.22);--cyan:#77f7ff;--gold:#ffe08a;--danger:#ff668c;--ok:#7cffb2}
*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#040814;color:var(--ink);font-family:system-ui,-apple-system,Segoe UI,sans-serif}button{font:inherit;color:inherit}
#game{position:relative;width:100%;height:100%;isolation:isolate}canvas{position:absolute;inset:0;width:100%;height:100%;image-rendering:auto}#world{z-index:0}.vignette{position:absolute;inset:0;pointer-events:none;z-index:2;background:radial-gradient(circle at 50% 42%,transparent 40%,rgba(0,0,0,.58));transition:.5s}
.ui{position:absolute;z-index:4}.glass{background:linear-gradient(145deg,rgba(18,39,64,.88),rgba(5,12,28,.91));border:1px solid var(--line);box-shadow:0 16px 50px #0008,inset 0 1px #fff1;backdrop-filter:blur(14px);border-radius:18px}
#hud{left:18px;top:16px;padding:12px 15px;min-width:248px}.zone{font-size:13px;color:var(--cyan);text-transform:uppercase;letter-spacing:2px;font-weight:800}.quest{font-size:12px;color:var(--muted);margin-top:4px}.hudrow{display:flex;align-items:center;gap:9px;margin-top:8px}.orb{width:10px;height:10px;border-radius:50%;background:var(--cyan);box-shadow:0 0 16px var(--cyan)}
#topBtns{right:16px;top:16px;display:flex;gap:8px}.iconbtn,.pill{border:1px solid var(--line);background:#071528cc;border-radius:12px;padding:9px 12px;cursor:pointer;box-shadow:0 6px 18px #0005;transition:.18s}.iconbtn:hover,.pill:hover,.choice:hover{transform:translateY(-2px);border-color:#82ecff88;background:#112b42}.iconbtn:active,.pill:active{transform:scale(.96)}
#toast{left:50%;top:18px;transform:translateX(-50%) translateY(-90px);padding:12px 22px;transition:.35s;pointer-events:none;text-align:center}.showtoast{transform:translateX(-50%) translateY(0)!important}
#touch{left:18px;bottom:18px;width:150px;height:150px;display:none}.dpad{position:absolute;width:48px;height:48px;border:1px solid #aef3ff33;background:#0a1c31bb;border-radius:14px;font-size:22px}.dpad:active{background:#2a6b83}.up{left:51px}.down{left:51px;bottom:0}.left{top:51px}.right{right:0;top:51px}
#interact{right:22px;bottom:35px;width:74px;height:74px;border-radius:50%;display:none;background:radial-gradient(circle at 35% 30%,#9fffff,#237b92);color:#03131c;border:3px solid #caffff;font-weight:900;box-shadow:0 0 28px #54eaff77}
.screen{position:absolute;inset:0;z-index:10;display:none;align-items:center;justify-content:center;padding:18px;background:radial-gradient(circle at 50% 30%,rgba(30,89,116,.35),rgba(2,5,15,.94))}.screen.active{display:flex}.panel{width:min(930px,96vw);max-height:90vh;overflow:auto;padding:24px}.titleScreen{background:radial-gradient(circle at 50% 36%,#153c58 0,#071424 35%,#02050e 78%)}
.logo{text-align:center;position:relative}.sigil{width:134px;height:134px;margin:0 auto 25px;border:2px solid #9dffff;border-radius:42% 58% 50% 50%;box-shadow:0 0 60px #52eaff88,inset 0 0 35px #57baff66;animation:float 4s ease-in-out infinite;display:grid;place-items:center}.sigil:before{content:'✦';font-size:64px;color:#eaffff;text-shadow:0 0 24px #65f5ff}.logo h1{font-size:clamp(38px,7vw,76px);margin:0;letter-spacing:5px;text-shadow:0 0 28px #5aeaff55}.logo p{color:#9fc6d7;letter-spacing:4px;text-transform:uppercase}.mainButtons{display:flex;gap:12px;justify-content:center;margin-top:35px}.primary{border:1px solid #a9ffff;background:linear-gradient(135deg,#79f6ff,#55a9de);color:#03131e;font-weight:900;padding:14px 28px;border-radius:14px;cursor:pointer;box-shadow:0 0 26px #56e7ff55}.primary:hover{filter:brightness(1.1);transform:translateY(-2px)}.secondary{border:1px solid var(--line);background:#0a1b31;color:white;padding:14px 28px;border-radius:14px;cursor:pointer}
.modalTitle{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px}.modalTitle h2{margin:0;letter-spacing:2px}.close{border:0;background:transparent;font-size:25px;cursor:pointer;color:#aac4d0}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}.card{padding:14px;border:1px solid var(--line);background:#07182aaa;border-radius:14px;position:relative}.card h3{margin:4px 0}.small{font-size:12px;color:var(--muted)}.aff{display:inline-block;padding:3px 8px;border-radius:20px;font-size:10px;font-weight:900;letter-spacing:1px;margin-right:4px;background:var(--c,#557);color:white}.hpbar{height:9px;background:#020813;border-radius:8px;overflow:hidden;margin:6px 0}.hpfill{height:100%;width:50%;background:linear-gradient(90deg,#42e994,#a5ffbc);transition:width .5s}.menuTabs{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:14px}.menuTabs button.active{border-color:var(--cyan);color:var(--cyan)}
#dialogue{left:50%;bottom:26px;transform:translateX(-50%);width:min(760px,90vw);padding:18px 22px;display:none}.speaker{color:var(--cyan);text-transform:uppercase;letter-spacing:2px;font-weight:900;font-size:12px}.dialogtext{font-size:17px;line-height:1.5;margin-top:5px}.hint{font-size:11px;color:#8ba9b8;text-align:right}.portraitGlow{position:absolute;width:160px;height:160px;border-radius:50%;left:-50px;top:-90px;filter:blur(30px);opacity:.45;pointer-events:none}
/* battle */#battle{background:transparent;align-items:stretch;padding:0}.battleShade{position:absolute;inset:0;background:linear-gradient(to bottom,transparent 55%,rgba(1,5,13,.86));pointer-events:none}.foeBox{position:absolute;right:5vw;top:8vh;width:min(330px,42vw);padding:13px}.allyBox{position:absolute;left:4vw;bottom:31vh;width:min(340px,46vw);padding:13px}.battleName{font-weight:900;letter-spacing:1px}.battleMeta{float:right;font-size:12px;color:var(--muted)}#battleMenu{position:absolute;bottom:16px;left:50%;transform:translateX(-50%);width:min(940px,96vw);padding:13px;display:grid;grid-template-columns:1.2fr 2fr;gap:12px}.battleLog{padding:9px 12px;color:#b7ceda;line-height:1.45;min-height:80px}.actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}.move{border:1px solid var(--line);background:#0b2034;padding:10px;border-radius:12px;text-align:left;cursor:pointer}.move:hover{border-color:var(--mc,#fff);box-shadow:inset 3px 0 var(--mc)}.move b{display:block}.move span{font-size:11px;color:#9eb8c3}.sprite{position:absolute;width:150px;height:150px;display:grid;place-items:center;filter:drop-shadow(0 20px 15px #0008);transition:.25s}.sprite .body{width:92px;height:92px;border-radius:48% 52% 44% 56%;background:radial-gradient(circle at 32% 25%,#fff,var(--sc) 16%,var(--sd) 70%);box-shadow:0 0 32px var(--sc);position:relative;animation:bob 2.2s ease-in-out infinite}.body:before,.body:after{content:'';position:absolute;background:#07101e;border:3px solid #dfffff;width:10px;height:14px;border-radius:50%;top:29px}.body:before{left:25px}.body:after{right:25px}.foeSprite{right:13vw;top:27vh}.allySprite{left:16vw;bottom:38vh;transform:scale(1.25)}.allySprite .body{animation-delay:-1s}.hit{animation:hit .3s}.shake{animation:shake .35s}.floatnum{position:absolute;font-weight:1000;font-size:25px;color:white;text-shadow:0 2px 5px #000;z-index:30;animation:rise .9s forwards;pointer-events:none}.flash{position:absolute;inset:0;background:#fff;opacity:0;z-index:25;pointer-events:none}.flash.go{animation:flash .25s}.statusline{display:flex;gap:5px;margin-top:4px}.status{font-size:9px;padding:2px 6px;border-radius:8px;background:#8c4266}
/* collection */#rhythm .panel{text-align:center;max-width:600px}.ring{width:220px;height:220px;border-radius:50%;margin:20px auto;position:relative;border:10px solid #193b52;display:grid;place-items:center;box-shadow:0 0 40px #5eeaff33}.targetRing{position:absolute;inset:15%;border:6px solid #ffe28b;border-radius:50%;opacity:.8}.pulseRing{position:absolute;inset:-10%;border:7px solid #7ff9ff;border-radius:50%;transition:none}.tapcore{width:90px;height:90px;border-radius:50%;background:radial-gradient(#eaffff,#55bed6);border:0;color:#05202c;font-weight:1000;cursor:pointer;z-index:2}
#starter .panel{max-width:850px;text-align:center}.starters{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.choice{padding:20px 12px;border:1px solid var(--line);border-radius:18px;background:#07182c;cursor:pointer;transition:.2s}.miniCreature{width:90px;height:90px;border-radius:46% 54%;margin:5px auto 15px;background:radial-gradient(circle at 30% 25%,white,var(--c) 20%,var(--d));box-shadow:0 0 25px var(--c)}
#cutin{z-index:20;background:radial-gradient(circle,var(--cut,#184a62),#020510 68%);text-align:center}.cutinText{max-width:700px}.cutinText h2{font-size:38px;letter-spacing:4px}.cutinText p{font-size:18px;line-height:1.7;color:#c3dbe5}
@keyframes float{50%{transform:translateY(-12px) rotate(4deg)}}@keyframes bob{50%{transform:translateY(-9px) rotate(2deg)}}@keyframes rise{to{transform:translateY(-70px);opacity:0}}@keyframes hit{50%{transform:translateX(25px);filter:brightness(3)}}@keyframes shake{25%{transform:translateX(-8px)}75%{transform:translateX(8px)}}@keyframes flash{50%{opacity:.65}}@keyframes drift{to{transform:translateY(-110vh) rotate(180deg)}}
@media(max-width:700px){#hud{min-width:190px;padding:9px}.quest{max-width:200px}#touch,#interact{display:block}#topBtns .iconbtn:not(#mute){display:none}#battleMenu{grid-template-columns:1fr}.battleLog{min-height:42px;font-size:12px}.foeSprite{right:8vw;top:25vh}.allySprite{left:10vw;bottom:43vh}.allyBox{bottom:35vh}.actions{gap:5px}.move{padding:7px}.starters{grid-template-columns:1fr}.choice{padding:8px}.miniCreature{width:55px;height:55px;float:left;margin:0 12px}.panel{padding:16px}}
</style>
</head>
<body><div id="game"><canvas id="world"></canvas><div class="vignette"></div>
<div id="hud" class="ui glass" style="display:none"><div class="zone" id="zoneName">Hushglass Verge</div><div class="quest" id="quest">Find the first echo.</div><div class="hudrow"><span class="orb"></span><b id="leadName">—</b><span class="small" id="leadHp"></span></div></div>
<div id="topBtns" class="ui" style="display:none"><button class="iconbtn" id="menuBtn">☰ Archive</button><button class="iconbtn" id="saveBtn">◇ Save</button><button class="iconbtn" id="mute">♪</button></div><div id="toast" class="ui glass"></div>
<div id="touch" class="ui"><button class="dpad up" data-dir="up">▲</button><button class="dpad down" data-dir="down">▼</button><button class="dpad left" data-dir="left">◀</button><button class="dpad right" data-dir="right">▶</button></div><button id="interact" class="ui">ACT</button>
<div id="dialogue" class="ui glass"><div class="portraitGlow"></div><div class="speaker" id="speaker"></div><div class="dialogtext" id="dialogText"></div><div class="hint">tap / Enter</div></div>
<section id="title" class="screen active titleScreen"><div class="logo"><div class="sigil"></div><h1>VEYRA</h1><p>The Luminous Binding</p><div class="mainButtons"><button class="primary" id="newBtn">New Binding</button><button class="secondary" id="continueBtn">Continue</button></div><div class="small" style="margin-top:25px">A world of living wavelengths awaits</div></div></section>
<section id="starter" class="screen"><div class="panel glass"><h2>Choose your first Veyr</h2><p class="small">Each living wavelength answers a different kind of courage.</p><div class="starters" id="starterCards"></div></div></section>
<section id="menu" class="screen"><div class="panel glass"><div class="modalTitle"><h2>Resonance Archive</h2><button class="close" data-close="menu">×</button></div><div class="menuTabs" id="tabs"></div><div id="menuContent"></div></div></section>
<section id="battle" class="screen"><div class="battleShade"></div><div class="flash"></div><div class="sprite foeSprite"><div class="body"></div></div><div class="sprite allySprite"><div class="body"></div></div><div class="foeBox glass"><span class="battleName" id="foeName"></span><span class="battleMeta" id="foeMeta"></span><div class="hpbar"><div class="hpfill" id="foeHp"></div></div><div class="statusline" id="foeStatus"></div></div><div class="allyBox glass"><span class="battleName" id="allyName"></span><span class="battleMeta" id="allyMeta"></span><div class="hpbar"><div class="hpfill" id="allyHp"></div></div><div class="statusline" id="allyStatus"></div></div><div id="battleMenu" class="glass"><div class="battleLog" id="battleLog">The air gathers into opposition.</div><div class="actions" id="actions"></div></div></section>
<section id="rhythm" class="screen"><div class="panel glass"><h2>Attunement</h2><p id="rhythmHint">Tap when the cyan ring touches gold.</p><div class="ring"><div class="targetRing"></div><div class="pulseRing"></div><button class="tapcore" id="tapBind">BIND</button></div><div id="bindChance">—</div></div></section>
<section id="cutin" class="screen"><div class="cutinText"><div class="zone">RESONANCE RESTORED</div><h2 id="cutTitle"></h2><p id="cutText"></p><button class="primary" id="cutContinue">Continue</button></div></section>
</div>
<script>
'use strict';
const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];
const AFF={A:{n:'Emberweave',c:'#ff6d6d'},B:{n:'Miretone',c:'#74d489'},C:{n:'Galescript',c:'#72c9ff'},D:{n:'Umbric',c:'#9c77db'},E:{n:'Lustral',c:'#ffe080'},F:{n:'Prismatic',c:'#ff83ec'},G:{n:'Nullstone',c:'#91a0af'}};
const {A,B,C,D,E,F,G}=AFF;
const strong={A:['B'],B:['C'],C:['A'],D:['A','C'],E:['D','B'],F:['A','B','C','D','E','G']};
const moves=[
['Cinder Nudge',A,18,98,3,'burn'],['Searlace',A,26,92,5,'burn'],['Flare Crown',A,38,82,8,'crit'],['Ashen Waltz',A,22,100,4,'speed'],['Warm Mend',A,0,100,5,'heal'],['Magma Quill',A,45,76,9,'burn'],
['Bog Pulse',B,20,98,3,'regen'],['Spore Hush',B,17,94,4,'shock'],['Moss Mantle',B,0,100,5,'barrier'],['Root Ravel',B,32,88,6,'frost'],['Verdant Coil',B,40,80,8,'multi'],['Sap Sip',B,24,95,5,'drain'],
['Draft Peck',C,19,100,3,'priority'],['Sky Needle',C,27,94,5,'crit'],['Gust Ladder',C,0,100,4,'speed'],['Cloud Tumble',C,23,91,4,'multi'],['Tempest Rune',C,42,79,8,'shock'],['Stillair',C,0,100,5,'heal'],
['Shade Prick',D,21,98,3,'shock'],['Dusk Fold',D,0,100,5,'barrier'],['Night Rasp',D,34,87,6,'burn'],['Hollow Wink',D,24,95,4,'frost'],['Eclipse Jaw',D,44,78,9,'crit'],['Gloom Siphon',D,28,90,6,'drain'],
['Gleam Tap',E,20,100,3,'priority'],['Aurora Thread',E,30,92,5,'regen'],['Beacon Roar',E,36,85,7,'shock'],['Dawn Shelter',E,0,100,5,'barrier'],['Sunshard',E,46,76,9,'crit'],['Bright Reprise',E,0,100,6,'heal'],
['Spectrum Kiss',F,18,98,4,'multi'],['Manylight',F,29,90,6,'crit'],['Iris Break',F,37,83,8,'burn'],['Chroma Surge',F,0,100,6,'speed'],['Sevenfold',F,48,73,10,'multi'],
['Quiet Knock',G,17,100,3,'frost'],['Slate Ward',G,0,100,4,'barrier'],['Zero Chime',G,25,94,5,'shock'],['Weight of Still',G,39,81,8,'crit'],['Pale Renewal',G,0,100,6,'regen']
].map((m,i)=>({id:i,n:m[0],aff:Object.keys(AFF).find(k=>AFF[k]===m[1]),pow:m[2],acc:m[3],cost:m[4],fx:m[5]}));
const baseNames=['Flarrow','Murlume','Kitesk','Noxlet','Solmoss','Prizzel','Cairnib','Emberlyn','Vellbog','Whiffin','Duskrill','Luxip','Chromew','Stillkin','Pyrelk','Mirefin','Zephroot','Glintwisp'];
const lore=['It stores unspoken promises in the heat beneath its crest. At dusk, those promises glow.','Its soft wake makes tired stones remember rain. It hums only near trusted footsteps.','Born where high winds crease the horizon, it sleeps while falling.','It collects shadows shed by frightened travelers and returns them as courage.','Tiny gardens drift across its back, blooming whenever someone laughs nearby.','Every facet shows a different possible morning. It dislikes closed curtains.','It waits so perfectly that moss mistakes it for an elder stone.','A quick-hearted ember that sketches maps in soot with its tail.','It filters bitter water through a pearl of living peat.','It speaks in whistles too high for sorrow to follow.','Its fin cuts doorways through darkness, though none remain for long.','A lantern-bellied wanderer said to guide lost echoes home.','Its colors swap places whenever it invents a new game.','Silence gathers around its shell and emerges polished.','Antler sparks mark paths visible only during rainless storms.','It braids riverweed into nests that never drift apart.','Roots and feathers share its body in an old, amicable argument.','It eats dim reflections, leaving mirrors a little more honest.'];
const affs=['A','B','C','D','E','F','G','A','B','C','D','E','F','G','A','B','C','E'];
const creatures=baseNames.map((n,i)=>({id:i,n,lore:lore[i],aff:affs[i],base:{hp:42+(i*7)%25,atk:18+(i*5)%16,def:17+(i*9)%17,spd:16+(i*11)%19},learn:[i%40,(i+7)%40,(i+14)%40,(i+23)%40],evo:i<9?18+(i%5)*3:null,cat:i<9?['Sunken Prism','Hush Pearl','Rift Seed'][i%3]:null}));
const evoNames=['Flarrow Zenith','Murlume Deepchoir','Kitesk Skyrune','Noxlet Umbrafold','Solmoss Grandbloom','Prizzel Iridescent','Cairnib Monolith','Emberlyn Hearthmane','Vellbog Fenoracle'];
evoNames.forEach((n,i)=>{let b=creatures[i];creatures.push({id:18+i,n,lore:'A transformed resonance of '+b.n+', carrying its earliest memory in a stronger and stranger shape.',aff:b.aff,base:{hp:b.base.hp+20,atk:b.base.atk+12,def:b.base.def+12,spd:b.base.spd+8},learn:[(i+4)%40,(i+13)%40,(i+25)%40,(i+34)%40],evo:null,cat:null,from:i})});
const zones=[
{n:'Hushglass Verge',grad:['#16405a','#071624'],rate:.08,guardian:3,story:'The first Anchor sings beneath fractured glass.'},
{n:'Morrowfen',grad:['#224f43','#071e22'],rate:.1,guardian:8,story:'A green tide holds a stolen memory.'},
{n:'Kitevault Rise',grad:['#355a82','#111b3a'],rate:.11,guardian:11,story:'Wind-script curls around the second Anchor.'},
{n:'Umbersink',grad:['#492f63','#110d29'],rate:.12,guardian:14,story:'The dark here is not empty. It is listening.'},
{n:'Aurora Loom',grad:['#75612c','#22213c'],rate:.14,guardian:16,story:'Threads of dawn knot around the final seal.'},
{n:'The Sevenfold',grad:['#613d6d','#172345'],rate:.17,guardian:17,story:'All wavelengths converge beyond the Anchors.'}
];
const defaultState=()=>({zone:0,x:7,y:7,unlocked:0,anchors:0,stage:0,team:[],seen:[],caught:[],bag:{'Wisp Loop':8,'True Loop':2,'Crown Loop':0,'Mend Dew':4,'Sunken Prism':1,'Hush Pearl':1,'Rift Seed':1},coins:120,tutorial:0,post:false,play:0});
let S=defaultState(), mode='title', ctx=$('#world').getContext('2d'), W=0,H=0,dpr=1,keys={},particles=[],last=0,stepCooldown=0,battle=null,dialogQueue=[],audio=null,muted=false, rhythm=null;
function inst(id,lvl=5){let c=creatures[id], hp=Math.floor(c.base.hp+lvl*4.2);return{id,lvl,xp:0,hp,max:hp,energy:30,maxEnergy:30,atk:Math.floor(c.base.atk+lvl*1.65),def:Math.floor(c.base.def+lvl*1.55),spd:Math.floor(c.base.spd+lvl*1.45),moves:c.learn.slice(0,4),status:{},prof:{}}}
function resize(){dpr=Math.min(2,devicePixelRatio||1);W=innerWidth;H=innerHeight;ctx.canvas.width=W*dpr;ctx.canvas.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0)}addEventListener('resize',resize);resize();
function show(id){$$('.screen').forEach(x=>x.classList.remove('active'));if(id)$('#'+id).classList.add('active');mode=id||'world';$('#hud').style.display=mode==='world'?'block':'none';$('#topBtns').style.display=mode==='world'?'flex':'none'}
function toast(t){let e=$('#toast');e.textContent=t;e.classList.add('showtoast');setTimeout(()=>e.classList.remove('showtoast'),1900)}
function save(manual=false){localStorage.setItem('veyra_binding',JSON.stringify(S));if(manual)toast('Binding secured at the last shrine.');updateHud()}
function load(){try{let s=JSON.parse(localStorage.getItem('veyra_binding'));if(s){S=Object.assign(defaultState(),s);S.team.forEach(c=>{if(c.energy==null)c.energy=30;if(c.maxEnergy==null)c.maxEnergy=30})}}catch(e){} }
function affTag(a){return `<span class="aff" style="--c:${AFF[a].c}">${AFF[a].n}</span>`}
function updateHud(){let c=S.team[0];$('#zoneName').textContent=zones[S.zone].n;$('#quest').textContent=S.post?'Free roam: heightened resonances':S.anchors<4?`Bind 8 Veyr (${S.caught.length}/8) • Restore 4 Anchors (${S.anchors}/4)`:S.caught.length<8?`Complete the chorus: ${S.caught.length}/8 bound`:'Reach the Sevenfold';if(c){$('#leadName').textContent=creatures[c.id].n+' · Lv '+c.lvl;$('#leadHp').textContent=`${c.hp}/${c.max}`}}
function dialogue(lines,done){dialogQueue=lines.map(x=>typeof x==='string'?['Ilyra',x]:x);$('#dialogue').style.display='block';mode='dialogue';window.dialogDone=done;nextDialogue()}
function nextDialogue(){if(!dialogQueue.length){$('#dialogue').style.display='none';mode='world';let f=window.dialogDone;window.dialogDone=null;if(f)f();return}let [s,t]=dialogQueue.shift();$('#speaker').textContent=s;$('#dialogText').textContent=t;$('.portraitGlow').style.background=AFF[['E','F','C'][Math.floor(Math.random()*3)]].c}
function cut(title,text,cb){$('#cutTitle').textContent=title;$('#cutText').textContent=text;$('#cutin').style.setProperty('--cut',zones[S.zone].grad[0]);show('cutin');$('#cutContinue').onclick=()=>{show(null);cb&&cb()}}
function newGame(){S=defaultState();renderStarters();show('starter')}
function renderStarters(){let ids=[0,1,2];$('#starterCards').innerHTML=ids.map(id=>{let c=creatures[id];return `<button class="choice" data-start="${id}"><div class="miniCreature" style="--c:${AFF[c.aff].c};--d:#142438"></div><h3>${c.n}</h3>${affTag(c.aff)}<p class="small">${c.lore}</p></button>`}).join('');$$('[data-start]').forEach(b=>b.onclick=()=>start(+b.dataset.start))}
function start(id){S.team=[inst(id,5)];S.seen=[id];S.caught=[id];show(null);updateHud();music();dialogue([['Ilyra, Keeper of Threads','You are awake. Good. The world is losing its colors one wavelength at a time.'],['Ilyra','Move with WASD, arrow keys, or the touch pad. Seek the pulsing star to the east.'],['Ilyra','Wild Veyr may answer your steps. Weaken them, then use a Wisp Loop to attempt a Binding.']],()=>{S.tutorial=1;save()})}
// world
function tileSize(){return Math.max(36,Math.min(56,W/18))}
function worldToScreen(x,y){let ts=tileSize();return{x:W/2+(x-S.x)*ts,y:H/2+(y-S.y)*ts}}
function drawWorld(t){let z=zones[S.zone],g=ctx.createLinearGradient(0,0,W,H);g.addColorStop(0,z.grad[0]);g.addColorStop(1,z.grad[1]);ctx.fillStyle=g;ctx.fillRect(0,0,W,H);let ts=tileSize(),ox=W/2-S.x*ts,oy=H/2-S.y*ts;ctx.globalAlpha=.16;ctx.strokeStyle='#b9f8ff';ctx.lineWidth=1;for(let x=0;x<=14;x++){ctx.beginPath();ctx.moveTo(ox+x*ts,oy);ctx.lineTo(ox+x*ts,oy+14*ts);ctx.stroke()}for(let y=0;y<=14;y++){ctx.beginPath();ctx.moveTo(ox,oy+y*ts);ctx.lineTo(ox+14*ts,oy+y*ts);ctx.stroke()}ctx.globalAlpha=1;
// decorative nodes
for(let x=0;x<15;x++)for(let y=0;y<15;y++){let n=(x*19+y*31+S.zone*17)%29;if(n<3){let p=worldToScreen(x,y);ctx.fillStyle=n===0?'#ffffff18':AFF[['A','B','C','D','E','F'][S.zone]].c+'26';ctx.beginPath();ctx.arc(p.x,p.y,5+n*3,0,7);ctx.fill()}}
let shrine=worldToScreen(2,2);drawNode(shrine.x,shrine.y,'◇','#8cffff','SHRINE');let gate=worldToScreen(12,7);drawNode(gate.x,gate.y,'✦',S.zone<=S.unlocked?'#ffe38a':'#77808c',S.zone===5?'CONVERGENCE':'PASSAGE');let boss=worldToScreen(10,4);if(S.zone<4 && S.anchors<=S.zone)drawNode(boss.x,boss.y,'⬡','#ff6f9e','ANCHOR');
// player
ctx.save();ctx.translate(W/2,H/2);ctx.shadowBlur=25;ctx.shadowColor='#7ffaff';ctx.fillStyle='#dfffff';ctx.beginPath();ctx.arc(0,-5,12,0,7);ctx.fill();ctx.fillStyle='#59ccdf';ctx.beginPath();ctx.moveTo(-15,18);ctx.lineTo(0,2);ctx.lineTo(15,18);ctx.closePath();ctx.fill();ctx.restore();
particles.forEach((p,i)=>{p.y-=p.s*.15;p.x+=Math.sin(t/700+p.q)*.2;ctx.globalAlpha=p.a;ctx.fillStyle=p.c;ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,7);ctx.fill();if(p.y<-10){p.y=H+10;p.x=Math.random()*W}});ctx.globalAlpha=1}
function drawNode(x,y,s,c,label){ctx.save();ctx.translate(x,y);ctx.shadowBlur=25;ctx.shadowColor=c;ctx.fillStyle=c;ctx.font='30px system-ui';ctx.textAlign='center';ctx.fillText(s,0,5);ctx.shadowBlur=0;ctx.font='9px system-ui';ctx.fillStyle='#eaffff';ctx.fillText(label,0,25);ctx.restore()}
function makeParticles(){particles=Array.from({length:45},(_,i)=>({x:Math.random()*W,y:Math.random()*H,s:.5+Math.random()*2,q:Math.random()*8,r:1+Math.random()*2,a:.15+Math.random()*.35,c:AFF[['A','B','C','D','E','F'][S.zone]||'F'].c}))}
function move(dx,dy){if(mode!=='world'||stepCooldown>0)return;let nx=Math.max(0,Math.min(14,S.x+dx)),ny=Math.max(0,Math.min(14,S.y+dy));if(nx===S.x&&ny===S.y)return;S.x=nx;S.y=ny;stepCooldown=120;S.play++;tone(180+Math.random()*50,.025,.03);checkNode();if(mode==='world'&&Math.random()<zones[S.zone].rate*(S.post?1.35:1))startBattle(false);updateHud()}
function checkNode(){if(S.x===2&&S.y===2)shrine();else if(S.x===10&&S.y===4&&S.zone<4&&S.anchors<=S.zone)startBattle(true);else if(S.x===12&&S.y===7){if(S.zone===5){if(S.anchors>=4&&S.caught.length>=8&&!S.post)finale();else toast('The Convergence requires 8 voices and 4 restored Anchors.')}else if(S.zone<S.unlocked){S.zone++;S.x=1;makeParticles();toast('Entered '+zones[S.zone].n)}else if(S.anchors>S.zone){S.unlocked=Math.max(S.unlocked,S.zone+1);S.zone++;S.x=1;makeParticles();toast('Passage unfurled: '+zones[S.zone].n)}else toast('The passage is knotted around this zone’s Anchor.')}}
function shrine(){S.team.forEach(c=>{c.hp=c.max;c.energy=c.maxEnergy||30;c.status={}});save();dialogue([['Shrine Echo','Your chorus is made whole. The Binding rests safely here.'],['Shrine Echo','Trade 30 glints for a Wisp Loop, or carry on.']],()=>{if(S.coins>=30&&S.bag['Wisp Loop']<12){S.coins-=30;S.bag['Wisp Loop']++;toast('Purchased 1 Wisp Loop')}save()})}
function interact(){if(mode==='dialogue')nextDialogue();else if(mode==='world')checkNode()}
// battle
function startBattle(boss){let count=boss?Math.min(3,1+Math.floor(S.zone/2)):1+((S.zone>2&&Math.random()<.35)?1:0), enemies=[];for(let i=0;i<count;i++){let poolStart=Math.min(17,S.zone*3),id=boss?zones[S.zone].guardian:Math.min(17,poolStart+Math.floor(Math.random()*6));let lv=Math.min(50,4+S.zone*5+Math.floor(Math.random()*5)+(S.post?12:0)+(boss?4:0));enemies.push(inst(id,lv))}battle={enemies,index:0,ally:0,boss,turn:false};S.seen=[...new Set([...S.seen,...enemies.map(e=>e.id)])];show('battle');renderBattle();battleLog(boss?'The Anchor Guardian tears free of its seal!':'A wild resonance gathers shape.');setTimeout(playerTurn,450)}
function activeAlly(){return S.team[battle.ally]}function activeFoe(){return battle.enemies[battle.index]}
function mult(move,target){if(strong[move.aff]?.includes(creatures[target.id].aff))return move.aff==='F'?1.2:1.6;if(creatures[target.id].aff==='G'&&move.aff!=='F')return .65;return 1}
function renderBattle(){let a=activeAlly(),f=activeFoe(),ac=creatures[a.id],fc=creatures[f.id];$('#allyName').textContent=ac.n;$('#allyMeta').textContent=`Lv ${a.lvl} · HP ${a.hp}/${a.max} · E ${a.energy}/${a.maxEnergy}`;$('#foeName').textContent=(battle.boss?'GUARDIAN ':'')+fc.n;$('#foeMeta').textContent=`Lv ${f.lvl} · ${battle.index+1}/${battle.enemies.length}`;$('#allyHp').style.width=(100*a.hp/a.max)+'%';$('#foeHp').style.width=(100*f.hp/f.max)+'%';$('.allySprite .body').style.setProperty('--sc',AFF[ac.aff].c);$('.allySprite .body').style.setProperty('--sd','#14233b');$('.foeSprite .body').style.setProperty('--sc',AFF[fc.aff].c);$('.foeSprite .body').style.setProperty('--sd','#101827');$('#allyStatus').innerHTML=statusHtml(a);$('#foeStatus').innerHTML=statusHtml(f)}
function statusHtml(c){return Object.keys(c.status).filter(k=>c.status[k]>0).map(k=>`<span class="status">${k}</span>`).join('')}
function battleLog(s){$('#battleLog').textContent=s}
function playerTurn(){if(!battle)return;let a=activeAlly();if(a.hp<=0){chooseReplacement();return}battle.turn=true;let html=a.moves.map(id=>{let m=moves[id];return `<button class="move" data-move="${id}" style="--mc:${AFF[m.aff].c}"><b>${m.n}</b><span>${AFF[m.aff].n} · PWR ${m.pow} · ${m.acc}% · E ${m.cost}</span></button>`}).join('');html+=`<button class="move" data-action="bind"><b>Wisp Loop</b><span>Bind weakened wild Veyr</span></button><button class="move" data-action="switch"><b>Switch Chorus</b><span>Choose another active Veyr</span></button>`;$('#actions').innerHTML=html;$$('[data-move]').forEach(b=>b.onclick=()=>usePlayer(+b.dataset.move));$('[data-action="bind"]').onclick=tryBind;$('[data-action="switch"]').onclick=chooseSwitch}
function canAct(c){if(c.status.shock&&Math.random()<.25){battleLog(creatures[c.id].n+' is caught in static!');return false}return true}
function usePlayer(id){if(!battle.turn)return;let a=activeAlly(),f=activeFoe(),m=moves[id];if(a.energy<m.cost){battleLog('Not enough energy. Rest this voice or choose another art.');return}a.energy-=m.cost;battle.turn=false;$('#actions').innerHTML='';let allyFirst=(a.spd*(a.status.frost?.5:1)+((m.fx==='priority')?999:0))>=f.spd*(f.status.frost?.5:1);if(allyFirst){resolve(a,f,m,true,()=>{if(f.hp<=0)foeDown();else enemyTurn()})}else enemyTurn(()=>{if(a.hp>0)resolve(a,f,m,true,()=>f.hp<=0?foeDown():endRound())})}
function resolve(att,def,m,isPlayer,cb){if(!canAct(att)){setTimeout(cb,650);return}if(m.pow===0){effect(m.fx,att,att);battleLog(`${creatures[att.id].n} used ${m.n}.`);renderBattle();setTimeout(cb,700);return}if(Math.random()*100>m.acc){battleLog(`${m.n} slipped wide.`);setTimeout(cb,600);return}if(def.status.barrier){delete def.status.barrier;battleLog('The barrier deflected the strike!');renderBattle();setTimeout(cb,650);return}let M=mult(m,def),stab=creatures[att.id].aff===m.aff?1.25:1,crit=Math.random()<(.08+(m.fx==='crit'?.12:0)),atk=att.atk*(att.status.burn?.5:1),hits=m.fx==='multi'?2+Math.floor(Math.random()*2):1;let total=0;for(let i=0;i<hits;i++)total+=Math.max(1,Math.floor(.4*att.lvl*atk/def.def*M*stab*(crit?1.5:1)+Math.random()*4));def.hp=Math.max(0,def.hp-total);if(m.fx!=='multi'&&Math.random()<.38)effect(m.fx,att,def);let sp=isPlayer?$('.foeSprite'):$('.allySprite');sp.classList.add('hit');setTimeout(()=>sp.classList.remove('hit'),300);floatDamage(total,isPlayer,crit,M);battleLog(`${m.n} dealt ${total}.${crit?' Critical resonance!':''}${M>1?' Harmonic breach!':''}`);tone(M>1?520:310,.1,.08);renderBattle();setTimeout(cb,750)}
function effect(fx,a,d){if(['burn','frost','shock'].includes(fx))d.status[fx]=3;else if(fx==='barrier')a.status.barrier=1;else if(fx==='regen')a.status.regen=3;else if(fx==='speed')a.spd=Math.floor(a.spd*1.2);else if(fx==='heal')a.hp=Math.min(a.max,a.hp+Math.floor(a.max*.32));else if(fx==='drain')a.hp=Math.min(a.max,a.hp+Math.floor(a.max*.12))}
function floatDamage(n,toFoe,crit,M){let e=document.createElement('div');e.className='floatnum';e.textContent=(crit?'✦ ':'−')+n;e.style.left=toFoe?'70%':'26%';e.style.top=toFoe?'38%':'55%';e.style.color=M>1?'#ffe58f':'#fff';$('#battle').appendChild(e);setTimeout(()=>e.remove(),950);if(crit||M>1){$('.flash').classList.add('go');setTimeout(()=>$('.flash').classList.remove('go'),300);$('#battle').classList.add('shake');setTimeout(()=>$('#battle').classList.remove('shake'),350)}}
function enemyTurn(after){let f=activeFoe(),a=activeAlly(),m=moves[f.moves[Math.floor(Math.random()*f.moves.length)]];resolve(f,a,m,false,()=>{if(a.hp<=0){battleLog(creatures[a.id].n+' has fallen silent.');setTimeout(chooseReplacement,700)}else{after?after():endRound()}})}
function endRound(){activeAlly().energy=Math.min(activeAlly().maxEnergy,activeAlly().energy+3);[activeAlly(),activeFoe()].forEach(c=>{if(c.status.burn)c.hp=Math.max(0,c.hp-Math.max(1,Math.floor(c.max*.06)));if(c.status.regen)c.hp=Math.min(c.max,c.hp+Math.max(1,Math.floor(c.max*.05)));Object.keys(c.status).forEach(k=>{if(k!=='barrier'&&--c.status[k]<=0)delete c.status[k]})});renderBattle();if(activeAlly().hp<=0)chooseReplacement();else if(activeFoe().hp<=0)foeDown();else setTimeout(playerTurn,450)}
function foeDown(){let f=activeFoe(),a=activeAlly(),gain=Math.max(8,Math.floor(f.lvl*12*(f.lvl/a.lvl)));gainXp(a,gain);S.coins+=8+f.lvl;battleLog(`${creatures[f.id].n} dispersed. ${a.xp}/${a.lvl*30} resonance.`);battle.index++;if(battle.index<battle.enemies.length){setTimeout(()=>{renderBattle();playerTurn()},900)}else setTimeout(victory,900)}
function gainXp(c,x){c.xp+=x;while(c.lvl<50&&c.xp>=c.lvl*30){c.xp-=c.lvl*30;c.lvl++;c.max+=4;c.hp=c.max;c.atk+=2;c.def+=2;c.spd+=1;let base=creatures[c.id];c.moves=base.learn.slice(0,Math.min(4,2+Math.floor(c.lvl/8)));toast(`${base.n} rose to level ${c.lvl}`);if(base.evo&&c.lvl>=base.evo&&S.bag[base.cat]>0){S.bag[base.cat]--;c.id=18+base.id;toast(`${base.n} transformed into ${creatures[c.id].n}!`)}}}
function victory(){let wasBoss=battle.boss;battle=null;show(null);if(wasBoss){S.anchors++;S.unlocked=Math.max(S.unlocked,S.zone+1);S.stage=Math.min(4,S.stage+1);S.bag[['True Loop','Crown Loop','Crown Loop','Crown Loop'][Math.min(3,S.zone)]]++;let texts=['The Verge exhales. A buried color returns to every pane.','Morrowfen releases the memory it was guarding: the world was once a single song.','The high air writes your name among its patient currents.','Four Anchors answer. The Sevenfold opens its impossible eye.'];cut('ANCHOR '+S.anchors+' RESTORED',texts[S.anchors-1],()=>{save();if(S.anchors>=4&&S.caught.length>=8)toast('The Convergence now awaits.')})}else save()}
function chooseReplacement(){let alive=S.team.map((c,i)=>[c,i]).filter(([c])=>c.hp>0);if(!alive.length){battle=null;S.x=2;S.y=2;S.team.forEach(c=>c.hp=c.max);show(null);toast('Your chorus returned to the shrine.');save();return}battle.ally=alive[0][1];renderBattle();setTimeout(playerTurn,400)}
function chooseSwitch(){let opts=S.team.map((c,i)=>c.hp>0&&i!==battle.ally?`<button class="move" data-sw="${i}"><b>${creatures[c.id].n}</b><span>Lv ${c.lvl} · ${c.hp}/${c.max}</span></button>`:'').join('');$('#actions').innerHTML=opts||'<button class="move">No other voice can answer.</button>';$$('[data-sw]').forEach(b=>b.onclick=()=>{battle.ally=+b.dataset.sw;renderBattle();battle.turn=false;enemyTurn()})}
function tryBind(){if(battle.boss){battleLog('Guardian seals cannot be bound.');return}let item=['Crown Loop','True Loop','Wisp Loop'].find(x=>S.bag[x]>0);if(!item){battleLog('No Binding Loops remain.');return}battle.turn=false;S.bag[item]--;let f=activeFoe(),quality={'Wisp Loop':.18,'True Loop':.35,'Crown Loop':.55}[item],base=.12+(1-f.hp/f.max)*.58+quality;rhythm={base,item,f,t:0,dir:1,raf:0};show('rhythm');$('#bindChance').textContent=`Base harmony ${Math.floor(base*100)}% · ${item}`;animateRhythm()}
function animateRhythm(){if(!rhythm)return;rhythm.t+=rhythm.dir*.012;if(rhythm.t>1||rhythm.t<0)rhythm.dir*=-1;let scale=.55+rhythm.t*.75;$('.pulseRing').style.transform=`scale(${scale})`;rhythm.raf=requestAnimationFrame(animateRhythm)}
function tapRhythm(){if(!rhythm)return;cancelAnimationFrame(rhythm.raf);let bonus=Math.max(0,.28-Math.abs(rhythm.t-.52)*.55),chance=Math.min(.94,rhythm.base+bonus),success=Math.random()<chance,f=rhythm.f;rhythm=null;if(success){let id=f.id;if(!S.caught.includes(id))S.caught.push(id);if(S.team.length<6)S.team.push(inst(id,Math.max(2,f.lvl-1)));else S.coins+=50;show(null);battle=null;toast(`${creatures[id].n} joined the chorus! ${S.team.length>=6?'Sent as archive resonance.':''}`);tone(660,.4,.12);save();if(S.tutorial===1){S.tutorial=2;dialogue([['Ilyra','A clean Binding. Your timing strengthens the Loop. Build a chorus of six, but remember: eight unique voices are needed to mend the world.'],['Ilyra','Shrines heal, save, and trade supplies. The first waits in the northwest.']])}}else{show('battle');battleLog('The harmonies slipped apart!');renderBattle();setTimeout(enemyTurn,500)}}
function finale(){S.post=true;cut('THE WORLD REMEMBERS COLOR','Eight voices rise through four restored Anchors. The Sevenfold does not close; it blooms, scattering every lost wavelength back across Veyra. Your Binding is complete—but stronger, stranger resonances now wander every path.',()=>{S.zone=0;S.x=7;S.y=7;S.unlocked=5;makeParticles();save();toast('Post-game free roam unlocked')})}
// menu
const menuTabs=['Chorus','Satchel','Bestiary','Map','Guide'];
function openMenu(tab='Chorus'){if(mode!=='world')return;show('menu');$('#tabs').innerHTML=menuTabs.map(x=>`<button class="pill ${x===tab?'active':''}" data-tab="${x}">${x}</button>`).join('');$$('[data-tab]').forEach(b=>b.onclick=()=>openMenu(b.dataset.tab));let out='';if(tab==='Chorus')out=`<div class="grid">${S.team.map((c,i)=>{let d=creatures[c.id];return `<div class="card"><div class="small">${i===0?'ACTIVE VOICE':'CHORUS '+(i+1)}</div><h3>${d.n}</h3>${affTag(d.aff)}<div class="hpbar"><div class="hpfill" style="width:${100*c.hp/c.max}%"></div></div><div class="small">Lv ${c.lvl} · HP ${c.hp}/${c.max}<br>ATK ${c.atk} · DEF ${c.def} · SPD ${c.spd}</div><button class="pill" data-lead="${i}" style="margin-top:8px">Set Lead</button></div>`}).join('')}</div>`;
if(tab==='Satchel')out=`<div class="grid">${Object.entries(S.bag).map(([n,q])=>`<div class="card"><h3>${n}</h3><b>× ${q}</b><p class="small">${n.includes('Loop')?'A tuned loop for living wavelengths.':'A rare catalyst or restorative field supply.'}</p>${n==='Mend Dew'?'<button class="pill" id="useDew">Use on chorus</button>':''}</div>`).join('')}</div><p>Glints: <b>${S.coins}</b></p>`;
if(tab==='Bestiary')out=`<p class="small">Observed ${S.seen.length}/27 · Bound ${S.caught.length}/27</p><div class="grid">${creatures.map(c=>S.seen.includes(c.id)?`<div class="card"><div class="small">#${String(c.id+1).padStart(2,'0')} ${S.caught.includes(c.id)?'BOUND':'OBSERVED'}</div><h3>${c.n}</h3>${affTag(c.aff)}<p class="small">${c.lore}</p>${c.evo?`<div class="small">Transforms at Lv ${c.evo} with ${c.cat}</div>`:''}</div>`:`<div class="card"><h3>Unknown wavelength</h3><p class="small">No resonance recorded.</p></div>`).join('')}</div>`;
if(tab==='Map')out=`<div class="grid">${zones.map((z,i)=>`<div class="card" style="border-color:${z.grad[0]}"><div class="zone">${i<=S.unlocked?'REACHABLE':'SEALED'}</div><h3>${z.n}</h3><p class="small">${z.story}</p>${i<=S.unlocked?`<button class="pill" data-travel="${i}">Travel</button>`:''}</div>`).join('')}</div>`;
if(tab==='Guide')out=`<h3>Binding Primer</h3><p class="small">Move across the tiled wilds. Shrines rest and save your chorus. Anchor sigils hold Guardians; defeating four opens the final Convergence.</p><h3>Affinity Cycle</h3><p>${affTag('A')} › ${affTag('B')} › ${affTag('C')} › Emberweave</p><p>${affTag('D')} breaches Emberweave & Galescript. ${affTag('E')} breaches Umbric & Miretone. ${affTag('F')} slightly breaches all. ${affTag('G')} resists all except Prismatic.</p><h3>Conditions</h3><p class="small">Burn deals damage and halves ATK. Frost halves SPD. Shock may skip a turn. Barrier deflects one hit. Regen restores health each round.</p><button class="primary" id="manualSave">Manual Save</button>`;
$('#menuContent').innerHTML=out;$$('[data-lead]').forEach(b=>b.onclick=()=>{let i=+b.dataset.lead;[S.team[0],S.team[i]]=[S.team[i],S.team[0]];openMenu('Chorus');save()});$$('[data-travel]').forEach(b=>b.onclick=()=>{S.zone=+b.dataset.travel;S.x=2;S.y=2;makeParticles();show(null);updateHud()});if($('#useDew'))$('#useDew').onclick=()=>{if(S.bag['Mend Dew']>0){S.bag['Mend Dew']--;S.team.forEach(c=>c.hp=Math.min(c.max,c.hp+30));openMenu('Satchel')}};if($('#manualSave'))$('#manualSave').onclick=()=>save(true)}
// audio/input/loop
function music(){if(muted||audio)return;try{audio=new (window.AudioContext||window.webkitAudioContext)();let o=audio.createOscillator(),g=audio.createGain(),l=audio.createBiquadFilter();o.type='sine';o.frequency.value=55;l.type='lowpass';l.frequency.value=180;g.gain.value=.018;o.connect(l).connect(g).connect(audio.destination);o.start()}catch(e){}}
function tone(freq,dur=.08,vol=.05){if(muted)return;music();if(!audio)return;let o=audio.createOscillator(),g=audio.createGain();o.type='triangle';o.frequency.value=freq;g.gain.setValueAtTime(vol,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+dur);o.connect(g).connect(audio.destination);o.start();o.stop(audio.currentTime+dur)}
function loop(t){let dt=t-last;last=t;if(stepCooldown>0)stepCooldown-=dt;if(mode==='world'){if(keys.ArrowUp||keys.w)move(0,-1);else if(keys.ArrowDown||keys.s)move(0,1);else if(keys.ArrowLeft||keys.a)move(-1,0);else if(keys.ArrowRight||keys.d)move(1,0)}drawWorld(t);requestAnimationFrame(loop)}
addEventListener('keydown',e=>{keys[e.key]=true;if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(e.key))e.preventDefault();if(e.key==='Enter'||e.key===' ')interact();if(e.key==='Escape'&&mode==='menu')show(null)});addEventListener('keyup',e=>keys[e.key]=false);
$$('.dpad').forEach(b=>{let go=()=>{let d=b.dataset.dir;move(d==='right'?1:d==='left'?-1:0,d==='down'?1:d==='up'?-1:0)};b.onpointerdown=go});$('#interact').onclick=interact;$('#newBtn').onclick=()=>{music();newGame()};$('#continueBtn').onclick=()=>{load();if(!S.team.length)newGame();else{show(null);makeParticles();updateHud();music();toast('Binding resumed')}};$('#menuBtn').onclick=()=>openMenu();$('#saveBtn').onclick=()=>save(true);$('#mute').onclick=()=>{muted=!muted;$('#mute').textContent=muted?'×':'♪';if(audio){audio.close();audio=null}if(!muted)music()};$$('[data-close]').forEach(b=>b.onclick=()=>show(null));$('#tapBind').onclick=tapRhythm;$('#dialogue').onclick=nextDialogue;
load();$('#continueBtn').disabled=!localStorage.getItem('veyra_binding');makeParticles();requestAnimationFrame(loop);
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-5.6-sol","provider":"openrouter","persona":"full","sessionId":"c38bc28d-8258-4afc-8bbd-8e64dc51fbe1","tokensIn":369359,"tokensOut":16013,"tokensTotal":385372,"cost":0.8276522500000001,"turns":18,"toolCalls":17,"failedToolCalls":0,"timestamp":"2026-07-31T19:44:58.245Z"} -->