{"connections":[{"sourceLocalID":1,"sourcePortID":"outlet_output","targetLocalID":0,"targetPortID":"inlet_input"}],"hideGrid":false,"name":"Starfighter Arcade","nodes":[{"config":{"chrome":"none","closeIsDismiss":true,"height":520,"html":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>Starfighter Arcade</title>\n<style>\n*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#030611;color:#e7f7ff;font-family:system-ui,sans-serif}canvas{position:fixed;inset:0;display:block}.hud{position:fixed;top:28px;left:32px;right:32px;display:flex;align-items:center;gap:32px;pointer-events:none}.brand{font-size:13px;letter-spacing:5px;font-weight:800;color:#6feaff}.stat{font-size:11px;letter-spacing:2px;color:#8a9bbd}.stat b{display:block;font-size:24px;letter-spacing:1px;color:white;margin-top:4px;font-variant-numeric:tabular-nums}.exit{margin-left:auto;pointer-events:auto}button{border:1px solid #54738c;background:#0b1830;color:#e7f7ff;border-radius:9px;padding:12px 20px;cursor:pointer;font:600 12px system-ui;letter-spacing:1px}button:hover{background:#183756;border-color:#6feaff}button:focus-visible{outline:2px solid #6feaff;outline-offset:4px}.controls{position:fixed;bottom:22px;left:0;right:0;text-align:center;font-size:11px;letter-spacing:2px;color:#8a9bbd;pointer-events:none}.overlay{position:fixed;inset:0;display:grid;place-items:center;background:#030611a8;backdrop-filter:blur(7px)}.overlay[hidden]{display:none}.card{text-align:center;padding:44px;border:1px solid #3f5775;border-radius:24px;background:linear-gradient(145deg,#13223bed,#070e20ed);box-shadow:0 20px 100px #0008;max-width:90vw}.eyebrow{color:#6feaff;letter-spacing:5px;font-size:11px}h1{font-size:clamp(32px,6vw,64px);margin:14px 0;letter-spacing:-2px}.card p{color:#a1b4ce;margin-bottom:28px}.card button{background:#6feaff;color:#061321;border:0}.hint{margin-left:16px;color:#8a9bbd;font-size:12px}\n</style>\n</head>\n<body>\n<canvas id=\"game\" aria-label=\"Space shooter. Use arrow keys to move and Space to shoot.\"></canvas>\n<div class=\"hud\"><div class=\"brand\">STARFIGHTER</div><div class=\"stat\">SCORE<b id=\"score\">0</b></div><div class=\"stat\">SHIELDS<b id=\"lives\">3</b></div><div class=\"stat\">WAVE<b id=\"wave\">1</b></div><button class=\"exit\" id=\"exit\">EXIT · ESC</button></div>\n<div class=\"controls\">↑ ↓ ← → MOVE &nbsp; / &nbsp; HOLD SPACE TO FIRE &nbsp; / &nbsp; R RESTART</div>\n<div class=\"overlay\" id=\"over\" hidden><div class=\"card\"><div class=\"eyebrow\">TRANSMISSION LOST</div><h1>Ship down.</h1><p id=\"summary\"></p><button id=\"restart\">FLY AGAIN</button><span class=\"hint\">or press R</span></div></div>\n<script>\n'use strict';\nconst canvas=document.getElementById('game'),ctx=canvas.getContext('2d');\nconst scoreEl=document.getElementById('score'),livesEl=document.getElementById('lives'),waveEl=document.getElementById('wave'),over=document.getElementById('over');\nlet w=innerWidth,h=innerHeight,dpr=1,player,bullets=[],monsters=[],sparks=[],stars=[],score=0,lives=3,wave=1,spawn=0,cooldown=0,elapsed=0,dead=false,last=performance.now(),shake=0;\nconst keys=new Set(),rand=(a,b)=>a+Math.random()*(b-a),clamp=(v,a,b)=>Math.max(a,Math.min(b,v));\nfunction resize(){w=innerWidth;h=innerHeight;dpr=Math.min(devicePixelRatio||1,2);canvas.width=Math.round(w*dpr);canvas.height=Math.round(h*dpr);canvas.style.width=w+'px';canvas.style.height=h+'px';ctx.setTransform(dpr,0,0,dpr,0,0);stars=Array.from({length:170},()=>({x:rand(0,w),y:rand(0,h),z:rand(.2,1)}));if(player){player.x=clamp(player.x,25,w-25);player.y=clamp(player.y,95,h-55)}}\nfunction hud(){scoreEl.textContent=score.toLocaleString();livesEl.textContent=lives;waveEl.textContent=wave}\nfunction reset(){player={x:w/2,y:h*.78,r:18,inv:2};bullets=[];monsters=[];sparks=[];score=0;lives=3;wave=1;elapsed=0;spawn=.7;cooldown=0;shake=0;dead=false;keys.clear();over.hidden=true;hud()}\nfunction exitGame(){keys.clear();if(window.watchflows)window.watchflows.dismiss()}\ndocument.getElementById('exit').onclick=exitGame;\ndocument.getElementById('restart').onclick=reset;\naddEventListener('keydown',e=>{if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Space','KeyR','Escape'].includes(e.code)){e.preventDefault();if(e.code==='Escape'){exitGame();return}if(e.code==='KeyR'&&!e.repeat){reset();return}keys.add(e.code)}});\naddEventListener('keyup',e=>keys.delete(e.code));\naddEventListener('blur',()=>keys.clear());\ndocument.addEventListener('visibilitychange',()=>{keys.clear();last=performance.now()});\naddEventListener('resize',resize);\nfunction burst(x,y,color,n=20){for(let i=0;i<n;i++){const a=rand(0,Math.PI*2),v=rand(40,230);sparks.push({x,y,vx:Math.cos(a)*v,vy:Math.sin(a)*v,life:rand(.25,.7),color})}}\nfunction damage(){if(dead||player.inv>0)return;lives--;player.inv=1.6;shake=12;burst(player.x,player.y,'#6feaff',30);hud();if(lives<=0){dead=true;keys.clear();over.hidden=false;document.getElementById('summary').textContent='You scored '+score.toLocaleString()+' points and reached wave '+wave+'.';document.getElementById('restart').focus()}}\nfunction createMonster(){const r=rand(19,32),x=rand(r+10,w-r-10);monsters.push({x,y:-r*2,base:x,r,speed:rand(65,105)+wave*12,phase:rand(0,Math.PI*2),age:0,hp:wave>=4?2:1,color:['#fc78bb','#b497ff','#82f5b1'][Math.floor(rand(0,3))]})}\nfunction update(dt){for(const s of stars){s.y+=(25+s.z*130)*dt;if(s.y>h){s.y=0;s.x=rand(0,w)}}shake=Math.max(0,shake-35*dt);for(const p of sparks){p.x+=p.vx*dt;p.y+=p.vy*dt;p.life-=dt}sparks=sparks.filter(p=>p.life>0);if(dead)return;elapsed+=dt;const next=1+Math.floor(elapsed/20);if(next!==wave){wave=next;hud()}player.inv=Math.max(0,player.inv-dt);let dx=(keys.has('ArrowRight')?1:0)-(keys.has('ArrowLeft')?1:0),dy=(keys.has('ArrowDown')?1:0)-(keys.has('ArrowUp')?1:0);const len=Math.hypot(dx,dy)||1;player.x=clamp(player.x+dx/len*360*dt,25,w-25);player.y=clamp(player.y+dy/len*360*dt,95,h-55);cooldown-=dt;if(keys.has('Space')&&cooldown<=0){bullets.push({x:player.x-10,y:player.y-20},{x:player.x+10,y:player.y-20});cooldown=.14}for(const b of bullets)b.y-=700*dt;spawn-=dt;if(spawn<=0){createMonster();spawn=Math.max(.24,1.05-wave*.075)*rand(.7,1.15)}for(const m of monsters){m.age+=dt;m.y+=m.speed*dt;m.x=clamp(m.base+Math.sin(m.age*2.2+m.phase)*65,m.r,w-m.r);for(const b of bullets){if(!b.used&&m.hp>0&&Math.hypot(b.x-m.x,b.y-m.y)<m.r+5){b.used=true;m.hp--;burst(b.x,b.y,m.color,5);if(m.hp===0){score+=100;burst(m.x,m.y,m.color,26);hud()}}}if(m.hp>0&&Math.hypot(player.x-m.x,player.y-m.y)<player.r+m.r-5){m.hp=0;burst(m.x,m.y,m.color,20);damage()}}bullets=bullets.filter(b=>!b.used&&b.y>-30);monsters=monsters.filter(m=>m.hp>0&&m.y<h+m.r*2)}\nfunction drawMonster(m){ctx.save();ctx.translate(m.x,m.y);ctx.shadowColor=m.color;ctx.shadowBlur=15;ctx.strokeStyle=m.color;ctx.fillStyle='#17203b';ctx.lineWidth=3;ctx.beginPath();ctx.ellipse(0,0,m.r,m.r*.77,0,0,Math.PI*2);ctx.fill();ctx.stroke();for(let i=-1;i<=1;i++){const x=i*m.r*.6;ctx.beginPath();ctx.moveTo(x,m.r*.5);ctx.quadraticCurveTo(x+Math.sin(elapsed*6+m.phase+i)*12,m.r*1.1,x-6,m.r*1.3);ctx.stroke()}ctx.beginPath();ctx.moveTo(-m.r*.7,-m.r*.48);ctx.lineTo(-m.r*.8,-m.r*1.1);ctx.moveTo(m.r*.7,-m.r*.48);ctx.lineTo(m.r*.8,-m.r*1.1);ctx.stroke();ctx.shadowBlur=0;ctx.fillStyle='#fff';for(const x of [-m.r*.35,m.r*.35]){ctx.beginPath();ctx.ellipse(x,-3,m.r*.23,m.r*.29,0,0,Math.PI*2);ctx.fill();ctx.fillStyle='#12162a';ctx.beginPath();ctx.arc(x+clamp((player.x-m.x)/100,-2,2),0,m.r*.1,0,Math.PI*2);ctx.fill();ctx.fillStyle='#fff'}ctx.strokeStyle=m.color;ctx.beginPath();ctx.moveTo(-7,10);ctx.lineTo(0,6);ctx.lineTo(7,10);ctx.stroke();ctx.restore()}\nfunction drawShip(){if(dead)return;ctx.save();ctx.translate(player.x,player.y);if(player.inv>0)ctx.globalAlpha=.55+.45*Math.sin(elapsed*30)**2;ctx.shadowColor='#60eaff';ctx.shadowBlur=22;ctx.fillStyle='#38bcff';ctx.beginPath();ctx.moveTo(-8,18);ctx.lineTo(0,34+Math.random()*18);ctx.lineTo(8,18);ctx.fill();ctx.fillStyle='#e5f7ff';ctx.strokeStyle='#6feaff';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(0,-25);ctx.lineTo(21,20);ctx.lineTo(7,14);ctx.lineTo(0,20);ctx.lineTo(-7,14);ctx.lineTo(-21,20);ctx.closePath();ctx.fill();ctx.stroke();ctx.shadowBlur=0;ctx.fillStyle='#17678e';ctx.beginPath();ctx.ellipse(0,-4,5,10,0,0,Math.PI*2);ctx.fill();if(player.inv>0){ctx.strokeStyle='#6feaff55';ctx.beginPath();ctx.arc(0,0,33,0,Math.PI*2);ctx.stroke()}ctx.restore()}\nfunction draw(){ctx.setTransform(dpr,0,0,dpr,0,0);ctx.fillStyle='#030611';ctx.fillRect(0,0,w,h);const glow=ctx.createRadialGradient(w*.65,h*.3,0,w*.65,h*.3,Math.max(w,h)*.65);glow.addColorStop(0,'#172348');glow.addColorStop(.45,'#0c1129');glow.addColorStop(1,'#030611');ctx.fillStyle=glow;ctx.fillRect(0,0,w,h);ctx.save();if(shake)ctx.translate(rand(-shake,shake),rand(-shake,shake));for(const s of stars){ctx.fillStyle='rgba(195,220,255,'+s.z+')';ctx.fillRect(s.x,s.y,s.z*2,s.z*3)}ctx.shadowColor='#72f7ff';ctx.shadowBlur=16;ctx.fillStyle='#bdffff';for(const b of bullets)ctx.fillRect(b.x-2,b.y-12,4,20);ctx.shadowBlur=0;for(const m of monsters)drawMonster(m);drawShip();for(const p of sparks){ctx.globalAlpha=Math.min(1,p.life*2);ctx.fillStyle=p.color;ctx.fillRect(p.x-2,p.y-2,4,4)}ctx.globalAlpha=1;ctx.restore();if(!dead&&elapsed<4){ctx.textAlign='center';ctx.fillStyle='rgba(170,220,255,'+Math.min(1,4-elapsed)+')';ctx.font='600 18px system-ui';ctx.fillText('Incoming monsters. Hold SPACE to fire.',w/2,h*.38)}}\nfunction frame(now){const dt=Math.min((now-last)/1000,.04);last=now;if(!document.hidden){update(dt);draw()}requestAnimationFrame(frame)}\nresize();reset();requestAnimationFrame(frame);\n</script>\n</body>\n</html>","timeoutMinutes":0,"title":"Starfighter Arcade","width":720,"windowMode":"fullscreen"},"customName":"Starfighter Arcade","isDisabled":false,"localID":0,"typeID":"action.html_view","x":460,"y":260},{"config":{"inputType":"text","textInput":"Play"},"customName":"Launch Game","isDisabled":false,"localID":1,"typeID":"trigger.input","x":100,"y":200},{"config":{"_height":380,"_width":460,"text":"**Starfighter Arcade is a full-screen space shooter that starts when you run this flow.**\n\n**What it does**\n- An arcade game that takes over the screen and works offline.\n- Shoot down waves of monsters for points. You have three shields.\n- The game waits until you close it, then the flow ends.\n\n**Set it up**\n- Nothing to connect. Run the flow to play.\n- Arrow keys move, hold Space to fire, R restarts.\n- Press Escape or click Exit to close the game.\n\n**Make it yours**\n- Set Window to Window on **Starfighter Arcade** to play in a normal window instead of full screen."},"isDisabled":false,"localID":2,"typeID":"note","x":100,"y":-220}],"version":1}