--- a/src/index.html
+++ b/src/index.html
@@ -4,7 +4,7 @@
let water=700,ac;function snd(f=220,d=.08,v=.025,type='square',m=.55){try{ac??=new AudioContext;ac.state=='suspended'&&ac.resume();let o=ac.createOscillator(),g=ac.createGain(),n=ac.currentTime;o.type=type;o.frequency.setValueAtTime(f,n);o.frequency.exponentialRampToValueAtTime(Math.max(35,f*m),n+d);g.gain.setValueAtTime(v,n);g.gain.exponentialRampToValueAtTime(.001,n+d);o.connect(g).connect(ac.destination);o.start();o.stop(n+d)}catch(e){}}
// ---- Online (js13kGames 2026 relay: native WebSocket, deterministic seed lockstep) ----
function wsend(m){try{sock&&sock.readyState==1&&sock.send(m)}catch(e){}}
-function netOpen(code,h){isHost=h;room=code;net=1;netUI='conn';myId='';guestId='';clearTimeout(netT);netT=setTimeout(()=>{if(netUI=='conn'||netUI=='wait')netFail();else if(netUI=='qwait'&&++qmTry<3){qmPool++;netOpen('QM'+qmPool.toString(36),0)}},7000);try{sock=new WebSocket('wss://relay.js13kgames.com/parties/spectrumsprites/'+code)}catch(e){netFail();return}sock.onmessage=e=>onNet(''+e.data);sock.onerror=()=>{if(!started)netFail()};sock.onclose=()=>{if(started&&net&&!over)netUI='left';else if(!started&&netUI!='full'&&netUI!='fail')netFail()};sock.onopen=()=>{netUI=h?'host':qm?'qwait':'wait';if(qm)qmT=setTimeout(()=>{if(!started&&netUI=='qwait'){isHost=1;netUI='host';if(lp&&!guestId){guestId=lp;seed0=Math.random()*1e9>>>0;wsend('S'+guestId+'|'+seed0+'|'+myId)}}},300+Math.random()*1200)}}
+function netOpen(){netFail()}
function netFail(){netUI='fail';net=0;clearTimeout(netT);try{sock&&sock.close()}catch(e){}sock=0}
function netHost(){let s='ABCDEFGHJKLMNPQRSTUVWXYZ23456789',c='';for(let i=0;i<4;i++)c+=s[Math.random()*32|0];netOpen(c,1)}
function endNet(){net=0;started=0;over=0;netUI='';isHost=0;guestId='';qm=0;clearTimeout(qmT);clearTimeout(netT);try{sock&&sock.close()}catch(e){}sock=0}
@@ -70,7 +70,7 @@
let sn=P.find(q=>!q.dead&&q.team!=p.team&&Math.hypot(q.x-p.x,q.y-p.y)<75);if(best.s<10&&sn){p.dir=sn.x>p.x?1:-1;weapon=10;power=0;fire();return}
p.dir=best.d;p.aim=best.a;weapon=best.w;aiPw=best.pw;charging=1;power=0}
function localStart(){net=0;started=1;document.body.classList.add('play');init()}
-addEventListener('keydown',e=>{let k=e.code;if(!started){if(netUI=='join'){if(k=='Enter'){if(joinBuf.length==4)netOpen(joinBuf,0)}else if(k=='Backspace')joinBuf=joinBuf.slice(0,-1);else if(k=='Escape'){netUI='';net=0}else if(joinBuf.length<4&&e.key&&e.key.length==1&&/[a-z0-9]/i.test(e.key))joinBuf+=e.key.toUpperCase();return}if(netUI=='fail'||netUI=='full'){if(k=='Enter'||k=='Escape'){netUI='';net=0}return}if(netUI){if(k=='Escape')endNet();return}if(k=='ArrowUp'){msel=(msel+4)%5;return}if(k=='ArrowDown'){msel=(msel+1)%5;return}if(k=='Enter'){msel==0?localStart():msel==1?aiStart():msel==2?quickMatch():msel==3?netHost():(netUI='join',joinBuf='');return}return}if(net&&netUI=='left'){if(k=='Enter'){net=0;netUI='';try{sock&&sock.close()}catch(x){}sock=0}return}if(over){if(k=='Enter')net?endNet():init();return}keys[k]=1;let mine=(!net||P[active]&&myTeam==P[active].team)&&!(aiMode&&P[active]&&P[active].team==1);if(k=='KeyW'&&!phase&&mine)choose(1);if(k=='KeyS'&&!phase&&mine)choose(-1);if(k=='Space')e.preventDefault();if(k=='KeyM')mus=!mus});addEventListener('keyup',e=>keys[e.code]=0);function point(e){let r=c.getBoundingClientRect();mouse.x=(e.clientX-r.left)*VW/r.width;mouse.y=(e.clientY-r.top)*VH/r.height}addEventListener('pointermove',point);c.addEventListener('pointerdown',e=>{point(e);if(!started){if(!netUI&&!e.button){let mx=373;for(let i=0;i<5;i++){let my=210+i*36;if(mouse.x>mx&&mouse.xmy&&mouse.y=0&&gx<4&&gy>=0&&gy<3&&i<12){let p=P[active];if(!i||ammo[p.team][i]){weapon=i;inv=0;wpop=150;snd(500,.04,.014,'square',1.1);say(i?WN[i]:SN[p.type],36)}}return}if(!phase&&!charging&&mine){charging=1;power=0}});addEventListener('pointerup',e=>{if(!e.button&&started&&!over&&charging){charging=0;fire()}});addEventListener('pointercancel',()=>{charging=power=0});c.addEventListener('contextmenu',e=>e.preventDefault());c.addEventListener('wheel',e=>{e.preventDefault();if(started){zoom=clamp(zoom+(e.deltaY<0?.15:-.15),1,2.2);cam=cClamp(cam)}},{passive:false});addEventListener('blur',()=>{keys={};charging=power=0});
+addEventListener('keydown',e=>{let k=e.code;if(!started){if(netUI=='join'){if(k=='Enter'){if(joinBuf.length==4)netOpen(joinBuf,0)}else if(k=='Backspace')joinBuf=joinBuf.slice(0,-1);else if(k=='Escape'){netUI='';net=0}else if(joinBuf.length<4&&e.key&&e.key.length==1&&/[a-z0-9]/i.test(e.key))joinBuf+=e.key.toUpperCase();return}if(netUI=='fail'||netUI=='full'){if(k=='Enter'||k=='Escape'){netUI='';net=0}return}if(netUI){if(k=='Escape')endNet();return}if(k=='ArrowUp'||k=='ArrowDown'){msel=1-msel;return}if(k=='Enter'){msel==0?localStart():aiStart();return}return}if(net&&netUI=='left'){if(k=='Enter'){net=0;netUI='';try{sock&&sock.close()}catch(x){}sock=0}return}if(over){if(k=='Enter')net?endNet():init();return}keys[k]=1;let mine=(!net||P[active]&&myTeam==P[active].team)&&!(aiMode&&P[active]&&P[active].team==1);if(k=='KeyW'&&!phase&&mine)choose(1);if(k=='KeyS'&&!phase&&mine)choose(-1);if(k=='Space')e.preventDefault();if(k=='KeyM')mus=!mus});addEventListener('keyup',e=>keys[e.code]=0);function point(e){let r=c.getBoundingClientRect();mouse.x=(e.clientX-r.left)*VW/r.width;mouse.y=(e.clientY-r.top)*VH/r.height}addEventListener('pointermove',point);c.addEventListener('pointerdown',e=>{point(e);if(!started){if(!netUI&&!e.button){let mx=373;for(let i=0;i<2;i++){let my=210+i*36;if(mouse.x>mx&&mouse.xmy&&mouse.y=0&&gx<4&&gy>=0&&gy<3&&i<12){let p=P[active];if(!i||ammo[p.team][i]){weapon=i;inv=0;wpop=150;snd(500,.04,.014,'square',1.1);say(i?WN[i]:SN[p.type],36)}}return}if(!phase&&!charging&&mine){charging=1;power=0}});addEventListener('pointerup',e=>{if(!e.button&&started&&!over&&charging){charging=0;fire()}});addEventListener('pointercancel',()=>{charging=power=0});c.addEventListener('contextmenu',e=>e.preventDefault());c.addEventListener('wheel',e=>{e.preventDefault();if(started){zoom=clamp(zoom+(e.deltaY<0?.15:-.15),1,2.2);cam=cClamp(cam)}},{passive:false});addEventListener('blur',()=>{keys={};charging=power=0});
function update(){t++;if(mus&&started&&!over&&!(t%14))snd(196*[1,1.5,2,2.25,3,2,1.5,1.8][(t/14|0)%8],.16,.005,'triangle',1.005);if(!started)return;if(wait&&!--wait){let a=after;after=0;if(a==1)endTurn();else if(a==2)narAct();else if(a==3)narHit();else if(a==4)narNext();else if(a==5)aiAct();if(a==1&&net){if(isHost)wsend('K'+turn+','+ghash());else{mh=ghash();mhT=turn;if(khT==turn&&kh!=mh)wsend('Q')}}}if(msgT)msgT--;if(wpop)wpop--;if(charging){power=clamp(power+.018,0,1);if(aiMode&&power>=aiPw&&P[active].team==1){charging=0;fire()}}if((!net||myTeam==P[active].team)&&!phase&&!narTurn&&!over&&--clock<=0){phase=2;say('TIME!');delay(21,1);if(net)wsend('X'+turn+','+active+','+(P[active].x|0)+','+(P[active].y|0)+',0')}if((!phase||phase==4)&&!over){let p=P[active],mine=(!net||p&&myTeam==p.team)&&!(aiMode&&p.team==1);if(keys.KeyQ||keys.KeyE){let d=(keys.KeyE?9:0)-(keys.KeyQ?9:0);cam=cClamp(cam+d);freeCam=1}if(!inv&&!phase&&mine){let m=worldMouse();if(weapon==6||weapon==11)targetX=clamp(m[0],0,W);else{p.dir=m[0]>=p.x?1:-1;p.aim=clamp(Math.atan2(m[1]-(p.y-8),Math.abs(m[0]-p.x)||1),-1.5,1.2)}if(net&&t-lastM>4){lastM=t;wsend('M'+turn+','+active+','+(p.x|0)+','+(p.y|0)+','+p.dir+','+p.aim.toFixed(2)+','+clock)}}let d=(keys.KeyD?1:0)-(keys.KeyA?1:0);if(d&&!inv&&mine){p.dir=d;freeCam=0;if(p.on){let nx=clamp(p.x+d*1.35,5,W-5),gy=ground(nx,p.y)-13,dy=gy-p.y;if(dy<15&&dy>-11){p.x=nx;p.y=gy;p.dx=0}else if(dy>=15){p.on=0;p.dx=d*.8}}else p.dx=clamp(p.dx+d*.04,-1.6,1.6)}if(keys.Space&&p.on&&!inv&&mine){p.dy=-3.4;p.on=0;keys.Space=0;snd(360,.07,.018,'square')}if(mine)for(let i=crates.length-1;i>=0;i--){let b=crates[i];if(Math.hypot(p.x-b.x,p.y-b.y)<25){ammo[p.team][b.w]+=b.w==3?2:1;snd(700,.12,.025,'sine');say('FOUND '+WN[b.w]);crates.splice(i,1);if(net)wsend('C'+turn+','+active+','+i)}}}
for(let b of crates){let gy=ground(b.x)-10;if(b.y=0&&solid(nx,ny+13)){p.y=ground(nx,ny+13)-13;p.dy=0;p.on=1;p.dx*=.82}else if(solid(nx+p.dir*7,ny)){p.dx*=-.2}else{p.x=clamp(nx,5,W-5);p.y=ny}}if(p.y>water+35){kill(p,' LOST TO THE DEEP!',0);snd(115,.22,.04,'sine');splash(p.x);for(let i=0;i<18;i++)parts.push({x:p.x,y:wsurf(p.x),dx:R(-2.2,2.2),dy:R(-3.6,-.6),life:R(20,42),c:i&1?'#fff':'#51c8ff'});if(p===P[active]&&!phase){phase=2;delay(30,1)}}}
for(let i=proj.length-1;i>=0;i--){let q=proj[i];if(q.y>=0||!q.g)q.life--;if(q.home){let e=P.filter(p=>p.team!=q.team&&!p.dead).sort((a,b)=>Math.hypot(a.x-q.x,a.y-q.y)-Math.hypot(b.x-q.x,b.y-q.y))[0];if(e){let a=Math.atan2(e.y-q.y,e.x-q.x),v=Math.hypot(q.dx,q.dy);q.dx=q.dx*.96+Math.cos(a)*v*.04;q.dy=q.dy*.96+Math.sin(a)*v*.04}}if(q.mine){q.dy+=.12;q.y+=q.dy;if(solid(q.x,q.y+q.r)){q.y=ground(q.x,q.y)-q.r;q.dy=0}if(q.life<=0){hit(q);proj.splice(i,1)}continue}q.dy+=q.g;q.dx+=wind*(q.g>0);q.x+=q.dx;q.y+=q.dy;let hitP=P.find(p=>!p.dead&&Math.hypot(p.x-q.x,p.y-q.y)<12);if(hitP||solid(q.x,q.y)||q.y>water){if(q.b&&!hitP&&q.life>20&&q.y=water)splash(q.x);else hit(q);proj.splice(i,1)}}else if(q.life<=0)proj.splice(i,1)}if((phase==1||phase==4)&&!proj.length){phase=2;delay(27,1)}for(let i=parts.length-1;i>=0;i--){let a=parts[i];a.x+=a.dx||0;a.y+=a.dy||0;if(a.smoke){a.s+=.7;a.dy=(a.dy||0)*.93}else if(!a.n&&!a.ring&&!a.flash)a.dy=(a.dy||0)+.09;if(--a.life<=0)parts.splice(i,1)}if(narAnim)narAnim--;if(narAwake&&!narTurn){narX+=narDir*1.35;if(narX<120||narX>W-120)narDir*=-1}if(proj.length||narTurn||!freeCam){let f=phase==4?P[active]:proj[0]||P[active],focus=f?.x??W/2;cam+=(cClamp(focus-VW/2)-cam)*.06;camY+=(clamp((narTurn?water-45:f?.y??270)-VH*.55,0,H-VH)-camY)*.07}if(shake)shake*=.8}
@@ -153,6 +153,6 @@
// weapon icons now shared via wIcon()
// Chamfered "toy warfare" panel, reused by every authored UI surface.
function chip(px,py,w,h,fill,st,c=5){x.beginPath();x.moveTo(px+c,py);x.lineTo(px+w-c,py);x.lineTo(px+w,py+c);x.lineTo(px+w,py+h-c);x.lineTo(px+w-c,py+h);x.lineTo(px+c,py+h);x.lineTo(px,py+h-c);x.lineTo(px,py+c);x.closePath();if(fill){x.fillStyle=fill;x.fill()}if(st){x.strokeStyle=st;x.lineWidth=1.5;x.stroke()}}
-function draw(){x.clearRect(0,0,VW,VH);let zy=zY(),sh=shake?(Math.random()*2-1)*shake:0;x.save();x.translate(VW/2+sh,zy);x.scale(zoom,zoom);x.translate(-VW/2,-zy);x.translate(0,-camY);let g=x.createLinearGradient(0,0,0,VH);g.addColorStop(0,'#1b1250');g.addColorStop(.65,'#493986');g.addColorStop(1,'#f483a6');x.fillStyle=g;x.fillRect(-VW,0,VW*3,VH);for(let l=0;l<3;l++){x.strokeStyle=['#f6f3','#6ff3','#ffb3'][l];x.lineWidth=18-l*4;x.beginPath();for(let i=-VW;i<=VW*2;i+=40){let yy=105+l*55+Math.sin((i+cam*(.05+l*.03))/110+l)*18;i>-VW?x.lineTo(i,yy):x.moveTo(i,yy)}x.stroke()}x.fillStyle='#fff8';for(let i=0;i<22;i++){let sx=((i*181-cam*.08)%1100+1100)%1100-40,sy=65+(i*67)%215;x.fillRect(sx,sy,1+(i%3==0),1+(i%3==0))}let tg=x.createLinearGradient(0,300,0,H);tg.addColorStop(0,'#874dc2');tg.addColorStop(.2,'#4c286f');tg.addColorStop(1,'#21142f');x.fillStyle=tg;x.beginPath();x.moveTo(-cam,h[0]);for(let i=0;i-.25){pen?x.lineTo(i-cam,y):(x.moveTo(i-cam,y),pen=1)}else pen=0}x.stroke()}x.strokeStyle='#0d0720aa';x.lineWidth=1.5;for(let i=0;i<8;i++){let wx=(i*829+400)%W,cy=h[wx|0]+26+(i*i*53)%118;x.beginPath();x.moveTo(wx-cam,cy);x.lineTo(wx-cam+7+(i%3)*4,cy+12+(i%2)*7);x.stroke()}for(let i=0;i<15;i++){let wx=(i*617+i*i*29)%W,cy=h[wx|0]+30+(i*251)%150;let s=3+(i%3),sx2=wx-cam;x.save();x.translate(sx2,cy);x.rotate(i*1.7);x.fillStyle='#2b2050';x.beginPath();x.moveTo(0,-s*1.9);x.lineTo(s,0);x.lineTo(0,s*2.3);x.lineTo(-s,0);x.closePath();x.fill();x.globalAlpha=.3+.2*Math.sin(t*.06+i);x.strokeStyle=C[i%6];x.lineWidth=1;x.beginPath();x.moveTo(0,-s*1.9);x.lineTo(-s,0);x.stroke();x.globalAlpha=1;x.restore()}x.strokeStyle='#21142f';x.lineWidth=92;x.beginPath();for(let i=vx0;i<=vx1;i+=12)x.lineTo(i-cam,ch(i));x.stroke();x.fillStyle=g;for(let a of holes){x.beginPath();x.arc(a[0]-cam,a[1],a[2],0,PI*2);x.fill()}x.restore();if(tdirty){buildEdge();tdirty=0}x.lineJoin=x.lineCap='round';function edge(dy,w,st){x.strokeStyle=st;x.lineWidth=w;x.beginPath();for(let i=0;i-VW?x.lineTo(sx,y):x.moveTo(sx,y)}x.stroke();x.strokeStyle='#82ddffcc';x.lineWidth=2.2;x.beginPath();for(let sx=-VW;sx<=VW*2;sx+=12){let y=wsurf(sx+cam);sx>-VW?x.lineTo(sx,y):x.moveTo(sx,y)}x.stroke();x.fillStyle='#e6fbffcc';for(let sx=-VW;sx.83)x.fillRect(sx-3,wsurf(wx)-1,7,2)}if(narTurn&&narMode>=0){let tx=narTX-cam,gy=ground(narTX)-8;x.strokeStyle='#fff9';x.fillStyle='#9ef2';x.lineWidth=2;if(!narMode){for(let j=0;j<3;j++){x.beginPath();x.arc(tx,gy,12+j*10+Math.sin(t*.18)*4,0,PI*2);x.stroke()}}else if(narMode==1)x.fillRect(-VW,water-30,VW*3,30);else if(narMode==2){x.beginPath();for(let i=-VW;i-VW?x.lineTo(i,water-25+Math.sin(i*.05+t*.2)*8):x.moveTo(i,water-25);x.stroke()}else{x.beginPath();x.arc(tx,gy,24,0,PI*2);x.moveTo(tx-30,gy);x.lineTo(tx+30,gy);x.moveTo(tx,gy-30);x.lineTo(tx,gy+30);x.stroke()}}drawNar();for(let b of crates){let bx=b.x-cam;x.fillStyle='#ffe14a';x.fillRect(bx-9,b.y-9,18,18);x.strokeStyle='#fff';x.strokeRect(bx-9,b.y-9,18,18);x.fillStyle='#5b2e85';x.fillRect(bx-2,b.y-8,4,16);x.fillRect(bx-8,b.y-2,16,4)}for(let p of P)if(!p.dead)sprite(p);if(started&&!phase&&!narTurn&&!over&&!inv&&(!net||myTeam==P[active].team)){let p=P[active],sx=p.x-cam,ay=p.y-9,aa=p.aim;if(charging){let pw=power,gc=weapon?'#ffe1a6':C[p.type];x.globalAlpha=.18+pw*.5;x.strokeStyle=gc;x.lineWidth=1.5+pw*2;x.beginPath();x.arc(sx,p.y-8,13+pw*12,0,PI*2);x.stroke();x.globalAlpha=1}x.strokeStyle='#fff';x.lineWidth=2;if(weapon==6||weapon==11){let tx=targetX-cam,gy=ground(targetX);x.beginPath();x.arc(tx,gy-8,16+Math.sin(t*.15)*3,0,PI*2);x.moveTo(tx-22,gy-8);x.lineTo(tx+22,gy-8);x.moveTo(tx,gy-30);x.lineTo(tx,gy+14);x.stroke()}else if(weapon==10){x.beginPath();x.arc(sx,ay,75,p.dir>0?-PI/2:PI/2,p.dir>0?PI/2:PI*1.5);x.stroke()}else{let arc=weapon==1||weapon==2||weapon==5||weapon==8||(!weapon&&p.type<5&&p.type!=3);if(arc){let gg=weapon==2||weapon==5?.11:weapon?.08:.1,sp=.8+power*6,vx=Math.cos(aa)*sp*p.dir,vy=Math.sin(aa)*sp,px=p.x+p.dir*12,py=p.y-8;x.fillStyle=weapon?'#ffe1a6':C[p.type];for(let s=1;s<180;s++){vy+=gg;vx+=wind;px+=vx;py+=vy;if(px<5||px>W-5||py>water||solid(px,py))break;if(s%6==0){let f=clamp(1.05-s/188,.22,1);x.globalAlpha=f;x.beginPath();x.arc(px-cam,py,1.5+f*1.4,0,PI*2);x.fill()}}x.globalAlpha=1}else{let L=weapon==3||weapon==9?90:42,ex=sx+Math.cos(aa)*p.dir*L,ey=ay+Math.sin(aa)*L;x.beginPath();x.moveTo(sx+p.dir*12,ay);x.lineTo(ex,ey);x.stroke();x.beginPath();x.arc(ex,ey,5,0,PI*2);x.stroke()}}}for(let q of proj){let qx=q.x-cam;x.strokeStyle='hsl('+((q.x*.7+q.y+t*3)%360|0)+',95%,64%)';x.globalAlpha=.45;x.lineWidth=Math.max(1,q.r/2);x.beginPath();x.moveTo(qx-q.dx*3,q.y-q.dy*3);x.lineTo(qx,q.y);x.stroke();x.globalAlpha=1;x.fillStyle=q.c;if(q.mine){x.fillRect(qx-q.r,q.y-q.r,q.r*2,q.r*2);x.fillStyle=q.life%20<10?'#fff':'#f44';x.fillRect(qx-2,q.y-q.r-4,4,4)}else if(q.shot)x.fillRect(qx-7,q.y-1,14,2);else if(q.beam){x.strokeStyle='#fff9a8';x.lineWidth=5;x.beginPath();x.moveTo(qx-q.dx*7,q.y-q.dy*7);x.lineTo(qx+q.dx,q.y+q.dy);x.stroke()}else if(q.home){x.beginPath();x.arc(qx,q.y,q.r+5+Math.sin(t*.3)*2,0,PI*2);x.stroke();x.beginPath();x.arc(qx,q.y,q.r-2,0,PI*2);x.fill()}else if(q.rocket){x.save();x.translate(qx,q.y);x.rotate(Math.atan2(q.dy,q.dx));x.fillRect(-8,-3,14,6);x.fillStyle='#ff6';x.fillRect(-11,-2,4,4);x.restore()}else if(q.orb){x.beginPath();x.arc(qx,q.y,q.r+2,0,PI*2);x.stroke();x.beginPath();x.arc(qx,q.y,q.r,0,PI*2);x.fill()}else{x.beginPath();x.arc(qx,q.y,q.r,0,PI*2);x.fill();if(q.rain||q.star){x.strokeStyle=q.rain?'#f8f':'#fff4a0';x.lineWidth=3;x.beginPath();x.moveTo(qx,q.y-18);x.lineTo(qx,q.y+10);x.stroke()}if(q.burst){x.strokeStyle='#fff';x.beginPath();x.arc(qx,q.y,q.r+5,0,PI*2);x.stroke()}}}for(let a of parts){x.globalAlpha=Math.min(1,a.life/25);x.fillStyle=x.strokeStyle=a.c;if(a.n){x.font='bold '+(a.big?15:12)+'px monospace';x.textAlign='center';x.lineJoin='round';x.lineWidth=3.5;x.strokeStyle='#120a1e';x.strokeText(a.n,a.x-cam,a.y);x.fillStyle=a.c;x.fillText(a.n,a.x-cam,a.y);x.textAlign='left'}else if(a.flash){x.globalAlpha=a.life/11;x.fillStyle='#fff';x.beginPath();x.arc(a.x-cam,a.y,a.flash*(.35+(1-a.life/11)*.55),0,PI*2);x.fill()}else if(a.smoke){x.globalAlpha=Math.min(.4,a.life/140);x.beginPath();x.arc(a.x-cam,a.y,a.s,0,PI*2);x.fill()}else if(a.ring){let pr=1-a.life/(a.rl||20);if(pr>0){x.lineWidth=a.ring>40?5:3;x.beginPath();x.arc(a.x-cam,a.y,a.ring*pr,0,PI*2);x.stroke()}}else{x.beginPath();x.arc(a.x-cam,a.y,a.s||3,0,PI*2);x.fill()}}x.globalAlpha=1;x.restore();if(started&&!over){let p=P[active];chip(10,10,150,32,'#150e2ce6','#4a3a7a');x.font='bold 12px monospace';x.textAlign='left';x.fillStyle='#b9a6ff';x.fillText('R'+round,20,30);x.fillStyle='#efe9ff';x.fillText(Math.ceil(clock/60)+'s',50,30);x.fillStyle='#8af29a';x.fillRect(21,35,128*clamp(clock/1800,0,1),2);let wv=Math.abs(wind*100|0);x.save();x.translate(104,25);let wd=wind>0?1:-1;x.fillStyle='#78dcff';x.beginPath();x.moveTo(-6*wd,-4);x.lineTo(7*wd,0);x.lineTo(-6*wd,4);x.closePath();x.fill();x.restore();x.fillStyle='#9fd0ff';x.fillText(wv,116,30);if(narAwake){let nw=196,n0=VW-nw-10,gs=narStun,gc=gs?'#8fb0d0':'#4fd0ff';chip(n0,10,nw,42,'#0d1a2ee6',gs?'#7fa0c0':'#3f8fce');x.strokeStyle=gc;x.lineWidth=2.4;x.beginPath();x.moveTo(n0+12,36);x.lineTo(n0+21,15);x.lineTo(n0+25,36);x.stroke();x.font='bold 11px monospace';x.fillStyle=gs?'#b6c6d8':'#cdeeff';x.fillText(gs?'STUNNED '+gs:'SEA UNICORN',n0+32,24);x.fillStyle='#081726';x.fillRect(n0+32,29,150,8);x.fillStyle=gc;x.fillRect(n0+33,30,148*(gs?gs/2:clamp(narHP/260,0,1)),6);if(water<700){x.font='10px monospace';x.fillStyle='#8fd6ff';x.fillText('TIDE '+(700-water)+'↑',n0+134,24)}}}if(started&&!over&&!narTurn&&phase!=3&&(wpop>0||charging)){let p=P[active],st=ammo[p.team],fp=weapon==4&&proj.find(q=>q.mine),nm=weapon?WN[weapon]:SN[p.type],am=weapon?'×'+st[weapon]:'∞',sky=weapon==6||weapon==11,pw=182,px=14,py=VH-42;x.globalAlpha=charging?1:Math.min(1,wpop/28);chip(px,py,pw,32,'#100a24ee',weapon?'#4a3a7a':C[p.type]);x.save();x.translate(px+18,py+16);x.scale(1.1,1.1);wIcon(weapon,p.type,weapon?'#f0e4c6':C[p.type]);x.restore();x.textAlign='left';x.font='bold 11px monospace';x.fillStyle='#fff';x.fillText(nm,px+34,py+14);if(charging){x.fillStyle='#0a0718';x.fillRect(px+34,py+20,pw-44,6);x.fillStyle=C[p.type];x.fillRect(px+35,py+21,(pw-46)*power,4)}else if(fp){x.fillStyle='#0a0718';x.fillRect(px+34,py+20,pw-44,6);x.fillStyle=fp.life%20<10?'#fff':'#f44';x.fillRect(px+35,py+21,(pw-46)*fp.life/180,4)}else{x.font='10px monospace';x.fillStyle=sky?'#ffd34a':'#b9a6ff';x.fillText(weapon==11?'TARGET · BLINK':weapon==3?'AIM · ROPE':sky?'TARGET · LMB FIRE':weapon==10?'RANGE 75 · SHOVE':am,px+34,py+26)}x.globalAlpha=1}if(inv&&started&&!over){let p=P[active],st=ammo[p.team],ox=VW-250,oy=95;chip(ox-15,oy-32,238,201,'#120c28f4',TC[p.team]);x.textAlign='left';x.font='bold 12px monospace';x.fillStyle='#cdbcff';x.fillText('ARMORY CASE',ox-4,oy-14);x.font='9px monospace';x.fillStyle='#6a6488';x.fillText('RMB CLOSE',ox+134,oy-14);for(let i=0;i<12;i++){let bx=ox+(i%4)*56,by=oy+(i/4|0)*56,sel=i==weapon,ok=!i||st[i],sig=!i;chip(bx,by,50,50,sel?'#2a2150':'#151230e6',sel?TC[p.team]:sig?C[p.type]:'#ffffff2b',6);if(sig){x.fillStyle=C[p.type];x.beginPath();for(let a=0;a<10;a++){let q=a*PI/5-1.57,r=a&1?1.7:4,X=bx+9+Math.cos(q)*r,Y=by+9+Math.sin(q)*r;a?x.lineTo(X,Y):x.moveTo(X,Y)}x.closePath();x.fill()}x.save();x.translate(bx+25,by+23);wIcon(i,p.type,sel?'#fff':ok?'#e9e4ff':'#5a5a72');x.restore();x.fillStyle=sel?'#fff':ok?'#cdbcff':'#5a5a72';x.font='8px monospace';x.textAlign='right';x.fillText(i?st[i]:'∞',bx+45,by+13);x.textAlign='center';x.fillText((i?WN[i]:SN[p.type]).slice(0,7),bx+25,by+45)}x.textAlign='left'}if(msgT){x.font='bold 20px monospace';let mw=x.measureText(msg).width;x.fillStyle='#000b';x.fillRect((VW-mw)/2-14,75,mw+28,34);x.fillStyle='#fff';x.fillText(msg,(VW-mw)/2,99)}if(!started){let poses=[2,2,7,4,5,7],aims=[-.5,.25,-1.05,-.35,-.8,-1.05],ys=[494,502,486,500,472,490],dirs=[1,1,1,-1,-1,-1];for(let i=0;i<6;i++)sprite({x:cam+300+i*72,y:ys[i],dx:0,dy:0,hp:100,team:i&1,type:i,dir:dirs[i],aim:aims[i],on:1,pose:poses[i]});let fxn=712,wy=wsurf(fxn+cam);x.save();x.translate(fxn,wy);x.strokeStyle='#bfe9ff55';x.lineWidth=2;x.beginPath();x.ellipse(4,4,74,11,0,0,PI*2);x.stroke();x.fillStyle='#6fcadf';x.beginPath();x.moveTo(-36,2);x.lineTo(-21,-27);x.lineTo(-9,3);x.closePath();x.fill();x.strokeStyle='#fff2b0';x.lineWidth=4;x.lineCap='round';x.beginPath();x.moveTo(26,4);x.lineTo(56,-48);x.stroke();x.lineCap='butt';x.fillStyle='#e6fbffcc';for(let k=0;k<4;k++)x.fillRect(22+k*4,-6-k*3,2,2);x.restore();x.textAlign='center';x.font='bold 46px monospace';x.fillStyle='#05030f';x.fillText('SPECTRUM SPRITES',483,161);x.fillStyle='#fff';x.fillText('SPECTRUM SPRITES',480,158);let lw=x.measureText('SPECTRUM SPRITES').width,lg=x.createLinearGradient(480-lw/2,0,480+lw/2,0);for(let i=0;i<6;i++)lg.addColorStop(i/5,C[i]);x.strokeStyle=lg;x.lineWidth=3;x.beginPath();x.moveTo(480-lw/2,170);x.lineTo(480+lw/2,170);x.stroke();x.fillStyle='#f5d94a';x.beginPath();for(let a=0;a<8;a++){let q=a*PI/4,r=a&1?3:8;a?x.lineTo(480-lw/2-22+Math.cos(q)*r,150+Math.sin(q)*r):x.moveTo(480-lw/2-22+Math.cos(q)*r,150+Math.sin(q)*r)}x.closePath();x.fill();x.font='14px monospace';x.fillStyle='#c9b8e8';x.fillText('PROCEDURAL ARTILLERY IN A LIVING RAINBOW SEA',480,193);if(netUI){chip(300,210,360,124,'#150e2cf6',TC[0]);x.textAlign='center';if(netUI=='host'){x.font='14px monospace';x.fillStyle='#b9a6ff';x.fillText(qm?'QUICK MATCH':'ROOM CODE',480,240);if(qm){x.font='bold 22px monospace';x.fillStyle='#fff';x.fillText('SEARCHING FOR OPPONENT…',480,284)}else{x.font='bold 40px monospace';x.fillStyle='#fff';x.fillText(room,480,284);x.font='13px monospace';x.fillStyle='#8fd6ff';x.fillText('WAITING FOR PLAYER…',480,312)}x.font='11px monospace';x.fillStyle='#6a6488';x.fillText('ESC CANCEL',480,328)}else if(netUI=='join'){x.font='14px monospace';x.fillStyle='#b9a6ff';x.fillText('ENTER ROOM CODE',480,240);for(let i=0;i<4;i++){let bx=388+i*48;chip(bx,256,40,42,'#0e0a20',joinBuf.length==i?TC[0]:'#38325a');x.font='bold 24px monospace';x.fillStyle='#fff';x.fillText(joinBuf[i]||'',bx+20,287)}x.font='11px monospace';x.fillStyle='#6a6488';x.fillText('ENTER CONNECT · ESC CANCEL',480,322)}else{let mm=netUI=='conn'?'CONNECTING…':netUI=='wait'?'WAITING FOR HOST…':netUI=='qwait'?'QUICK MATCH…':netUI=='full'?'ROOM FULL':'CONNECTION FAILED';x.font='bold 22px monospace';x.fillStyle='#fff';x.fillText(mm,480,274);if(netUI=='fail'||netUI=='full'){x.font='bold 13px monospace';x.fillStyle='#ffd34a';x.fillText('ENTER — BACK',480,304)}else{x.font='11px monospace';x.fillStyle='#6a6488';x.fillText('ESC CANCEL',480,304)}}x.textAlign='left'}else{let items=['LOCAL BATTLE','VS COMPUTER','QUICK MATCH','ONLINE HOST','ONLINE JOIN'];x.textAlign='center';for(let i=0;i<5;i++){let mw=214,mx=480-mw/2,my=210+i*36,on=i==msel;chip(mx,my,mw,34,on?'#241b46ee':'#130e26bb',on?TC[0]:'#38325a');x.font='bold 15px monospace';x.fillStyle=on?'#fff':'#8a84a8';x.fillText(items[i],480,my+22)}x.font='bold 13px monospace';x.fillStyle='#ffd34a';x.fillText('▶ ENTER / CLICK',480,404);x.textAlign='left'}x.font='12px monospace';x.textAlign='center';x.fillStyle='#8a84a8';x.fillText('A/D MOVE · SPACE JUMP · MOUSE AIM · RMB WEAPONS · WHEEL ZOOM · M MUSIC',480,527);x.textAlign='left'}if(over){let win=over<3;if(win){let wt=over-1;for(let i=0;i<3;i++)sprite({x:cam+390+i*90,y:452,dx:0,dy:0,hp:100,team:wt,type:wt?[1,3,5][i]:[0,2,4][i],dir:i-1||1,aim:-1.1,on:1,pose:7})}chip(300,150,360,104,'#140d2af4',win?TC[over-1]:'#8a3f5e');x.textAlign='center';x.font='bold 30px monospace';let tt=win?'TEAM '+(over==1?'A':'B')+' WINS':'NO SURVIVORS';x.fillStyle='#05030f';x.fillText(tt,482,200);x.fillStyle='#fff';x.fillText(tt,480,198);let uw=x.measureText(tt).width,ug=x.createLinearGradient(480-uw/2,0,480+uw/2,0);for(let i=0;i<6;i++)ug.addColorStop(i/5,C[i]);x.strokeStyle=ug;x.lineWidth=2.5;x.beginPath();x.moveTo(480-uw/2,210);x.lineTo(480+uw/2,210);x.stroke();x.font='13px monospace';x.fillStyle='#b9a6ff';x.fillText(win?'the spectrum holds':'mutual oblivion',480,230);x.font='bold 13px monospace';x.fillStyle='#ffd34a';x.fillText('▶ ENTER · REMATCH',480,248);x.textAlign='left'}if(net&&started){x.fillStyle=netUI=='left'?'#ff5a7a':'#4fe0ff';x.beginPath();x.arc(VW-15,VH-14,4,0,PI*2);x.fill();if(netUI=='left'){chip(300,214,360,96,'#1a0d1ff6','#ff5a7a');x.textAlign='center';x.font='bold 24px monospace';x.fillStyle='#fff';x.fillText('OPPONENT LEFT',480,260);x.font='bold 13px monospace';x.fillStyle='#ffd34a';x.fillText('ENTER — CONTINUE LOCAL',480,292);x.textAlign='left'}}}
+function draw(){x.clearRect(0,0,VW,VH);let zy=zY(),sh=shake?(Math.random()*2-1)*shake:0;x.save();x.translate(VW/2+sh,zy);x.scale(zoom,zoom);x.translate(-VW/2,-zy);x.translate(0,-camY);let g=x.createLinearGradient(0,0,0,VH);g.addColorStop(0,'#1b1250');g.addColorStop(.65,'#493986');g.addColorStop(1,'#f483a6');x.fillStyle=g;x.fillRect(-VW,0,VW*3,VH);for(let l=0;l<3;l++){x.strokeStyle=['#f6f3','#6ff3','#ffb3'][l];x.lineWidth=18-l*4;x.beginPath();for(let i=-VW;i<=VW*2;i+=40){let yy=105+l*55+Math.sin((i+cam*(.05+l*.03))/110+l)*18;i>-VW?x.lineTo(i,yy):x.moveTo(i,yy)}x.stroke()}x.fillStyle='#fff8';for(let i=0;i<22;i++){let sx=((i*181-cam*.08)%1100+1100)%1100-40,sy=65+(i*67)%215;x.fillRect(sx,sy,1+(i%3==0),1+(i%3==0))}let tg=x.createLinearGradient(0,300,0,H);tg.addColorStop(0,'#874dc2');tg.addColorStop(.2,'#4c286f');tg.addColorStop(1,'#21142f');x.fillStyle=tg;x.beginPath();x.moveTo(-cam,h[0]);for(let i=0;i-.25){pen?x.lineTo(i-cam,y):(x.moveTo(i-cam,y),pen=1)}else pen=0}x.stroke()}x.strokeStyle='#0d0720aa';x.lineWidth=1.5;for(let i=0;i<8;i++){let wx=(i*829+400)%W,cy=h[wx|0]+26+(i*i*53)%118;x.beginPath();x.moveTo(wx-cam,cy);x.lineTo(wx-cam+7+(i%3)*4,cy+12+(i%2)*7);x.stroke()}for(let i=0;i<15;i++){let wx=(i*617+i*i*29)%W,cy=h[wx|0]+30+(i*251)%150;let s=3+(i%3),sx2=wx-cam;x.save();x.translate(sx2,cy);x.rotate(i*1.7);x.fillStyle='#2b2050';x.beginPath();x.moveTo(0,-s*1.9);x.lineTo(s,0);x.lineTo(0,s*2.3);x.lineTo(-s,0);x.closePath();x.fill();x.globalAlpha=.3+.2*Math.sin(t*.06+i);x.strokeStyle=C[i%6];x.lineWidth=1;x.beginPath();x.moveTo(0,-s*1.9);x.lineTo(-s,0);x.stroke();x.globalAlpha=1;x.restore()}x.strokeStyle='#21142f';x.lineWidth=92;x.beginPath();for(let i=vx0;i<=vx1;i+=12)x.lineTo(i-cam,ch(i));x.stroke();x.fillStyle=g;for(let a of holes){x.beginPath();x.arc(a[0]-cam,a[1],a[2],0,PI*2);x.fill()}x.restore();if(tdirty){buildEdge();tdirty=0}x.lineJoin=x.lineCap='round';function edge(dy,w,st){x.strokeStyle=st;x.lineWidth=w;x.beginPath();for(let i=0;i-VW?x.lineTo(sx,y):x.moveTo(sx,y)}x.stroke();x.strokeStyle='#82ddffcc';x.lineWidth=2.2;x.beginPath();for(let sx=-VW;sx<=VW*2;sx+=12){let y=wsurf(sx+cam);sx>-VW?x.lineTo(sx,y):x.moveTo(sx,y)}x.stroke();x.fillStyle='#e6fbffcc';for(let sx=-VW;sx.83)x.fillRect(sx-3,wsurf(wx)-1,7,2)}if(narTurn&&narMode>=0){let tx=narTX-cam,gy=ground(narTX)-8;x.strokeStyle='#fff9';x.fillStyle='#9ef2';x.lineWidth=2;if(!narMode){for(let j=0;j<3;j++){x.beginPath();x.arc(tx,gy,12+j*10+Math.sin(t*.18)*4,0,PI*2);x.stroke()}}else if(narMode==1)x.fillRect(-VW,water-30,VW*3,30);else if(narMode==2){x.beginPath();for(let i=-VW;i-VW?x.lineTo(i,water-25+Math.sin(i*.05+t*.2)*8):x.moveTo(i,water-25);x.stroke()}else{x.beginPath();x.arc(tx,gy,24,0,PI*2);x.moveTo(tx-30,gy);x.lineTo(tx+30,gy);x.moveTo(tx,gy-30);x.lineTo(tx,gy+30);x.stroke()}}drawNar();for(let b of crates){let bx=b.x-cam;x.fillStyle='#ffe14a';x.fillRect(bx-9,b.y-9,18,18);x.strokeStyle='#fff';x.strokeRect(bx-9,b.y-9,18,18);x.fillStyle='#5b2e85';x.fillRect(bx-2,b.y-8,4,16);x.fillRect(bx-8,b.y-2,16,4)}for(let p of P)if(!p.dead)sprite(p);if(started&&!phase&&!narTurn&&!over&&!inv&&(!net||myTeam==P[active].team)){let p=P[active],sx=p.x-cam,ay=p.y-9,aa=p.aim;if(charging){let pw=power,gc=weapon?'#ffe1a6':C[p.type];x.globalAlpha=.18+pw*.5;x.strokeStyle=gc;x.lineWidth=1.5+pw*2;x.beginPath();x.arc(sx,p.y-8,13+pw*12,0,PI*2);x.stroke();x.globalAlpha=1}x.strokeStyle='#fff';x.lineWidth=2;if(weapon==6||weapon==11){let tx=targetX-cam,gy=ground(targetX);x.beginPath();x.arc(tx,gy-8,16+Math.sin(t*.15)*3,0,PI*2);x.moveTo(tx-22,gy-8);x.lineTo(tx+22,gy-8);x.moveTo(tx,gy-30);x.lineTo(tx,gy+14);x.stroke()}else if(weapon==10){x.beginPath();x.arc(sx,ay,75,p.dir>0?-PI/2:PI/2,p.dir>0?PI/2:PI*1.5);x.stroke()}else{let arc=weapon==1||weapon==2||weapon==5||weapon==8||(!weapon&&p.type<5&&p.type!=3);if(arc){let gg=weapon==2||weapon==5?.11:weapon?.08:.1,sp=.8+power*6,vx=Math.cos(aa)*sp*p.dir,vy=Math.sin(aa)*sp,px=p.x+p.dir*12,py=p.y-8;x.fillStyle=weapon?'#ffe1a6':C[p.type];for(let s=1;s<180;s++){vy+=gg;vx+=wind;px+=vx;py+=vy;if(px<5||px>W-5||py>water||solid(px,py))break;if(s%6==0){let f=clamp(1.05-s/188,.22,1);x.globalAlpha=f;x.beginPath();x.arc(px-cam,py,1.5+f*1.4,0,PI*2);x.fill()}}x.globalAlpha=1}else{let L=weapon==3||weapon==9?90:42,ex=sx+Math.cos(aa)*p.dir*L,ey=ay+Math.sin(aa)*L;x.beginPath();x.moveTo(sx+p.dir*12,ay);x.lineTo(ex,ey);x.stroke();x.beginPath();x.arc(ex,ey,5,0,PI*2);x.stroke()}}}for(let q of proj){let qx=q.x-cam;x.strokeStyle='hsl('+((q.x*.7+q.y+t*3)%360|0)+',95%,64%)';x.globalAlpha=.45;x.lineWidth=Math.max(1,q.r/2);x.beginPath();x.moveTo(qx-q.dx*3,q.y-q.dy*3);x.lineTo(qx,q.y);x.stroke();x.globalAlpha=1;x.fillStyle=q.c;if(q.mine){x.fillRect(qx-q.r,q.y-q.r,q.r*2,q.r*2);x.fillStyle=q.life%20<10?'#fff':'#f44';x.fillRect(qx-2,q.y-q.r-4,4,4)}else if(q.shot)x.fillRect(qx-7,q.y-1,14,2);else if(q.beam){x.strokeStyle='#fff9a8';x.lineWidth=5;x.beginPath();x.moveTo(qx-q.dx*7,q.y-q.dy*7);x.lineTo(qx+q.dx,q.y+q.dy);x.stroke()}else if(q.home){x.beginPath();x.arc(qx,q.y,q.r+5+Math.sin(t*.3)*2,0,PI*2);x.stroke();x.beginPath();x.arc(qx,q.y,q.r-2,0,PI*2);x.fill()}else if(q.rocket){x.save();x.translate(qx,q.y);x.rotate(Math.atan2(q.dy,q.dx));x.fillRect(-8,-3,14,6);x.fillStyle='#ff6';x.fillRect(-11,-2,4,4);x.restore()}else if(q.orb){x.beginPath();x.arc(qx,q.y,q.r+2,0,PI*2);x.stroke();x.beginPath();x.arc(qx,q.y,q.r,0,PI*2);x.fill()}else{x.beginPath();x.arc(qx,q.y,q.r,0,PI*2);x.fill();if(q.rain||q.star){x.strokeStyle=q.rain?'#f8f':'#fff4a0';x.lineWidth=3;x.beginPath();x.moveTo(qx,q.y-18);x.lineTo(qx,q.y+10);x.stroke()}if(q.burst){x.strokeStyle='#fff';x.beginPath();x.arc(qx,q.y,q.r+5,0,PI*2);x.stroke()}}}for(let a of parts){x.globalAlpha=Math.min(1,a.life/25);x.fillStyle=x.strokeStyle=a.c;if(a.n){x.font='bold '+(a.big?15:12)+'px monospace';x.textAlign='center';x.lineJoin='round';x.lineWidth=3.5;x.strokeStyle='#120a1e';x.strokeText(a.n,a.x-cam,a.y);x.fillStyle=a.c;x.fillText(a.n,a.x-cam,a.y);x.textAlign='left'}else if(a.flash){x.globalAlpha=a.life/11;x.fillStyle='#fff';x.beginPath();x.arc(a.x-cam,a.y,a.flash*(.35+(1-a.life/11)*.55),0,PI*2);x.fill()}else if(a.smoke){x.globalAlpha=Math.min(.4,a.life/140);x.beginPath();x.arc(a.x-cam,a.y,a.s,0,PI*2);x.fill()}else if(a.ring){let pr=1-a.life/(a.rl||20);if(pr>0){x.lineWidth=a.ring>40?5:3;x.beginPath();x.arc(a.x-cam,a.y,a.ring*pr,0,PI*2);x.stroke()}}else{x.beginPath();x.arc(a.x-cam,a.y,a.s||3,0,PI*2);x.fill()}}x.globalAlpha=1;x.restore();if(started&&!over){let p=P[active];chip(10,10,150,32,'#150e2ce6','#4a3a7a');x.font='bold 12px monospace';x.textAlign='left';x.fillStyle='#b9a6ff';x.fillText('R'+round,20,30);x.fillStyle='#efe9ff';x.fillText(Math.ceil(clock/60)+'s',50,30);x.fillStyle='#8af29a';x.fillRect(21,35,128*clamp(clock/1800,0,1),2);let wv=Math.abs(wind*100|0);x.save();x.translate(104,25);let wd=wind>0?1:-1;x.fillStyle='#78dcff';x.beginPath();x.moveTo(-6*wd,-4);x.lineTo(7*wd,0);x.lineTo(-6*wd,4);x.closePath();x.fill();x.restore();x.fillStyle='#9fd0ff';x.fillText(wv,116,30);if(narAwake){let nw=196,n0=VW-nw-10,gs=narStun,gc=gs?'#8fb0d0':'#4fd0ff';chip(n0,10,nw,42,'#0d1a2ee6',gs?'#7fa0c0':'#3f8fce');x.strokeStyle=gc;x.lineWidth=2.4;x.beginPath();x.moveTo(n0+12,36);x.lineTo(n0+21,15);x.lineTo(n0+25,36);x.stroke();x.font='bold 11px monospace';x.fillStyle=gs?'#b6c6d8':'#cdeeff';x.fillText(gs?'STUNNED '+gs:'SEA UNICORN',n0+32,24);x.fillStyle='#081726';x.fillRect(n0+32,29,150,8);x.fillStyle=gc;x.fillRect(n0+33,30,148*(gs?gs/2:clamp(narHP/260,0,1)),6);if(water<700){x.font='10px monospace';x.fillStyle='#8fd6ff';x.fillText('TIDE '+(700-water)+'↑',n0+134,24)}}}if(started&&!over&&!narTurn&&phase!=3&&(wpop>0||charging)){let p=P[active],st=ammo[p.team],fp=weapon==4&&proj.find(q=>q.mine),nm=weapon?WN[weapon]:SN[p.type],am=weapon?'×'+st[weapon]:'∞',sky=weapon==6||weapon==11,pw=182,px=14,py=VH-42;x.globalAlpha=charging?1:Math.min(1,wpop/28);chip(px,py,pw,32,'#100a24ee',weapon?'#4a3a7a':C[p.type]);x.save();x.translate(px+18,py+16);x.scale(1.1,1.1);wIcon(weapon,p.type,weapon?'#f0e4c6':C[p.type]);x.restore();x.textAlign='left';x.font='bold 11px monospace';x.fillStyle='#fff';x.fillText(nm,px+34,py+14);if(charging){x.fillStyle='#0a0718';x.fillRect(px+34,py+20,pw-44,6);x.fillStyle=C[p.type];x.fillRect(px+35,py+21,(pw-46)*power,4)}else if(fp){x.fillStyle='#0a0718';x.fillRect(px+34,py+20,pw-44,6);x.fillStyle=fp.life%20<10?'#fff':'#f44';x.fillRect(px+35,py+21,(pw-46)*fp.life/180,4)}else{x.font='10px monospace';x.fillStyle=sky?'#ffd34a':'#b9a6ff';x.fillText(weapon==11?'TARGET · BLINK':weapon==3?'AIM · ROPE':sky?'TARGET · LMB FIRE':weapon==10?'RANGE 75 · SHOVE':am,px+34,py+26)}x.globalAlpha=1}if(inv&&started&&!over){let p=P[active],st=ammo[p.team],ox=VW-250,oy=95;chip(ox-15,oy-32,238,201,'#120c28f4',TC[p.team]);x.textAlign='left';x.font='bold 12px monospace';x.fillStyle='#cdbcff';x.fillText('ARMORY CASE',ox-4,oy-14);x.font='9px monospace';x.fillStyle='#6a6488';x.fillText('RMB CLOSE',ox+134,oy-14);for(let i=0;i<12;i++){let bx=ox+(i%4)*56,by=oy+(i/4|0)*56,sel=i==weapon,ok=!i||st[i],sig=!i;chip(bx,by,50,50,sel?'#2a2150':'#151230e6',sel?TC[p.team]:sig?C[p.type]:'#ffffff2b',6);if(sig){x.fillStyle=C[p.type];x.beginPath();for(let a=0;a<10;a++){let q=a*PI/5-1.57,r=a&1?1.7:4,X=bx+9+Math.cos(q)*r,Y=by+9+Math.sin(q)*r;a?x.lineTo(X,Y):x.moveTo(X,Y)}x.closePath();x.fill()}x.save();x.translate(bx+25,by+23);wIcon(i,p.type,sel?'#fff':ok?'#e9e4ff':'#5a5a72');x.restore();x.fillStyle=sel?'#fff':ok?'#cdbcff':'#5a5a72';x.font='8px monospace';x.textAlign='right';x.fillText(i?st[i]:'∞',bx+45,by+13);x.textAlign='center';x.fillText((i?WN[i]:SN[p.type]).slice(0,7),bx+25,by+45)}x.textAlign='left'}if(msgT){x.font='bold 20px monospace';let mw=x.measureText(msg).width;x.fillStyle='#000b';x.fillRect((VW-mw)/2-14,75,mw+28,34);x.fillStyle='#fff';x.fillText(msg,(VW-mw)/2,99)}if(!started){let poses=[2,2,7,4,5,7],aims=[-.5,.25,-1.05,-.35,-.8,-1.05],ys=[494,502,486,500,472,490],dirs=[1,1,1,-1,-1,-1];for(let i=0;i<6;i++)sprite({x:cam+300+i*72,y:ys[i],dx:0,dy:0,hp:100,team:i&1,type:i,dir:dirs[i],aim:aims[i],on:1,pose:poses[i]});let fxn=712,wy=wsurf(fxn+cam);x.save();x.translate(fxn,wy);x.strokeStyle='#bfe9ff55';x.lineWidth=2;x.beginPath();x.ellipse(4,4,74,11,0,0,PI*2);x.stroke();x.fillStyle='#6fcadf';x.beginPath();x.moveTo(-36,2);x.lineTo(-21,-27);x.lineTo(-9,3);x.closePath();x.fill();x.strokeStyle='#fff2b0';x.lineWidth=4;x.lineCap='round';x.beginPath();x.moveTo(26,4);x.lineTo(56,-48);x.stroke();x.lineCap='butt';x.fillStyle='#e6fbffcc';for(let k=0;k<4;k++)x.fillRect(22+k*4,-6-k*3,2,2);x.restore();x.textAlign='center';x.font='bold 46px monospace';x.fillStyle='#05030f';x.fillText('SPECTRUM SPRITES',483,161);x.fillStyle='#fff';x.fillText('SPECTRUM SPRITES',480,158);let lw=x.measureText('SPECTRUM SPRITES').width,lg=x.createLinearGradient(480-lw/2,0,480+lw/2,0);for(let i=0;i<6;i++)lg.addColorStop(i/5,C[i]);x.strokeStyle=lg;x.lineWidth=3;x.beginPath();x.moveTo(480-lw/2,170);x.lineTo(480+lw/2,170);x.stroke();x.fillStyle='#f5d94a';x.beginPath();for(let a=0;a<8;a++){let q=a*PI/4,r=a&1?3:8;a?x.lineTo(480-lw/2-22+Math.cos(q)*r,150+Math.sin(q)*r):x.moveTo(480-lw/2-22+Math.cos(q)*r,150+Math.sin(q)*r)}x.closePath();x.fill();x.font='14px monospace';x.fillStyle='#c9b8e8';x.fillText('PROCEDURAL ARTILLERY IN A LIVING RAINBOW SEA',480,193);if(netUI){chip(300,210,360,124,'#150e2cf6',TC[0]);x.textAlign='center';if(netUI=='host'){x.font='14px monospace';x.fillStyle='#b9a6ff';x.fillText(qm?'QUICK MATCH':'ROOM CODE',480,240);if(qm){x.font='bold 22px monospace';x.fillStyle='#fff';x.fillText('SEARCHING FOR OPPONENT…',480,284)}else{x.font='bold 40px monospace';x.fillStyle='#fff';x.fillText(room,480,284);x.font='13px monospace';x.fillStyle='#8fd6ff';x.fillText('WAITING FOR PLAYER…',480,312)}x.font='11px monospace';x.fillStyle='#6a6488';x.fillText('ESC CANCEL',480,328)}else if(netUI=='join'){x.font='14px monospace';x.fillStyle='#b9a6ff';x.fillText('ENTER ROOM CODE',480,240);for(let i=0;i<4;i++){let bx=388+i*48;chip(bx,256,40,42,'#0e0a20',joinBuf.length==i?TC[0]:'#38325a');x.font='bold 24px monospace';x.fillStyle='#fff';x.fillText(joinBuf[i]||'',bx+20,287)}x.font='11px monospace';x.fillStyle='#6a6488';x.fillText('ENTER CONNECT · ESC CANCEL',480,322)}else{let mm=netUI=='conn'?'CONNECTING…':netUI=='wait'?'WAITING FOR HOST…':netUI=='qwait'?'QUICK MATCH…':netUI=='full'?'ROOM FULL':'CONNECTION FAILED';x.font='bold 22px monospace';x.fillStyle='#fff';x.fillText(mm,480,274);if(netUI=='fail'||netUI=='full'){x.font='bold 13px monospace';x.fillStyle='#ffd34a';x.fillText('ENTER — BACK',480,304)}else{x.font='11px monospace';x.fillStyle='#6a6488';x.fillText('ESC CANCEL',480,304)}}x.textAlign='left'}else{let items=['LOCAL BATTLE','VS COMPUTER'];x.textAlign='center';for(let i=0;i<2;i++){let mw=214,mx=480-mw/2,my=210+i*36,on=i==msel;chip(mx,my,mw,34,on?'#241b46ee':'#130e26bb',on?TC[0]:'#38325a');x.font='bold 15px monospace';x.fillStyle=on?'#fff':'#8a84a8';x.fillText(items[i],480,my+22)}x.font='bold 13px monospace';x.fillStyle='#ffd34a';x.fillText('▶ ENTER / CLICK',480,404);x.textAlign='left'}x.font='12px monospace';x.textAlign='center';x.fillStyle='#8a84a8';x.fillText('A/D MOVE · SPACE JUMP · MOUSE AIM · RMB WEAPONS · WHEEL ZOOM · M MUSIC',480,527);x.textAlign='left'}if(over){let win=over<3;if(win){let wt=over-1;for(let i=0;i<3;i++)sprite({x:cam+390+i*90,y:452,dx:0,dy:0,hp:100,team:wt,type:wt?[1,3,5][i]:[0,2,4][i],dir:i-1||1,aim:-1.1,on:1,pose:7})}chip(300,150,360,104,'#140d2af4',win?TC[over-1]:'#8a3f5e');x.textAlign='center';x.font='bold 30px monospace';let tt=win?'TEAM '+(over==1?'A':'B')+' WINS':'NO SURVIVORS';x.fillStyle='#05030f';x.fillText(tt,482,200);x.fillStyle='#fff';x.fillText(tt,480,198);let uw=x.measureText(tt).width,ug=x.createLinearGradient(480-uw/2,0,480+uw/2,0);for(let i=0;i<6;i++)ug.addColorStop(i/5,C[i]);x.strokeStyle=ug;x.lineWidth=2.5;x.beginPath();x.moveTo(480-uw/2,210);x.lineTo(480+uw/2,210);x.stroke();x.font='13px monospace';x.fillStyle='#b9a6ff';x.fillText(win?'the spectrum holds':'mutual oblivion',480,230);x.font='bold 13px monospace';x.fillStyle='#ffd34a';x.fillText('▶ ENTER · REMATCH',480,248);x.textAlign='left'}if(net&&started){x.fillStyle=netUI=='left'?'#ff5a7a':'#4fe0ff';x.beginPath();x.arc(VW-15,VH-14,4,0,PI*2);x.fill();if(netUI=='left'){chip(300,214,360,96,'#1a0d1ff6','#ff5a7a');x.textAlign='center';x.font='bold 24px monospace';x.fillStyle='#fff';x.fillText('OPPONENT LEFT',480,260);x.font='bold 13px monospace';x.fillStyle='#ffd34a';x.fillText('ENTER — CONTINUE LOCAL',480,292);x.textAlign='left'}}}
let last=0,acc=0,step=1e3/60;function loop(n){if(last)acc=Math.min(acc+n-last,250);last=n;while(acc>=step){update();acc-=step}draw();requestAnimationFrame(loop)}requestAnimationFrame(loop);