← Creature Collection results

Creature Collection

GPT 5.6 Luna · composer

Initial view of Creature Collection

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 29,109 bytes · SHA-256 2ae697d4323e
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asterweave: The Lumenbound Atlas</title>
<style>
/* ── css: core ── */
:root{--ink:#e9f4ff;--muted:#91a8c2;--bg:#07111f;--glass:rgba(13,28,49,.78);--line:rgba(166,211,255,.18);--cyan:#62e2e2;--gold:#ffd38a;--pink:#ff8fae;--violet:#a999ff}*{box-sizing:border-box}body{margin:0;background:#040a14;color:var(--ink);font-family:Inter,ui-sans-serif,system-ui,sans-serif;overflow:hidden}button{font:inherit;color:inherit;cursor:pointer}#game{height:100vh;min-height:620px;position:relative;overflow:hidden;background:linear-gradient(130deg,#081328,#142345 48%,#3a1f45);isolation:isolate}#scene{position:absolute;inset:0;width:100%;height:100%;z-index:-2;image-rendering:auto}#vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse at center,transparent 35%,rgba(2,5,15,.6) 100%);z-index:-1}.hidden{display:none!important}.screen{position:absolute;inset:0;display:grid;place-items:center;background:rgba(3,9,19,.34);backdrop-filter:blur(4px)}.titleCard,.modal,.dialogCard{background:linear-gradient(140deg,rgba(13,29,53,.91),rgba(12,18,39,.83));border:1px solid var(--line);box-shadow:0 25px 100px #02050dcc,0 0 0 1px #ffffff08 inset;border-radius:24px;padding:42px;backdrop-filter:blur(18px)}.titleCard{width:min(560px,90vw);text-align:center}.eyebrow{font-size:10px;letter-spacing:3px;color:var(--cyan);font-weight:800;margin-bottom:15px}.titleCard h1{font-size:76px;letter-spacing:-6px;line-height:.95;margin:4px 0 16px;font-weight:800;background:linear-gradient(100deg,#ecfaff,#75e9df 45%,#c2a8ff);-webkit-background-clip:text;color:transparent}.titleCard h1 span{font-weight:300}.tagline{font-size:19px;color:#d2ddf7;margin:0 0 24px}.titleLore{font-size:13px;line-height:1.65;color:var(--muted);max-width:390px;margin:auto}.titleBtns{display:flex;justify-content:center;gap:12px;margin:32px 0 20px}.primary,.secondary,.iconBtn{border:1px solid #ffffff22;border-radius:12px;padding:12px 20px;transition:.2s}.primary{background:linear-gradient(110deg,#55d9d3,#7b8dff);color:#071326;border:0;font-weight:800}.secondary{background:#ffffff0b;color:#d9e6ff}.iconBtn{background:#ffffff08;font-size:11px;padding:8px 12px}.primary:hover,.secondary:hover,.iconBtn:hover{transform:translateY(-2px);filter:brightness(1.15)}.hint,.muted,small{color:var(--muted)}.wide{width:min(780px,94vw);text-align:center}.cards{display:flex;gap:14px;margin-top:28px}.starter{flex:1;text-align:left;padding:19px;border:1px solid var(--line);background:#ffffff08;border-radius:16px;transition:.2s}.starter:hover{border-color:var(--cyan);background:#62e2e214;transform:translateY(-4px)}.starter h3{margin:8px 0 3px}.starter p{font-size:12px;line-height:1.5;color:var(--muted)}.orb{width:52px;height:52px;border-radius:50%;display:grid;place-items:center;font-size:27px;box-shadow:0 0 30px currentColor}.starter .stats{font-size:11px;color:var(--cyan);letter-spacing:1px}.modal{max-width:620px;width:90vw}.modal h2{margin:0 0 6px}.hud{display:flex;align-items:center;gap:18px}.brand{display:flex;align-items:center;gap:10px}.brand b{font-size:13px;letter-spacing:3px}.brand small{display:block;font-size:8px;letter-spacing:2px}.sigil{font-size:25px;color:var(--cyan);text-shadow:0 0 15px var(--cyan)}#topbar{position:absolute;top:0;left:0;right:0;padding:20px 28px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #ffffff0b;background:linear-gradient(#061020aa,transparent);z-index:2}#gameUI{position:absolute;inset:0;pointer-events:none}#quest{position:absolute;top:92px;left:28px;display:flex;gap:10px;align-items:center;background:#09182bcc;padding:12px 16px;border:1px solid var(--line);border-radius:12px;pointer-events:auto}#quest small{display:block;font-size:9px;letter-spacing:2px}#quest b{font-size:13px}.questDot{width:8px;height:8px;background:var(--gold);border-radius:50%;box-shadow:0 0 15px var(--gold)}#partyMini{position:absolute;right:25px;top:92px;width:190px;pointer-events:auto}.pm{display:flex;align-items:center;gap:8px;margin:7px 0;font-size:11px}.pm .bar{height:5px;flex:1;background:#0006;border-radius:5px;overflow:hidden}.pm i{display:block;height:100%;background:linear-gradient(90deg,var(--cyan),#9ff)}#controls{position:absolute;bottom:26px;left:28px;text-align:center;pointer-events:auto}.#controls div{display:block}.#controls button{background:#0b1e36cc;border:1px solid var(--line);width:40px;height:36px;border-radius:8px;margin:2px;font-size:14px}.#controls small{display:block;font-size:9px;letter-spacing:2px;margin-top:7px}#zoneCards{position:absolute;bottom:25px;right:25px;display:flex;gap:6px;pointer-events:auto}.zonePip{width:12px;height:12px;border-radius:50%;border:1px solid #ffffff66}.zonePip.on{background:var(--cyan);box-shadow:0 0 13px var(--cyan)}#dialogue{position:absolute;inset:0;display:grid;place-items:end center;padding:35px;background:linear-gradient(transparent 45%,#050914cc)}.dialogCard{width:min(760px,90vw);padding:25px 30px;display:grid;grid-template-columns:1fr auto;gap:15px;align-items:center}.dialogCard #dialogueTag{grid-column:1/-1;margin:0}.dialogCard #dialogueText{font-size:17px;line-height:1.5}.dialogCard button{grid-row:2;grid-column:2}#battle{position:absolute;inset:0;background:linear-gradient(140deg,#101b3a,#382449);padding:25px 5vw}.battleTop{display:flex;justify-content:space-between;align-items:start}.battleTop h2{margin:4px 0;font-size:32px}.battleThreat{color:var(--pink);letter-spacing:3px;font-size:11px}.battleStage{height:370px;position:relative}.battleStage canvas{width:100%;height:100%}#enemyInfo,#playerInfo{position:absolute;width:245px;padding:13px;background:#071222cc;border:1px solid var(--line);border-radius:12px}.#enemyInfo{top:30px;right:4%}#playerInfo{bottom:5px;left:4%}.monName{display:flex;justify-content:space-between;font-weight:700}.hpbar{height:8px;background:#0008;border-radius:9px;margin-top:10px;overflow:hidden}.hpfill{height:100%;background:linear-gradient(90deg,#61dbbf,#b8f28c);transition:width .45s}.hpfill.low{background:linear-gradient(90deg,#ff6685,#ffd06b)}#battleLog{height:36px;color:#b9c8e2;font-size:13px;text-align:center}.abilityGrid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;max-width:570px;flex:1}.ability{background:#ffffff0d;border:1px solid var(--line);padding:10px 13px;border-radius:10px;text-align:left}.ability:hover{border-color:var(--cyan);background:#62e2e214}.ability b{display:block;font-size:12px}.ability small{font-size:10px}.#battleActions{display:flex;gap:9px;align-items:center;max-width:900px;margin:auto}.#toast{position:absolute;left:50%;bottom:26px;transform:translateX(-50%);background:#101d35ee;border:1px solid var(--line);border-radius:30px;padding:11px 20px;opacity:0;transition:.3s;z-index:8}.#toast.show{opacity:1}#flash{position:absolute;inset:0;background:#b9f5ff;opacity:0;pointer-events:none;z-index:9}.flash{animation:flash .35s}@keyframes flash{0%{opacity:.55}100%{opacity:0}}@media(max-width:650px){.titleCard{padding:28px 20px}.titleCard h1{font-size:58px}.cards{flex-direction:column}.hud span{display:none}#topbar{padding:15px}.battleTop h2{font-size:23px}.battleStage{height:310px}.battleStage canvas{height:100%}#battleActions{flex-wrap:wrap}.abilityGrid{min-width:100%}.dialogCard{grid-template-columns:1fr}.dialogCard button{grid-column:1;grid-row:auto}.#quest{left:15px;top:75px}.partyMini{right:10px}}
</style>
</head>
<body>
<div id="game"><canvas id="scene" width="960" height="540"></canvas><div id="vignette"></div><header id="topbar"><div class="brand"><span class="sigil">✦</span><div><b>ASTERWEAVE</b><small>THE LUMENBOUND ATLAS</small></div></div><div class="hud"><span id="zoneName">—</span><span id="shrineStatus">✧ wayfarer</span><button id="muteBtn" class="iconBtn">◖ sound</button><button id="pauseBtn" class="iconBtn">Ⅱ pause</button></div></header><section id="titleScreen" class="screen"><div class="titleCard"><div class="eyebrow">FIELD LEDGER // 01</div><h1>Aster<span>weave</span></h1><p class="tagline">Bind the wild light. Reconnect the six horizons.</p><div class="titleLore">The sky-thread shattered into living sparks. Eight rare beings remember its pattern; four anchors still hold the world apart.</div><div class="titleBtns"><button id="newBtn" class="primary">New Binding</button><button id="continueBtn" class="secondary">Continue</button></div><small class="hint">A quiet atlas of small battles, bright choices, and strange friends.</small></div></section><section id="startScreen" class="screen hidden"><div class="modal wide"><div class="eyebrow">CHOOSE YOUR FIRST THREAD</div><h2>Who answers the lantern?</h2><p class="muted">Three sparks drift close. Each changes the shape of your first journey.</p><div id="starterChoices" class="cards"></div></div></section><section id="gameUI" class="hidden"><div id="quest"><span class="questDot"></span><div><small>CURRENT THREAD</small><b id="questText">Find the first wild signal.</b></div></div><div id="partyMini"></div><div id="controls"><button data-move="up">▲</button><div><button data-move="left">◀</button><button data-move="down">▼</button><button data-move="right">▶</button></div><small>WASD / ARROWS</small></div><div id="zoneCards"></div></section><section id="dialogue" class="hidden"><div class="dialogCard"><div id="dialogueTag" class="eyebrow">FIELD NOTE</div><div id="dialogueText"></div><button id="dialogueNext" class="primary">Continue ↗</button></div></section><section id="battle" class="hidden"><div class="battleTop"><div><small>ENCOUNTER // <span id="battleZone"></span></small><h2 id="battleTitle">Unknown signal</h2></div><div class="battleThreat" id="battleThreat">WILD</div></div><div id="battleStage"><canvas id="battleCanvas" width="960" height="370"></canvas><div id="enemyInfo"></div><div id="playerInfo"></div></div><div id="battleLog">A tremor passes through the grass.</div><div id="battleActions"><div class="abilityGrid" id="abilityGrid"></div><button id="bagBtn" class="secondary">◈ satchel</button><button id="switchBtn" class="secondary">⇄ switch</button><button id="fleeBtn" class="secondary">↟ withdraw</button></div></section><section id="modalLayer" class="hidden"></section><div id="toast"></div><div id="flash"></div></div>
<script>
'use strict';

// ── const: TYPES ──
const TYPES={A:'Lumen',B:'Mire',C:'Cinder',D:'Gale',E:'Tide',F:'Prism',G:'Null'};

// ── const: ZONES ──
const ZONES=[{name:'Morrow Fen',sub:'the blue hush',colors:['#0c2940','#18344b'],rate:.018,story:'A low bell rings beneath the reedwater.'},{name:'Vellum Expanse',sub:'where maps grow',colors:['#352a54','#6b3f61'],rate:.024,story:'Paper-colored moths carry a new constellation.'},{name:'Siltglass Steps',sub:'the patient ascent',colors:['#493125','#a75e48'],rate:.028,story:'The stone remembers footsteps that are not yours.'},{name:'Auric Hollow',sub:'the amber below',colors:['#263d3a','#b07c42'],rate:.035,story:'An anchor dreams aloud, and the dream has teeth.'},{name:'Cloudroot Shelf',sub:'above the weather',colors:['#203956','#7398ba'],rate:.04,story:'The wind knots itself into a door.'},{name:'The Quiet Meridian',sub:'all horizons meet',colors:['#261e42','#743d75'],rate:.05,story:'Every lost thread points here.'}];

// ── const: CREATURES ──
const CREATURES=[
{name:'Murnlet',aff:'A',base:[42,30,28,32],lore:'A pocket-sized glow that nests in abandoned lanterns.',moves:['Glimmer Nip','Warm Thread','Halo Mend','First Gleam']},
{name:'Brindlewisp',aff:'B',base:[48,27,34,25],lore:'It leaves patient rings on the surface of still pools.',moves:['Bog Pluck','Moss Veil','Silt Soothe','Tangle Peck']},
{name:'Cairnix',aff:'C',base:[38,38,22,35],lore:'A coal-hearted crawler builds tiny cairns for passing stars.',moves:['Cinder Tap','Ashen Rush','Kindlehide','Coal Choir']},
{name:'Swayfin',aff:'D',base:[40,29,25,46],lore:'Its fins are folded breezes, always pointing toward change.',moves:['Draft Dart','Slipstream','Skirl Mend','Whistlebind']},
{name:'Oolune',aff:'E',base:[52,25,32,24],lore:'A moon-pale swimmer collects dew in its translucent crown.',moves:['Dew Lance','Undertow','Tidekindle','Moonwash']},
{name:'Veyr',aff:'F',base:[35,36,27,40],lore:'It fractures moonlight into colors no eye can name.',moves:['Prism Pin','Scatterray','Color Ward','Splitstep']},
{name:'Nulldeer',aff:'G',base:[45,31,42,20],lore:'A gentle antlered absence makes room wherever it stands.',moves:['Quiet Hoof','Hollow Guard','Still Field','Unmake']},
{name:'Ruskettle',aff:'B',base:[44,33,30,30],lore:'Its shell steams when it is pleased.',moves:['Brine Bash','Mire Curl','Kettleheart','Scalding Note']},
{name:'Thimbral',aff:'A',base:[34,42,20,38],lore:'A needle-winged mote stitches cracks in the dawn.',moves:['Needle Ray','Bright Stitch','Pinwheel','Threadcut']},
{name:'Gloamjaw',aff:'C',base:[55,35,38,18],lore:'Its wide smile is a doorway to a warmer dark.',moves:['Ember Bite','Dusk Roar','Smoke Mantle','Jawflare']},
{name:'Fennelark',aff:'D',base:[39,28,28,44],lore:'It sings only when the landscape has forgotten its name.',moves:['Larkbolt','Airsong','Feather Mend','Updraft']},
{name:'Pell',aff:'E',base:[46,31,29,34],lore:'A shy tidekin hides small pearls of weather under its tongue.',moves:['Pearl Pop','Drift','Rainveil','Lowtide']},
{name:'Chromlet',aff:'F',base:[40,34,31,39],lore:'It wears a different color for every promise kept nearby.',moves:['Hue Jab','Refraction','Prismatic Skin','Flashfold']},
{name:'Moth Null',aff:'G',base:[37,29,45,29],lore:'A silent flutter that makes loud rooms suddenly kind.',moves:['Mute Dust','Softening','Blanket','Quietus']},
{name:'Yarrowick',aff:'A',base:[50,31,35,27],lore:'Its crown of leaves glows brightest under difficult questions.',moves:['Leafspark','Rootlace','Green Pulse','Crownlight']},
{name:'Vurn',aff:'C',base:[43,41,24,29],lore:'It warms cold stones and considers this a grand adventure.',moves:['Furnace Knuckle','Heat Haze','Flareguard','Red Rattle']},
{name:'Dreel',aff:'D',base:[36,30,30,49],lore:'A ribbon-bodied skimmer loops through sunbeams for breakfast.',moves:['Ribbon Cut','Gustbell','Loop Mend','Vane Dance']},
{name:'Marell',aff:'E',base:[58,27,36,19],lore:'Its broad back is a safe harbor for drifting sparks.',moves:['Harbor Hit','Deepcall','Shelter','Wakefall']}];

// ── const: MOVES ──
const MOVES={};

// ── const: EFFECTS ──
const EFFECTS=['burn','frost','shock','heal','boost','barrier','regen','multi','priority'];

// ── function: buildMoves ──
function buildMoves(){let n=0;CREATURES.forEach(c=>c.moves.forEach((name,i)=>{MOVES[name]={name,power:18+i*7+(n%4)*3,acc:86+(n%3)*4,aff:c.aff,pp:8+(n%5)*2,effect:EFFECTS[n%EFFECTS.length]};n++}));}

// ── function: makeMon ──
function makeMon(template,level=5,wild=false){let m={...template,level,hpMax:template.base[0]+level*4,hp:template.base[0]+level*4,atk:template.base[1]+level*2,def:template.base[2]+level*2,spd:template.base[3]+level*2,exp:0,pp:{}};m.moves=m.moves.map(x=>MOVES[x]);m.moves.forEach(x=>m.pp[x.name]=x.pp);m.wild=wild;return m}

// ── function: typeMult ──
function typeMult(a,d){if(a==='F')return d==='G'?1.3:1.12;if(d==='G')return a==='F'?.7:0.78;const win={A:'B',B:'C',C:'A',D:'A,C',E:'D,B'};return win[a]?.split(',').includes(d)?1.5:win[d]?.split(',').includes(a)?.7:1}

// ── function: save ──
function save(){if(!state)return;localStorage.setItem('asterweave-save',JSON.stringify({zone:state.zone,x:state.x,y:state.y,party:state.party.map(m=>({...m, moves:m.moves.map(x=>x.name)})),inventory:state.inventory,anchors:state.anchors,started:state.started}))}

// ── function: toast ──
function toast(t){let e=document.querySelector('#toast');e.textContent=t;e.classList.add('show');setTimeout(()=>e.classList.remove('show'),2400)}

// ── function: spark ──
function spark(x,y,col){ctx.fillStyle=col;for(let i=0;i<8;i++){ctx.globalAlpha=Math.random();ctx.fillRect(x+Math.random()*60-30,y+Math.random()*60-30,2,2)}ctx.globalAlpha=1}

// ── function: drawWorld ──
function drawWorld(){let z=ZONES[state.zone],w=scene.width,h=scene.height,g=ctx.createLinearGradient(0,0,w,h);g.addColorStop(0,z.colors[0]);g.addColorStop(1,z.colors[1]);ctx.fillStyle=g;ctx.fillRect(0,0,w,h);ctx.globalAlpha=.18;for(let i=0;i<70;i++){let x=(i*137+state.t*.02)%w,y=(i*71+Math.sin(i+state.t/800)*18)%h;ctx.fillStyle=i%3?'#9be7ed':'#ffd994';ctx.beginPath();ctx.arc(x,y,1+(i%3),0,7);ctx.fill()}ctx.globalAlpha=1;ctx.strokeStyle='#b9eced18';for(let x=0;x<w;x+=48){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,h);ctx.stroke()}for(let y=0;y<h;y+=48){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(w,y);ctx.stroke()}let px=w/2+state.x*48,py=h/2+state.y*48;ctx.fillStyle='#ffd58a';ctx.shadowBlur=22;ctx.shadowColor='#ffd58a';ctx.beginPath();ctx.arc(px,py,10,0,7);ctx.fill();ctx.shadowBlur=0;ctx.strokeStyle='#ffffff77';ctx.strokeRect(w/2-25,h/2-25,50,50);for(let i=0;i<state.zone+2;i++){let ax=90+i*155,ay=150+Math.sin(i*3)*75;ctx.fillStyle=i<state.anchors?'#7ae5db':'#ffffff20';ctx.beginPath();ctx.arc(ax,ay,10,0,7);ctx.fill();ctx.fillStyle='#ffffff55';ctx.font='10px sans-serif';ctx.fillText(i<state.anchors?'ANCHOR':'LOCKED',ax-22,ay+25)}}

// ── function: renderParty ──
function renderParty(){document.querySelector('#partyMini').innerHTML=state.party.map(m=>`<div class="pm"><span>${m.name}</span><div class="bar"><i style="width:${m.hp/m.hpMax*100}%"></i></div><small>Lv${m.level}</small></div>`).join('');document.querySelector('#zoneName').textContent=ZONES[state.zone].name;document.querySelector('#questText').textContent=state.anchors<4?`Reach the ${['Fen','Expanse','Steps','Hollow'][state.anchors]} anchor`:'The four anchors answer. Roam freely.';document.querySelector('#zoneCards').innerHTML=ZONES.map((z,i)=>`<i class="zonePip ${i<=state.zone?'on':''}" title="${z.name}"></i>`).join('')}

// ── function: move ──
function move(dx,dy){if(state.mode!=='world')return;state.x=Math.max(-7,Math.min(7,state.x+dx));state.y=Math.max(-4,Math.min(4,state.y+dy));if(state.x===0&&state.y===0){state.party.forEach(m=>m.hp=m.hpMax);save();toast('Shrine of Still Current: party restored, atlas saved.')}if(state.x===7&&dx>0){if(state.zone<5&&state.anchors>state.zone){state.zone++;state.x=-6;state.y=0;showDialogue('HORIZON CROSSED',`${ZONES[state.zone].story} The next horizon unfolds around your lantern.`);save()}else if(state.zone<5)toast('The horizon is sealed. Answer the anchor first.')}if(Math.random()<ZONES[state.zone].rate){startBattle();return}if(Math.random()<.04)showDialogue('FIELD NOTE',ZONES[state.zone].story);renderParty()}

// ── function: showDialogue ──
function showDialogue(tag,text,after){state.dialogueAfter=after;document.querySelector('#dialogueTag').textContent=tag;document.querySelector('#dialogueText').textContent=text;document.querySelector('#dialogue').classList.remove('hidden')}

// ── function: startBattle ──
function startBattle(){state.mode='battle';let base=CREATURES[(Math.floor(Math.random()*CREATURES.length)+state.zone*2)%CREATURES.length];state.enemy=makeMon(base,Math.min(5+state.zone*4+Math.floor(Math.random()*4),45),true);document.querySelector('#gameUI').classList.add('hidden');document.querySelector('#battle').classList.remove('hidden');document.querySelector('#battleZone').textContent=ZONES[state.zone].name;document.querySelector('#battleTitle').textContent=state.enemy.name;document.querySelector('#battleThreat').textContent=state.zone>=4?'FERAL SIGNAL':'WILD SIGNAL';renderBattle();drawBattle()}

// ── function: renderBattle ──
function renderBattle(){let e=state.enemy,p=state.party[state.active];document.querySelector('#enemyInfo').innerHTML=infoHTML(e);document.querySelector('#playerInfo').innerHTML=infoHTML(p);document.querySelector('#abilityGrid').innerHTML=p.moves.map((m,i)=>`<button class="ability" data-ability="${i}"><b>${m.name}</b><small>${TYPES[m.aff]} · PWR ${m.power} · ${m.effect} · ◇${p.pp[m.name]}</small></button>`).join('');document.querySelectorAll('[data-ability]').forEach(b=>b.onclick=()=>useMove(+b.dataset.ability));}

// ── function: infoHTML ──
function infoHTML(m){return `<div class="monName"><span>${m.name}</span><small>Lv ${m.level} · ${TYPES[m.aff]}</small></div><div class="hpbar"><div class="hpfill ${m.hp<m.hpMax*.3?'low':''}" style="width:${Math.max(0,m.hp/m.hpMax*100)}%"></div></div><small>${Math.max(0,m.hp)} / ${m.hpMax} vitality</small>`}

// ── function: drawBattle ──
function drawBattle(){let c=document.querySelector('#battleCanvas'),b=c.getContext('2d'),w=c.width,h=c.height;b.clearRect(0,0,w,h);b.fillStyle='#ffffff08';b.beginPath();b.ellipse(w*.72,h*.58,150,32,0,0,7);b.fill();b.beginPath();b.ellipse(w*.25,h*.82,180,35,0,0,7);b.fill();drawCreature(b,state.enemy,w*.72,h*.5,1.25);drawCreature(b,state.party[state.active],w*.25,h*.73,1)}

// ── function: drawCreature ──
function drawCreature(b,m,x,y,s){let col={A:'#7ee9d4',B:'#80c69d',C:'#ff9c71',D:'#a7b6ff',E:'#75c7ec',F:'#f4d27e',G:'#c0b5e9'}[m.aff];b.save();b.translate(x,y);b.scale(s,s);b.shadowBlur=25;b.shadowColor=col;b.fillStyle=col;b.beginPath();b.arc(0,-35,32,0,7);b.fill();b.fillStyle='#081322';b.beginPath();b.arc(-10,-40,4,0,7);b.arc(10,-40,4,0,7);b.fill();b.strokeStyle='#ffffffaa';b.lineWidth=3;b.beginPath();b.arc(0,-30,18,.2,2.9);b.stroke();b.restore()}

// ── function: useMove ──
function useMove(i){if(state.busy)return;let p=state.party[state.active],m=p.moves[i];if(!m||p.pp[m.name]<=0){toast('That thread is quiet.');return}state.busy=true;p.pp[m.name]--;let hit=Math.random()*100<m.acc;let log='';if(hit){let mult=typeMult(m.aff,state.enemy.aff),stab=m.aff===p.aff?1.25:1,crit=Math.random()<.1?1.5:1;let dmg=Math.max(1,Math.floor(.4*p.level*p.atk/state.enemy.def*mult*stab*crit+Math.random()*4));state.enemy.hp-=dmg;log=`${p.name} cast ${m.name}: ${dmg} impact${crit>1?' — CRITICAL':''}!`;if(mult>1){log+=' Superbly aligned.';document.querySelector('#flash').classList.add('flash');setTimeout(()=>document.querySelector('#flash').classList.remove('flash'),350);spark(700,180,'#ffd98a')}if(m.effect==='heal'){p.hp=Math.min(p.hpMax,p.hp+12);log+=' Vitality returns.'}if(m.effect==='burn')state.enemy.burn=3;if(m.effect==='frost')state.enemy.frost=2;if(m.effect==='boost')p.spd+=3;if(m.effect==='barrier')p.barrier=1}else log=`${p.name}'s ${m.name} slipped past the signal.`;document.querySelector('#battleLog').textContent=log;renderBattle();drawBattle();setTimeout(()=>{if(state.enemy.hp<=0)winBattle();else enemyTurn()},650)}

// ── function: enemyTurn ──
function enemyTurn(){let e=state.enemy,p=state.party[state.active],d=Math.max(1,Math.floor(.4*e.level*e.atk/p.def*(typeMult(e.aff,p.aff))));if(p.barrier){p.barrier=0;d=0}p.hp=Math.max(0,p.hp-d);document.querySelector('#battleLog').textContent=d?`${e.name} answers. ${d} vitality lost.`:`${p.name}'s barrier turns the blow aside.`;renderBattle();drawBattle();state.busy=false;if(p.hp<=0){let next=state.party.findIndex(x=>x.hp>0);if(next>=0){state.active=next;toast(`${state.party[next].name} takes the thread.`)}else{state.mode='world';state.party.forEach(x=>x.hp=x.hpMax);document.querySelector('#battle').classList.add('hidden');document.querySelector('#gameUI').classList.remove('hidden');toast('The shrine pulls you home.');}renderParty()}}

// ── function: winBattle ──
function winBattle(){let e=state.enemy,p=state.party[state.active],xp=10+e.level*4;p.exp+=xp;document.querySelector('#battleLog').textContent=`Signal quiet. ${xp} resonance earned.`;if(Math.random()<.42&&state.party.length<6){setTimeout(()=>capturePrompt(),500)}else setTimeout(endBattle,800);if(state.zone>=0&&Math.random()<.1){state.anchors++;toast('An anchor has noticed your courage.')}levelCheck(p)}

// ── function: levelCheck ──
function levelCheck(m){while(m.exp>=m.level*18&&m.level<50){m.exp-=m.level*18;m.level++;m.hpMax+=4;m.hp=m.hpMax;m.atk+=2;m.def+=2;m.spd+=2;toast(`${m.name} reached level ${m.level}!`)}}

// ── function: capturePrompt ──
function capturePrompt(){let ok=confirm(`The ${state.enemy.name} is wavering. Use a Lumenloop to bind it?`);if(ok){let chance=.35+(1-state.enemy.hp/state.enemy.hpMax)*.5+(state.inventory.orb-1)*.1;if(Math.random()<chance){state.party.push(makeMon(CREATURES.find(c=>c.name===state.enemy.name),state.enemy.level));state.inventory.orb=Math.max(0,state.inventory.orb-1);toast(`${state.enemy.name} joined your atlas!`)}else toast('The loop unraveled.');}endBattle()}

// ── function: endBattle ──
function endBattle(){state.mode='world';state.busy=false;document.querySelector('#battle').classList.add('hidden');document.querySelector('#gameUI').classList.remove('hidden');renderParty();save()}

// ── main ──
buildMoves();const scene=document.querySelector('#scene'),ctx=scene.getContext('2d');let state={mode:'title',zone:0,x:0,y:0,t:0,party:[],active:0,inventory:{orb:3,potion:2},anchors:0,busy:false};
function boot(){document.querySelector('#titleScreen').classList.add('hidden');document.querySelector('#startScreen').classList.remove('hidden');document.querySelector('#starterChoices').innerHTML=CREATURES.slice(0,3).map((c,i)=>`<button class="starter" data-starter="${i}"><div class="orb" style="color:${['#7ee9d4','#80c69d','#ff9c71'][i]}">${['✦','◌','◈'][i]}</div><h3>${c.name}</h3><div class="stats">${TYPES[c.aff]} · ${c.base.join(' / ')}</div><p>${c.lore}</p></button>`).join('');document.querySelectorAll('[data-starter]').forEach(x=>x.onclick=()=>startNew(+x.dataset.starter))}
function startNew(i){state={...state,mode:'world',started:true,party:[makeMon(CREATURES[i],5)],inventory:{orb:3,potion:2},anchors:0};document.querySelector('#startScreen').classList.add('hidden');document.querySelector('#gameUI').classList.remove('hidden');showDialogue('THE FIRST THREAD',`Asterweave has gone quiet, but ${state.party[0].name} answers your lantern. Move with the arrow keys, then follow the glints toward a wild signal.`);renderParty();save()}
function continueGame(){let raw=localStorage.getItem('asterweave-save');if(!raw){toast('No binding found in the archive.');return}let d=JSON.parse(raw);state={...state,...d,mode:'world',party:d.party.map(m=>{let x=makeMon(CREATURES.find(c=>c.name===m.name)||CREATURES[0],m.level);return {...x,...m,moves:m.moves.map(n=>MOVES[n]||MOVES[x.moves[0].name])}})};document.querySelector('#titleScreen').classList.add('hidden');document.querySelector('#gameUI').classList.remove('hidden');renderParty();toast('The atlas opens at your last shrine.')}
function loop(ts){state.t=ts; if(state.mode==='world'){drawWorld();}requestAnimationFrame(loop)}
document.querySelector('#newBtn').onclick=boot;document.querySelector('#continueBtn').onclick=continueGame;document.querySelectorAll('[data-move]').forEach(b=>b.onclick=()=>{let d={up:[0,-1],down:[0,1],left:[-1,0],right:[1,0]}[b.dataset.move];move(...d)});document.addEventListener('keydown',e=>{if(['ArrowUp','w','W'].includes(e.key))move(0,-1);if(['ArrowDown','s','S'].includes(e.key))move(0,1);if(['ArrowLeft','a','A'].includes(e.key))move(-1,0);if(['ArrowRight','d','D'].includes(e.key))move(1,0);if(e.key==='Escape'&&state.mode==='world')showDialogue('ATLAS PAUSED','The lantern waits. Press Continue to return to the field.')});document.querySelector('#dialogueNext').onclick=()=>{document.querySelector('#dialogue').classList.add('hidden');if(state.dialogueAfter)state.dialogueAfter()};document.querySelector('#switchBtn').onclick=()=>{if(state.party.length<2){toast('Only one thread is bound.');return}state.active=(state.active+1)%state.party.length;document.querySelector('#battleLog').textContent=`${state.party[state.active].name} enters the weave.`;renderBattle();drawBattle()};document.querySelector('#bagBtn').onclick=()=>{let p=state.party[state.active];if(state.inventory.potion){state.inventory.potion--;p.hp=Math.min(p.hpMax,p.hp+Math.floor(p.hpMax*.35));document.querySelector('#battleLog').textContent='A Vellum Tonic restores the thread.';renderBattle()}else toast('Your satchel is empty.')};document.querySelector('#fleeBtn').onclick=()=>{if(Math.random()<.7)endBattle();else{document.querySelector('#battleLog').textContent='The signal knots the path. Withdraw failed.';setTimeout(enemyTurn,500)}};document.querySelector('#pauseBtn').onclick=()=>showDialogue('ATLAS PAUSED','Your progress is saved automatically at every calm moment.');document.querySelector('#muteBtn').onclick=e=>{e.currentTarget.textContent=e.currentTarget.textContent.includes('sound')?'◖ muted':'◖ sound';toast(e.currentTarget.textContent.includes('muted')?'Ambient hush enabled.':'Ambient signal restored.')};renderParty();requestAnimationFrame(loop);
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-luna","provider":"openrouter","persona":"composer","sessionId":"56e21445-dec9-47e6-8a00-9f5fddadd894","tokensIn":137132,"tokensOut":9588,"tokensTotal":146720,"turns":13,"toolCalls":12,"failedToolCalls":0,"timestamp":"2026-07-14T03:27:52.925Z"} -->