Run details
- Model
- opencode/muse-spark-1.3-contributor-free
- Route
- opencode CLI
- Reasoning
- provider default
- Limits
- 60 min · 131,072 output tokens
- Date
- 2 Oct 2026
- Time
- 77 s
- Size
- 25 kB
- Tokens
- 18,931 in, 10,507 out
- Paid
- Not billed per run (CLI plan)
Exact prompt
Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice. It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Save it as index.html in the current directory, then stop. Do not ask questions.
Source
25 kB 524 lines Raw
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BREAKOUT — Neon Break</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
html,body { height:100%; }
body {
background:#070714 radial-gradient(1200px 800px at 50% -10%, #1b1b4d 0%, #070714 60%) fixed;
color:#eef2ff;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
display:flex; align-items:center; justify-content:center;
min-height:100vh; overflow:hidden;
user-select:none; -webkit-user-select:none;
}
#wrap { width:min(920px,98vw); display:flex; flex-direction:column; align-items:center; gap:10px; padding:12px; }
#hud {
width:min(860px,94vw); display:flex; align-items:center; justify-content:space-between;
background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.14);
border-radius:14px; padding:10px 16px; backdrop-filter: blur(6px);
box-shadow:0 8px 30px rgba(0,0,0,.4);
font-variant-numeric: tabular-nums;
}
#hud .title { font-weight:900; letter-spacing:.14em; font-size:15px; }
#hud .title span { color:#22d3ee; text-shadow:0 0 12px #22d3ee; }
#stats { display:flex; gap:18px; font-size:14px; font-weight:700; letter-spacing:.06em; }
#stats b { color:#fff; font-size:16px; }
.pill { background:rgba(0,0,0,.35); border:1px solid rgba(255,255,255,.12); border-radius:999px; padding:4px 12px; }
#stage { position:relative; width:min(860px,94vw); aspect-ratio: 860/600; }
canvas {
width:100%; height:100%; display:block; border-radius:16px;
background: #0a0a22;
border:1px solid rgba(255,255,255,.16);
box-shadow: 0 20px 80px rgba(34,211,238,.15), 0 10px 40px rgba(0,0,0,.6);
cursor:none; touch-action:none;
}
#overlay {
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
text-align:center; pointer-events:none;
}
#card {
pointer-events:auto;
background:rgba(8,8,28,.82); border:1px solid rgba(255,255,255,.18);
border-radius:18px; padding:28px 34px; max-width:520px; width:88%;
box-shadow:0 20px 60px rgba(0,0,0,.6); backdrop-filter: blur(10px);
}
#card h1 { font-size:44px; letter-spacing:.1em; font-weight:900; line-height:1; }
#card h1 .neon { color:#22d3ee; text-shadow:0 0 18px #22d3ee,0 0 50px #0ea5e9; }
#card h1 .pink { color:#f472b6; text-shadow:0 0 18px #f472b6,0 0 50px #ec4899; }
#card p { margin:12px 0; color:#cbd5e1; font-size:14.5px; line-height:1.55; }
#card kbd {
background:#1e293b; border:1px solid #475569; border-bottom-width:3px;
border-radius:6px; padding:1px 7px; font-size:12px; font-family:inherit; font-weight:800;
}
#bigbtn {
margin-top:14px; font-size:18px; font-weight:900; letter-spacing:.08em;
color:#04121a; background:linear-gradient(180deg,#67e8f9,#22d3ee 60%,#0ea5e9);
border:0; border-radius:12px; padding:12px 26px; cursor:pointer; width:100%;
box-shadow:0 0 24px rgba(34,211,238,.6), 0 6px 0 #0c4a6e;
}
#bigbtn:active { transform:translateY(3px); box-shadow:0 0 18px rgba(34,211,238,.5), 0 2px 0 #0c4a6e; }
#hint { font-size:12.5px; color:#94a3b8; margin-top:10px; }
#levelbar { display:flex; gap:8px; align-items:center; font-size:12px; font-weight:800; letter-spacing:.1em; color:#94a3b8; }
.dot { width:26px; height:8px; border-radius:99px; background:#1e293b; border:1px solid rgba(255,255,255,.15); }
.dot.done { background:#22d3ee; box-shadow:0 0 10px #22d3ee; }
.dot.cur { background:#f472b6; box-shadow:0 0 10px #f472b6; }
.hidden { display:none !important; }
</style>
</head>
<body>
<div id="wrap">
<div id="hud">
<div class="title"><span>NEON</span> BREAKOUT</div>
<div id="stats">
<div class="pill">SCORE <b id="score">0</b></div>
<div class="pill">BEST <b id="best">0</b></div>
<div class="pill">LEVEL <b id="level">1/5</b></div>
<div class="pill">LIVES <b id="lives">●●●</b></div>
</div>
</div>
<div id="stage">
<canvas id="game" width="860" height="600"></canvas>
<div id="overlay"><div id="card">
<h1><span class="neon">NEON</span> <span class="pink">BREAK</span></h1>
<p id="cardtext">Smash all bricks across <b>5 levels</b>. Move with <kbd>◀</kbd><kbd>▶</kbd> or <b>mouse</b>. Launch with <kbd>Space</kbd> or <b>click</b>.<br>Catch falling capsules for power-ups. Chain bricks without missing for <b>COMBO ×</b> bonus.</p>
<button id="bigbtn">▶ START GAME</button>
<div id="hint">P pause • M mute • R restart level</div>
</div></div>
</div>
<div id="levelbar">LEVELS <div class="dot" id="d0"></div><div class="dot" id="d1"></div><div class="dot" id="d2"></div><div class="dot" id="d3"></div><div class="dot" id="d4"></div> W wide • M multiball • S slow • L +life • F laser</div>
</div>
<script>
"use strict";
const cv = document.getElementById('game');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height;
const elScore=document.getElementById('score'), elBest=document.getElementById('best'),
elLevel=document.getElementById('level'), elLives=document.getElementById('lives'),
overlay=document.getElementById('overlay'), card=document.getElementById('card'),
cardtext=document.getElementById('cardtext'), bigbtn=document.getElementById('bigbtn');
const COLORS = { '1':'#22d3ee', '2':'#a78bfa', '3':'#f472b6', '4':'#fbbf24', '5':'#34d399', 'X':'#64748b' };
const HP = { '1':1,'2':1,'3':2,'4':2,'5':3,'X':Infinity };
// 5 levels. .=empty
const LEVELS = [
["",
"1111111111",
"1111111111",
"2222222222",
"2222222222",
"3333333333"],
["",
"5........5",
".44444444.",
"..333333..",
"..222222..",
"...1111...",
"...1111..."],
["",
"X11XXXX11X",
"X111XX111X",
"2211111122",
"3332222333",
".44444444.",
"..555555.."],
["",
"1X2X3X4X5X1",
"1112223334",
"55..XXXX..",
"..543212..",
"..5....5..",
".44....44."],
["",
"5555555555",
"4444444444",
"3333333333",
"2222222222",
"1111111111",
"X1X1X1X1X1"]
];
let state='menu', score=0, best=0, lives=3, levelIdx=0, combo=0, comboTimer=0;
let shake=0, flash=0, slowmo=0, timeScale=1;
let particles=[], popups=[], powerups=[], bullets=[], stars=[], balls=[];
let paddle={x:W/2,w:120,h:16,squash:0,targetW:120,sticky:false,laser:0,laserCd:0};
let keys={};
let muted=false, audio=null;
function ac(){ if(!audio){ try{ audio=new (window.AudioContext||window.webkitAudioContext)(); }catch(e){} } if(audio&&audio.state==='suspended')audio.resume(); return audio; }
function beep(f,dur,type,vol,slide){
if(muted) return; const a=ac(); if(!a) return;
const o=a.createOscillator(), g=a.createGain();
o.type=type||'square'; o.frequency.setValueAtTime(f,a.currentTime);
if(slide) o.frequency.exponentialRampToValueAtTime(Math.max(30,f+slide),a.currentTime+dur);
g.gain.setValueAtTime(vol||.12,a.currentTime);
g.gain.exponentialRampToValueAtTime(.0001,a.currentTime+dur);
o.connect(g); g.connect(a.destination); o.start(); o.stop(a.currentTime+dur+.02);
}
const sfx={
paddle(){beep(220,.09,'square',.10,220);},
wall(){beep(180,.06,'square',.07,80);},
brick(hp){beep(hp<=0?520:340,.12,'sawtooth',.11,hp<=0?380:120); if(hp<=0)beep(880,.18,'sine',.09,300);},
launch(){beep(300,.15,'sine',.12,400);},
power(){[440,560,700,880].forEach((f,i)=>setTimeout(()=>beep(f,.12,'square',.09),i*60));},
lose(){beep(300,.5,'sawtooth',.12,-240);},
win(){[523,659,784,1046].forEach((f,i)=>setTimeout(()=>beep(f,.22,'triangle',.11),i*110));},
shoot(){beep(900,.08,'square',.06,-400);}
};
for(let i=0;i<90;i++) stars.push({x:Math.random()*W,y:Math.random()*H,s:Math.random()*1.8+.4,v:Math.random()*18+4});
let bricks=[];
function buildLevel(idx){
bricks=[]; bullets=[]; powerups=[]; particles=[]; popups=[];
paddle.w=paddle.targetW=120; paddle.sticky=false; paddle.laser=0;
balls=[newBall(true)];
const rows=LEVELS[idx]; const gap=6, top=86, side=36;
const cols=Math.max(...rows.map(r=>r.length));
const bw=(W-side*2-gap*(cols-1))/cols, bh=26;
rows.forEach((row,r)=>{
for(let c=0;c<row.length;c++){
const ch=row[c]; if(!ch||ch==='.'||ch===' ') continue;
bricks.push({c,r,ch,hp:HP[ch],maxhp:HP[ch]===Infinity?1:HP[ch],
x:side+c*(bw+gap),y:top+r*(bh+gap),w:bw,h:bh,
flash:0,wob:Math.random()*6.28,color:COLORS[ch]||'#fff'});
}
});
updateHUD();
}
function newBall(stuck){
return {x:paddle.x,y:H-60,r:8,vx:(Math.random()<.5?-1:1)*(260+levelIdx*35),vy:-(380+levelIdx*40),
stuck:!!stuck,trail:[],speedMul:1};
}
function updateHUD(){
elScore.textContent=score;
elBest.textContent=best;
elLevel.textContent=(levelIdx+1)+"/"+LEVELS.length;
elLives.textContent="●".repeat(Math.max(0,lives))+"○".repeat(Math.max(0,3-Math.min(3,lives)))+(lives>3?" +"+(lives-3):"");
for(let i=0;i<5;i++){const d=document.getElementById('d'+i);d.className='dot'+(i<levelIdx?' done':i===levelIdx?' cur':'');}
}
function addScore(n,x,y,label){
const mult=1+Math.min(8,Math.floor(combo/4));
const pts=n*mult; score+=pts; if(score>best)best=score;
popups.push({x:x||W/2,y:y||H/2,txt:'+'+pts+(label?' '+label:'')+(mult>1?' x'+mult:''),life:1,vy:-60});
updateHUD();
}
function burst(x,y,color,n,power){
for(let i=0;i<(n||18);i++){const a=Math.random()*6.28,sp=(power||260)*(0.25+Math.random());
particles.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-80,life:.5+Math.random()*.6,max:1,color,r:2+Math.random()*3.5});}
}
function setShake(n){ shake=Math.max(shake,n); }
function showCard(html,btn){ cardtext.innerHTML=html; bigbtn.innerHTML=btn; overlay.classList.remove('hidden'); }
function hideCard(){ overlay.classList.add('hidden'); }
function startGame(){
ac(); score=0;lives=3;levelIdx=0;combo=0;buildLevel(0);
balls=[newBall(true)]; state='serve'; hideCard(); updateHUD(); sfx.launch();
}
function nextLevel(){
levelIdx++;
if(levelIdx>=LEVELS.length){ winGame(); return; }
score+=500*(levelIdx); sfx.win();
showCard('LEVEL '+(levelIdx)+' CLEARED!<br>Bonus <b>+'+(500*levelIdx)+'</b> points.<br>Get ready for <b>LEVEL '+(levelIdx+1)+'</b> — faster ball, tougher bricks.',
'▶ PLAY LEVEL '+(levelIdx+1));
state='between'; updateHUD(); bigbtn.onclick=()=>{ buildLevel(levelIdx); state='serve'; hideCard(); };
}
function winGame(){
state='win'; sfx.win();
burst(W/2,H/2,'#22d3ee',120,500); burst(W/2,H/2,'#f472b6',120,500);
showCard('YOU BEAT ALL 5 LEVELS!<br>Final score: <b>'+score+'</b> '+(score>=best?'— NEW BEST!':'')+'<br>Press the button to play again and chase a higher combo.',
'↻ PLAY AGAIN');
bigbtn.onclick=startGame;
}
function loseLife(){
lives--; combo=0; sfx.lose(); setShake(16); flash=.5;
burst(paddle.x,H-50,'#f87171',40,380);
updateHUD();
if(lives<=0){
state='over'; sfx.lose();
showCard('GAME OVER<br>Score: <b>'+score+'</b> — Level reached: <b>'+(levelIdx+1)+'</b><br>Tip: angle your shots off the paddle edges, and save the <b>F laser</b> capsule for walls of tough bricks.',
'↻ TRY AGAIN');
bigbtn.onclick=startGame;
} else { balls=[newBall(true)]; state='serve'; }
}
bigbtn.onclick=startGame;
// input
window.addEventListener('keydown',e=>{
keys[e.key.toLowerCase()]=true;
if([' ','arrowleft','arrowright'].includes(e.key.toLowerCase())||e.key===' ') e.preventDefault();
ac();
if(e.key===' '||e.key==='Enter'){ if(state!=='play'&&state!=='pause') bigbtn.click(); else launchBalls(); }
if(e.key.toLowerCase()==='p'){ if(state==='play'){state='pause';showCard('PAUSED<br>Take a breath. Ball frozen mid-flight.','▶ RESUME');bigbtn.onclick=()=>{hideCard();state='play';};} }
if(e.key.toLowerCase()==='m'){ muted=!muted; }
if(e.key.toLowerCase()==='r'&&(state==='play'||state==='serve')){ buildLevel(levelIdx); state='serve'; }
if(e.key===' '&&paddle.laser>0&&state==='play'){ fireLaser(); }
});
window.addEventListener('keyup',e=>{keys[e.key.toLowerCase()]=false;});
function canvasX(e){ const r=cv.getBoundingClientRect(); const cx=(e.touches?e.touches[0].clientX:e.clientX); return (cx-r.left)/r.width*W; }
cv.addEventListener('mousemove',e=>{ if(state==='play'||state==='serve') paddle.x=Math.max(paddle.w/2+4,Math.min(W-paddle.w/2-4,canvasX(e))); });
cv.addEventListener('click',e=>{ ac(); if(state==='play'||state==='serve'){ const r=canvasX(e); paddle.x=Math.max(paddle.w/2+4,Math.min(W-paddle.w/2-4,r)); launchBalls(); } });
cv.addEventListener('touchmove',e=>{ e.preventDefault(); if(state==='play'||state==='serve') paddle.x=Math.max(paddle.w/2+4,Math.min(W-paddle.w/2-4,canvasX(e))); },{passive:false});
cv.addEventListener('touchstart',e=>{ ac(); if(state==='play'||state==='serve') launchBalls(); },{passive:true});
function launchBalls(){ let any=false; balls.forEach(b=>{if(b.stuck){b.stuck=false;any=true;}}); if(any) sfx.launch(); }
function fireLaser(){ if(paddle.laserCd>0)return; paddle.laserCd=.22; sfx.shoot();
bullets.push({x:paddle.x-18,y:H-70,vy:-900}); bullets.push({x:paddle.x+18,y:H-70,vy:-900}); }
function dropPower(x,y){
if(Math.random()<0.22){
const t=['W','M','S','L','F'][Math.floor(Math.random()*5)];
if(t==='L'&&Math.random()<.6) return; // extra life rarer
powerups.push({x,y,vy:150,vy2:0,type:t,ph:Math.random()*6});
}
}
function applyPower(t,x,y){
sfx.power(); popups.push({x,y,txt:{W:'WIDE!',M:'MULTIBALL!',S:'SLOW-MO!',L:'+1 LIFE!',F:'LASER!'}[t],life:1.1,vy:-70});
burst(x,y,'#ffffff',22,300);
if(t==='W'){ paddle.targetW=Math.min(220,paddle.targetW+50); paddle.w+=(paddle.targetW-paddle.w)*.9; setTimeout(()=>{paddle.targetW=Math.max(120,paddle.targetW-50);},12000); }
if(t==='M'){ const src=balls[0]||newBall(false); for(let i=0;i<2&&balls.length<6;i++) balls.push({x:src.x,y:src.y,r:8,vx:-src.vx+(Math.random()*200-100),vy:-Math.abs(src.vy),stuck:false,trail:[],speedMul:1}); }
if(t==='S'){ slowmo=6; }
if(t==='L'){ lives++; updateHUD(); }
if(t==='F'){ paddle.laser=12; }
}
// physics helpers
function collideBallBrick(b,br){
const nx=Math.max(br.x,Math.min(b.x,br.x+br.w)), ny=Math.max(br.y,Math.min(b.y,br.y+br.h));
const dx=b.x-nx, dy=b.y-ny;
if(dx*dx+dy*dy<=b.r*b.r){
// determine side
const overlapL=b.x+b.r-br.x, overlapR=br.x+br.w-(b.x-b.r),
overlapT=b.y+b.r-br.y, overlapB=br.y+br.h-(b.y-b.r);
const m=Math.min(overlapL,overlapR,overlapT,overlapB);
if(m===overlapL||m===overlapR){ b.vx*=-1; b.x+= (m===overlapL?-4:4); }
else { b.vy*=-1; b.y+=(m===overlapT?-4:4); }
return true;
} return false;
}
let last=performance.now();
function loop(now){
requestAnimationFrame(loop);
let dt=Math.min(.033,(now-last)/1000); last=now;
timeScale += ((slowmo>0?0.45:1)-timeScale)*Math.min(1,dt*6);
if(slowmo>0) slowmo-=dt;
if(state==='play') update(dt*timeScale,dt);
else updateFx(dt);
render(now/1000);
}
function updateFx(dt){
particles=particles.filter(p=>{p.life-=dt;p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=700*dt;return p.life>0;});
popups=popups.filter(p=>{p.life-=dt;p.y+=p.vy*dt;return p.life>0;});
stars.forEach(s=>{s.y+=s.v*dt;if(s.y>H){s.y=0;s.x=Math.random()*W;}});
if(shake>0)shake=Math.max(0,shake-dt*40);
if(flash>0)flash=Math.max(0,flash-dt*2);
}
function update(dt,rdt){
// paddle
const spd=640;
const prevX=paddle.x;
if(keys['arrowleft']||keys['a']) paddle.x-=spd*dt;
if(keys['arrowright']||keys['d']) paddle.x+=spd*dt;
paddle.x=Math.max(paddle.w/2+4,Math.min(W-paddle.w/2-4,paddle.x));
paddle.w+=(paddle.targetW-paddle.w)*Math.min(1,dt*6);
paddle.squash*=Math.max(0,1-dt*8);
const pvx=(paddle.x-prevX)/Math.max(dt,.001);
paddle.laser=Math.max(0,paddle.laser-dt); paddle.laserCd=Math.max(0,paddle.laserCd-rdt);
if(keys[' ']&&paddle.laser>0) fireLaser();
if(comboTimer>0){comboTimer-=dt; if(comboTimer<=0)combo=0;}
// bullets
bullets=bullets.filter(bl=>{ bl.y+=bl.vy*dt;
for(const br of bricks){ if(br.hp>0&&bl.x>br.x&&bl.x<br.x+br.w&&bl.y>br.y&&bl.y<br.y+br.h){ hitBrick(br,bl.x,bl.y); burst(bl.x,bl.y,'#fde047',10,220); bl.dead=true; break; } }
return !bl.dead&&bl.y>-20; });
// balls
let alive=true;
for(const b of balls){
b.trail.push({x:b.x,y:b.y,life:.3}); b.trail=b.trail.filter(t=>{t.life-=dt;return t.life>0;});
if(b.stuck){ b.x+=(paddle.x-b.x)*Math.min(1,dt*14); b.y=H-60; continue; }
b.x+=b.vx*dt; b.y+=b.vy*dt;
if(b.x<b.r+4){b.x=b.r+4;b.vx=Math.abs(b.vx);sfx.wall();burst(4,b.y,'#38bdf8',6,160);}
if(b.x>W-b.r-4){b.x=W-b.r-4;b.vx=-Math.abs(b.vx);sfx.wall();burst(W-4,b.y,'#38bdf8',6,160);}
if(b.y<b.r+4){b.y=b.r+4;b.vy=Math.abs(b.vy);sfx.wall();}
// paddle
const ph=H-44;
if(b.vy>0&&b.y+b.r>=ph&&b.y+b.r<=ph+paddle.h+14&&Math.abs(b.x-paddle.x)<=paddle.w/2+b.r){
const rel=(b.x-paddle.x)/(paddle.w/2); // -1..1
const ang=rel*1.05; // max ~60deg
const sp=Math.min(760,Math.hypot(b.vx,b.vy)*1.015+8);
b.vx=Math.sin(ang)*sp + pvx*0.25;
b.vy=-Math.abs(Math.cos(ang)*sp);
b.y=ph-b.r-1;
paddle.squash=1; combo=0; sfx.paddle(); burst(b.x,ph,'#22d3ee',8,200);
}
// bricks
for(const br of bricks){
if(br.hp<=0) continue;
if(collideBallBrick(b,br)){ hitBrick(br,b.x,b.y); break; }
}
}
// remove fallen
const before=balls.length;
balls=balls.filter(b=>b.y<H+30);
if(balls.length===0&&before>0){ loseLife(); return; }
// powerups
const pph=H-44;
powerups=powerups.filter(p=>{ p.ph+=dt*4; p.x+=Math.sin(p.ph)*30*dt; p.y+=p.vy*dt;
if(p.y+pph>H-200){}
if(p.y+14>=pph&&p.y+14<=pph+26&&Math.abs(p.x-paddle.x)<paddle.w/2+14){ applyPower(p.type,p.x,p.y); return false; }
return p.y<H+20; });
updateFx(rdt);
// win check
if(bricks.every(b=>b.hp<=0||b.ch==='X')||bricks.filter(b=>b.hp>0&&b.ch!=='X').length===0){
// if unbreakable remain but all breakable gone -> clear them with fanfare
burst(W/2,H/2,'#fde047',60,420); setShake(8);
score+=100; updateHUD(); nextLevel(); return;
}
}
function hitBrick(br,x,y){
if(br.ch==='X'){ sfx.wall(); br.flash=1; burst(x,y,'#94a3b8',6,180); setShake(2); return; }
br.hp--; br.flash=1;
combo++; comboTimer=4;
if(br.hp<=0){ addScore(50,x+br.w/2,y); burst(x,y,br.color,24,340); dropPower(x,y); setShake(5); flash=Math.max(flash,.08); sfx.brick(0); }
else { addScore(20,x+br.w/2,y,'crack'); burst(x,y,'#ffffff',10,240); setShake(3); sfx.brick(1); }
}
function rr(x,y,w,h,r){ ctx.beginPath(); ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath(); }
function render(t){
ctx.save();
ctx.clearRect(0,0,W,H);
if(shake>0) ctx.translate((Math.random()-.5)*shake,(Math.random()-.5)*shake);
// bg
const g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,'#0b0b2a'); g.addColorStop(.6,'#0a0a22'); g.addColorStop(1,'#120a2e');
ctx.fillStyle=g; ctx.fillRect(-20,-20,W+40,H+40);
ctx.fillStyle='rgba(255,255,255,.5)';
stars.forEach(s=>{ ctx.globalAlpha=.25+((s.x+s.y+t*10)%100)/220; ctx.fillRect(s.x,s.y,s.s,s.s*2.2); });
ctx.globalAlpha=1;
// neon side walls glow
ctx.fillStyle='rgba(34,211,238,.25)'; ctx.fillRect(0,0,4,H); ctx.fillRect(W-4,0,4,H);
ctx.fillStyle='rgba(34,211,238,.08)'; ctx.fillRect(4,0,3,H); ctx.fillRect(W-7,0,3,H);
// combo meter
if(combo>=2&&state==='play'){
ctx.save(); ctx.textAlign='left'; ctx.font='900 18px system-ui';
ctx.fillStyle='#fde047'; ctx.shadowColor='#f59e0b'; ctx.shadowBlur=14;
ctx.fillText('COMBO x'+(1+Math.min(8,Math.floor(combo/4)))+' ('+combo+')',18,30);
// timer bar
ctx.shadowBlur=0; ctx.fillStyle='rgba(255,255,255,.15)'; rr(18,36,140,6,3); ctx.fill();
ctx.fillStyle='#fde047'; rr(18,36,140*Math.max(0,comboTimer/4),6,3); ctx.fill();
ctx.restore();
}
if(slowmo>0){ ctx.save(); ctx.textAlign='right'; ctx.font='900 16px system-ui'; ctx.fillStyle='#67e8f9'; ctx.shadowColor='#22d3ee'; ctx.shadowBlur=12; ctx.fillText('◔ SLOW-MO',W-16,30); ctx.restore(); }
// bricks
for(const br of bricks){
if(br.hp<=0) continue;
const wob = br.flash>0 ? 0 : Math.sin(t*2+br.wob)*1.2;
br.flash=Math.max(0,br.flash-0.06);
const f=br.flash;
ctx.save();
ctx.translate(br.x+br.w/2,br.y+br.h/2+wob*.4);
if(br.hp>0&&br.maxhp>1&&br.ch!=='X'){ const s=1+Math.sin(t*5+br.wob)*.015; ctx.scale(s,s); }
const bx=-br.w/2, by=-br.h/2;
ctx.shadowColor=br.color; ctx.shadowBlur=14+f*30;
const bg=ctx.createLinearGradient(0,by,0,by+br.h);
if(br.ch==='X'){ bg.addColorStop(0,'#475569'); bg.addColorStop(1,'#1e293b'); }
else { bg.addColorStop(0,br.color); bg.addColorStop(1,'#0f172a'); }
ctx.fillStyle=bg; rr(bx,by,br.w,br.h,7); ctx.fill();
ctx.shadowBlur=0;
ctx.fillStyle='rgba(255,255,255,'+(0.28+f*0.5)+')'; rr(bx+3,by+3,br.w-6,br.h/2-4,5); ctx.fill();
if(br.maxhp>1&&br.ch!=='X'){
// cracks / pips showing remaining hp
ctx.fillStyle='rgba(0,0,0,.45)'; ctx.font='900 13px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
ctx.fillText('●'.repeat(br.hp),0,1);
}
if(br.ch==='X'){ ctx.fillStyle='rgba(255,255,255,.7)'; ctx.font='900 14px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText('◆',0,1); }
ctx.restore();
}
// powerups
for(const p of powerups){
ctx.save(); ctx.translate(p.x,p.y); ctx.shadowBlur=16;
const col={W:'#34d399',M:'#f472b6',S:'#67e8f9',L:'#fde047',F:'#fb7185'}[p.type];
ctx.shadowColor=col; ctx.fillStyle='#0f172a'; ctx.strokeStyle=col; ctx.lineWidth=2;
ctx.beginPath(); ctx.ellipse(0,0,20,13,0,0,6.283); ctx.fill(); ctx.stroke();
ctx.fillStyle=col; ctx.font='900 13px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
ctx.fillText(p.type,0,1);
ctx.fillStyle=col; ctx.globalAlpha=.5+Math.sin(p.ph*2)*.3;
ctx.beginPath(); ctx.ellipse(0,16,10,3,0,0,6.283); ctx.fill(); ctx.globalAlpha=1;
ctx.restore();
}
// bullets
ctx.save(); ctx.shadowColor='#fde047'; ctx.shadowBlur=12; ctx.fillStyle='#fef08a';
for(const b of bullets){ rr(b.x-3,b.y-14,6,16,3); ctx.fill(); }
ctx.restore();
// paddle (with squash + laser)
const ph=H-44;
ctx.save();
ctx.translate(paddle.x,ph+paddle.h/2);
ctx.scale(1+paddle.squash*.18,1-paddle.squash*.28);
const pw=paddle.w, phh=paddle.h;
ctx.shadowColor= paddle.laser>0 ? '#fb7185' : '#22d3ee'; ctx.shadowBlur=22;
const pg=ctx.createLinearGradient(0,-phh/2,0,phh/2);
pg.addColorStop(0,'#e0feff'); pg.addColorStop(.45,'#22d3ee'); pg.addColorStop(1,'#0e7490');
ctx.fillStyle=pg; rr(-pw/2,-phh/2,pw,phh,8); ctx.fill();
ctx.shadowBlur=0; ctx.fillStyle='rgba(255,255,255,.75)'; rr(-pw/2+6,-phh/2+3,pw-12,5,3); ctx.fill();
if(paddle.laser>0){ ctx.fillStyle='#fb7185'; ctx.fillRect(-22,-phh/2-10,5,10); ctx.fillRect(17,-phh/2-10,5,10); }
ctx.restore();
// balls + trail
for(const b of balls){
for(const tr of b.trail){ ctx.globalAlpha=tr.life*1.6; ctx.fillStyle='#22d3ee'; ctx.beginPath(); ctx.arc(tr.x,tr.y,b.r*tr.life*2,0,6.283); ctx.fill(); }
ctx.globalAlpha=1;
ctx.save(); ctx.shadowColor='#fff'; ctx.shadowBlur=20;
const rg=ctx.createRadialGradient(b.x-3,b.y-3,1,b.x,b.y,b.r);
rg.addColorStop(0,'#fff'); rg.addColorStop(.5,'#a5f3fc'); rg.addColorStop(1,'#0891b2');
ctx.fillStyle=rg; ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,6.283); ctx.fill(); ctx.restore();
if(b.stuck&&state==='serve'){
ctx.save(); ctx.strokeStyle='rgba(255,255,255,.45)'; ctx.setLineDash([6,6]); ctx.lineWidth=2;
ctx.beginPath(); ctx.moveTo(b.x,b.y-12); ctx.lineTo(b.x+b.vx*.25,b.y+b.vy*.25); ctx.stroke(); ctx.restore();
ctx.save(); ctx.textAlign='center'; ctx.font='800 15px system-ui'; ctx.fillStyle='rgba(255,255,255,.85)';
ctx.fillText('SPACE / CLICK TO LAUNCH — ← → / MOUSE TO AIM',W/2,H-14); ctx.restore();
}
}
// particles
for(const p of particles){ ctx.globalAlpha=Math.max(0,p.life); ctx.fillStyle=p.color; ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,6.283); ctx.fill(); }
ctx.globalAlpha=1;
// popups
ctx.save(); ctx.textAlign='center'; ctx.font='900 17px system-ui';
for(const p of popups){ ctx.globalAlpha=Math.min(1,p.life*1.6); ctx.fillStyle='#fff'; ctx.shadowColor='#22d3ee'; ctx.shadowBlur=10; ctx.fillText(p.txt,p.x,p.y); }
ctx.restore(); ctx.globalAlpha=1;
if(flash>0){ ctx.fillStyle='rgba(255,255,255,'+(flash*.35)+')'; ctx.fillRect(-20,-20,W+40,H+40); }
// vignette
const v=ctx.createRadialGradient(W/2,H/2,H*.35,W/2,H/2,H*.85);
v.addColorStop(0,'rgba(0,0,0,0)'); v.addColorStop(1,'rgba(0,0,0,.45)');
ctx.fillStyle=v; ctx.fillRect(0,0,W,H);
ctx.restore();
if(state==='serve'&&overlay.classList.contains('hidden')){
// keep paddle/ball glued visual update even before first input handled in update? handle here minimally
}
}
// idle menu animation behind card
buildLevel(0); state='menu'; updateHUD();
requestAnimationFrame(loop);
// keep serve balls glued while in menu/serve without full update
setInterval(()=>{
if(state==='menu'||state==='serve'||state==='between'||state==='over'||state==='win'){
updateFx(1/60);
if(state==='serve') balls.forEach(b=>{ if(b.stuck){ b.x+=(paddle.x-b.x)*.3; b.y=H-60; }});
const spd=640/60;
if(keys['arrowleft']||keys['a']) paddle.x-=spd;
if(keys['arrowright']||keys['d']) paddle.x+=spd;
paddle.x=Math.max(paddle.w/2+4,Math.min(W-paddle.w/2-4,paddle.x));
}
},16);
</script>
</body>
</html>Muse Spark 1.3 Contributor: other attempts
- Muse Spark 1.3 Contributor, Pelican: Not run: no OpenRouter route
- Muse Spark 1.3 Contributor, Watch: Not run: no OpenRouter route


- About · 157 words