hypeorcode
Network offOpen alone (new tab)

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)

Raw files: output · meta.json

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">▶ &nbsp;START GAME</button>
      <div id="hint">P pause &nbsp;•&nbsp; M mute &nbsp;•&nbsp; 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> &nbsp; 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.',
    '▶ &nbsp;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.',
    '↻ &nbsp;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.',
      '↻ &nbsp;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.','▶ &nbsp;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