Models 44

Muse Ember

Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
14.1 s
Cost
$0.0043
Output tokens
8,446
Spent thinking
839 tokens
Speed
695.3 tok/s
Console errors
0
First token after
2.0 s
First visible text after
3.2 s
Prompt tokens
526
Code size
21.7 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"low"}
Max output
943,718 tokens
Generated
Nov 12, 2026

What it was asked

The prompt
Build a polished Breakout (brick breaker) game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A paddle controlled with the mouse and with the left/right arrow keys. - The bounce angle off the paddle depends on where the ball hits it (center = straight up, edges = sharp angle). - One level with at least 6 rows of bricks, mixing normal bricks and tougher bricks that need 2 hits and visibly crack after the first one. - Some bricks visibly carry a power-up icon; when broken they drop it, and catching it with the paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls). - The ball must never pass through bricks or the paddle. - Particles when a brick breaks. - 3 lives, score displayed. Game over and victory screens with a restart option.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 452 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEON BREAKOUT</title>
<style>
  *{margin:0;padding:0;box-sizing:border-box}
  html,body{height:100%}
  body{
    font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
    background:radial-gradient(1200px 700px at 50% -10%,#1b2350 0%,#0a0e24 55%,#050814 100%);
    color:#eef2ff;min-height:100vh;display:flex;flex-direction:column;align-items:center;
    padding:14px 12px 18px;overflow-x:hidden;
  }
  body:before{content:"";position:fixed;inset:0;pointer-events:none;opacity:.35;
    background:radial-gradient(400px 200px at 20% 20%,rgba(0,229,255,.18),transparent 60%),radial-gradient(400px 200px at 80% 30%,rgba(255,45,149,.16),transparent 60%);}
  .wrap{width:min(880px,100%);position:relative;z-index:1}
  header{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap}
  .logo{font-weight:900;letter-spacing:.14em;font-size:clamp(22px,4vw,34px);line-height:1;text-transform:uppercase}
  .logo span{background:linear-gradient(90deg,#00e5ff,#7c4dff,#ff2d95);-webkit-background-clip:text;background-clip:text;color:transparent}
  .logo small{display:block;font-size:10px;letter-spacing:.28em;color:#8ea0d6;font-weight:700;margin-top:5px;text-transform:uppercase}
  .hud{display:flex;gap:8px;flex-wrap:wrap}
  .pill{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.12);backdrop-filter:blur(6px);
    border-radius:999px;padding:7px 12px;font-size:13px;font-weight:700;min-width:92px;text-align:center}
  .pill b{font-size:16px}
  .pill label{display:block;font-size:9px;letter-spacing:.18em;color:#8ea0d6;text-transform:uppercase;font-weight:800}
  #lives{color:#ff5c8a;letter-spacing:2px;font-size:15px}
  .barWrap{height:6px;background:rgba(255,255,255,.1);border-radius:99px;overflow:hidden;margin-top:5px}
  #wideBar{height:100%;width:0%;background:linear-gradient(90deg,#00e5ff,#00ff9d);border-radius:99px;transition:width .2s linear}
  .stage{position:relative;border-radius:18px;overflow:hidden;border:1px solid rgba(255,255,255,.16);
    box-shadow:0 20px 60px rgba(0,0,0,.55),0 0 0 6px rgba(255,255,255,.03),0 0 40px rgba(80,80,255,.15);background:#070b1d}
  canvas{display:block;width:100%;height:auto;aspect-ratio:4/3;cursor:none;background:linear-gradient(180deg,#0a1030 0%,#070b1d 55%,#090a1e 100%)}
  .overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;
    padding:22px;background:radial-gradient(600px 320px at center,rgba(10,14,40,.55),rgba(5,7,20,.82));backdrop-filter:blur(2px);transition:opacity .25s}
  .overlay.hide{opacity:0;pointer-events:none}
  .overlay h1{font-size:clamp(28px,6vw,52px);letter-spacing:.12em;text-transform:uppercase;line-height:1;margin-bottom:10px}
  .overlay h1 i{font-style:normal;background:linear-gradient(90deg,#00e5ff,#ff2d95);-webkit-background-clip:text;background-clip:text;color:transparent}
  .overlay p{max-width:560px;color:#c6d0f2;font-size:clamp(13px,2vw,15px);line-height:1.55}
  .start{margin-top:16px;font-weight:900;letter-spacing:.08em;text-transform:uppercase;font-size:13px;
    background:linear-gradient(90deg,#00e5ff,#7c4dff);color:white;border:none;padding:12px 22px;border-radius:999px;cursor:pointer;
    box-shadow:0 8px 24px rgba(0,229,255,.35);animation:pulse 1.4s infinite}
  @keyframes pulse{50%{transform:scale(1.04)}}
  .legend{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;margin-top:14px;font-size:12px;color:#aebcf0}
  .legend b{color:#fff}
  .kbd{background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.2);padding:2px 7px;border-radius:6px;font-weight:800;color:#fff}
  .controls{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:10px;font-size:12px;color:#8ea0d6}
  .controls div{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);border-radius:10px;padding:8px 10px;flex:1;min-width:150px;text-align:center}
  .controls b{color:#dfe7ff}
  .msg{position:absolute;left:50%;top:62%;transform:translateX(-50%);background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.2);
    padding:8px 14px;border-radius:999px;font-size:12px;font-weight:800;letter-spacing:.06em;pointer-events:none;white-space:nowrap}
  .msg.hide{display:none}
  @media(max-width:520px){.pill{min-width:auto;flex:1}.logo small{font-size:8px}}
</style>
</head>
<body>
<div class="wrap">
  <header>
    <div class="logo">NEON <span>Breakout</span><small>Classic arcade • rebooted</small></div>
    <div class="hud">
      <div class="pill"><label>Score</label><b id="score">0</b></div>
      <div class="pill"><label>Best</label><b id="best">0</b></div>
      <div class="pill"><label>Lives</label><span id="lives">♥♥♥</span></div>
      <div class="pill"><label>Bricks</label><b id="bricksLeft">70</b></div>
      <div class="pill" style="min-width:120px"><label>Wide Paddle</label><div class="barWrap"><div id="wideBar"></div></div></div>
    </div>
  </header>

  <div class="stage" id="stage">
    <canvas id="game" width="800" height="600" aria-label="Breakout game"></canvas>
    <div class="msg hide" id="msg">CLICK / SPACE TO LAUNCH</div>
    <div class="overlay" id="overlay">
      <h1 id="ovTitle">NEON <i>BREAKOUT</i></h1>
      <p id="ovText">Break all the bricks without letting the ball fall.<br><b style="color:#fff">Demo is playing — click or press a key to take control!</b></p>
      <button class="start" id="startBtn">▶&nbsp; Click or press a key to start</button>
      <div class="legend">
        <span>◈ <b>2-hit bricks crack</b> after 1st hit</span>
        <span>⬌ <b>W</b> = Wide paddle · 10s</span>
        <span>◎ <b>M</b> = Multi-ball ×3</span>
      </div>
    </div>
  </div>

  <div class="controls">
    <div><b>Mouse</b> or <span class="kbd">◀</span> <span class="kbd">▶</span> move paddle</div>
    <div><b>Click / Space</b> launch ball &nbsp;•&nbsp; <b>P</b> pause &nbsp;•&nbsp; <b>R</b> restart</div>
    <div>Hit with paddle <b>edges</b> for sharp angles</div>
  </div>
</div>

<script>
(() => {
"use strict";
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = 800, H = 600;
const $ = id => document.getElementById(id);
const scoreEl=$('score'), bestEl=$('best'), livesEl=$('lives'), bricksEl=$('bricksLeft'), wideBar=$('wideBar');
const overlay=$('overlay'), ovTitle=$('ovTitle'), ovText=$('ovText'), startBtn=$('startBtn'), msg=$('msg');

let state = 'demo'; // demo | ready | playing | paused | over | win
let score=0, best=0, lives=3, shake=0, flash=0;
let wideUntil=0, wideActive=false;
let keys={}, mouseX=W/2, demoTimer=0;
let balls=[], particles=[], powerups=[], floaters=[];
let audioCtx=null;

const paddle = {x:W/2, y:H-28, w:110, h:14, baseW:110, speed:620};
const COLS=10, ROWS=7;
const brickW = (W-40-(COLS-1)*6)/COLS, brickH=22, brickTop=58, brickLeft=20, gap=6;
const rowColors=[['#ff2d95','#ff6bb5'],['#ff7a18','#ffb347'],['#ffe600','#fff59d'],['#00e676','#69f0ae'],['#00b0ff','#40c4ff'],['#7c4dff','#b388ff'],['#00e5ff','#84ffff']];
let bricks=[];

function beep(f,d=0.06,type='square',v=0.08){
  try{
    if(!audioCtx) audioCtx=new (window.AudioContext||window.webkitAudioContext)();
    if(audioCtx.state==='suspended') audioCtx.resume();
    const o=audioCtx.createOscillator(), g=audioCtx.createGain();
    o.type=type; o.frequency.value=f; g.gain.value=v;
    o.connect(g); g.connect(audioCtx.destination); o.start();
    g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime+d); o.stop(audioCtx.currentTime+d);
  }catch(e){}
}
function initBricks(){
  bricks=[];
  // deterministic powerup placement for a good first impression
  const powerMap = {4:'W',17:'M',26:'W',38:'M',52:'W',63:'M'};
  let idx=0;
  for(let r=0;r<ROWS;r++){
    for(let c=0;c<COLS;c++){
      // tough bricks: top rows + checker pattern, not on powerup bricks (so they drop cleanly)
      let tough = (!powerMap[idx]) && (r<2 || (r===2 && c%3===1) || (r===3 && c%4===2) || (r===4 && (c===0||c===9)));
      if(r===0 && (c===0||c===9)) tough=false;
      bricks.push({
        x:brickLeft+c*(brickW+gap), y:brickTop+r*(brickH+gap), w:brickW, h:brickH,
        hp:tough?2:1, maxHp:tough?2:1, row:r, col:c, alive:true,
        power:powerMap[idx]||null, hue:rowColors[r]
      });
      idx++;
    }
  }
}
function resetBallOnPaddle(){
  balls=[{x:paddle.x, y:paddle.y-14, vx:0, vy:0, r:8, stuck:true, trail:[]}];
  msg.classList.remove('hide'); msg.textContent='CLICK / SPACE TO LAUNCH';
}
function resetGame(){
  score=0; lives=3; wideActive=false; wideUntil=0;
  balls=[]; particles=[]; powerups=[]; floaters=[];
  paddle.w=paddle.baseW; paddle.x=W/2;
  initBricks(); resetBallOnPaddle(); updateHUD();
}
function launchBall(){
  if(state==='ready') state='playing';
  if(state!=='playing' && state!=='demo') return;
  balls.forEach(b=>{
    if(b.stuck){
      b.stuck=false;
      const ang = (Math.random()*0.6-0.3); // slight random
      const sp = 400;
      b.vx = Math.sin(ang)*sp; b.vy = -Math.cos(ang)*sp;
    }
  });
  msg.classList.add('hide');
}
function addScore(n,x,y){
  score+=n; if(score>best) best=score;
  floaters.push({x,y,text:'+'+n,life:1,vy:-40});
  updateHUD();
}
function updateHUD(){
  scoreEl.textContent=score; bestEl.textContent=best;
  livesEl.textContent='♥'.repeat(Math.max(0,lives))+'♡'.repeat(Math.max(0,3-lives));
  bricksEl.textContent=bricks.filter(b=>b.alive).length;
  wideBar.style.width = wideActive ? Math.max(0,((wideUntil-performance.now())/10000)*100)+'%' : '0%';
}
function spawnParticles(x,y,color,n=16){
  for(let i=0;i<n;i++){
    const a=Math.random()*Math.PI*2, s=60+Math.random()*240;
    particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-40,life:0.5+Math.random()*0.5,max:1,size:2+Math.random()*4,color});
  }
}
function spawnPowerup(x,y,type){
  powerups.push({x,y,vy:150,w:42,h:20,type,rot:0});
}
function breakBrick(b){
  b.alive=false;
  const cx=b.x+b.w/2, cy=b.y+b.h/2;
  spawnParticles(cx,cy,b.hue[0],22); spawnParticles(cx,cy,b.hue[1],10);
  shake=Math.min(7,shake+2); flash=0.12;
  addScore(b.maxHp===2?100:50,cx,cy);
  beep(520+Math.random()*120,0.07,'square',0.07);
  if(b.power) spawnPowerup(cx,cy,b.power);
  updateHUD();
  if(bricks.every(x=>!x.alive)){
    state='win'; beep(880,0.2,'sine',0.1);
    showOverlay('VICTORY!','You smashed every brick! 🎉<br>Final score: <b style="color:#fff">'+score+'</b> — the neon wall is yours.', '↻&nbsp; Play again');
  }
}
function hitBrick(b, ball, prevX, prevY){
  if(b.hp>1){
    b.hp--; addScore(20, ball.x, ball.y-10);
    spawnParticles(ball.x,ball.y,'#ffffff',10); beep(300,0.06,'triangle',0.09);
    // crack particles
    spawnParticles(b.x+b.w/2,b.y+b.h/2,'#dbeafe',6);
  } else {
    breakBrick(b);
  }
}
// Collision helpers: substep to prevent tunneling
function moveBall(b, dt){
  const dist=Math.hypot(b.vx,b.vy)*dt;
  const steps=Math.max(1, Math.ceil(dist/3));
  const sdt=dt/steps;
  for(let s=0;s<steps;s++){
    const prevX=b.x, prevY=b.y;
    b.x+=b.vx*sdt; b.y+=b.vy*sdt;
    // walls
    if(b.x<b.r){b.x=b.r; b.vx=Math.abs(b.vx); beep(220,0.04,'square',0.04);}
    if(b.x>W-b.r){b.x=W-b.r; b.vx=-Math.abs(b.vx); beep(220,0.04,'square',0.04);}
    if(b.y<b.r){b.y=b.r; b.vy=Math.abs(b.vy); beep(220,0.04,'square',0.04);}
    // paddle swept check
    if(b.vy>0 && b.y+b.r>=paddle.y && b.y-b.r<=paddle.y+paddle.h && b.x>=paddle.x-paddle.w/2-b.r && b.x<=paddle.x+paddle.w/2+b.r){
      const rel=(b.x-paddle.x)/(paddle.w/2); // -1..1
      const clamped=Math.max(-1,Math.min(1,rel));
      const maxAng=65*Math.PI/180;
      const ang=clamped*maxAng;
      const sp=Math.min(560, Math.hypot(b.vx,b.vy)+4);
      b.vx=Math.sin(ang)*sp; b.vy=-Math.cos(ang)*sp;
      b.y=paddle.y-b.r-1;
      spawnParticles(b.x,paddle.y,'#00e5ff',5); beep(340,0.05,'sine',0.08);
    }
    // bricks
    for(const br of bricks){
      if(!br.alive) continue;
      if(b.x+b.r>br.x && b.x-b.r<br.x+br.w && b.y+b.r>br.y && b.y-b.r<br.y+br.h){
        // determine bounce axis by previous position
        const overlapLeft = (b.x+b.r)-br.x, overlapRight=(br.x+br.w)-(b.x-b.r);
        const overlapTop=(b.y+b.r)-br.y, overlapBottom=(br.y+br.h)-(b.y-b.r);
        const minX=Math.min(overlapLeft,overlapRight), minY=Math.min(overlapTop,overlapBottom);
        if(minX<minY){
          b.vx = b.x < br.x+br.w/2 ? -Math.abs(b.vx) : Math.abs(b.vx);
          b.x += b.x < br.x+br.w/2 ? -minX-1 : minX+1;
        } else {
          b.vy = b.y < br.y+br.h/2 ? -Math.abs(b.vy) : Math.abs(b.vy);
          b.y += b.y < br.y+br.h/2 ? -minY-1 : minY+1;
        }
        hitBrick(br,b,prevX,prevY);
        break; // one brick per substep
      }
    }
  }
  b.trail.push({x:b.x,y:b.y}); if(b.trail.length>10) b.trail.shift();
}
function catchPowerup(p){
  addScore(250,p.x,p.y);
  if(p.type==='W'){
    wideActive=true; wideUntil=performance.now()+10000; paddle.w=paddle.baseW*1.65;
    floaters.push({x:p.x,y:p.y-14,text:'WIDE PADDLE!',life:1.4,vy:-30,color:'#00ff9d'});
    beep(660,0.15,'sine',0.1);
  } else if(p.type==='M'){
    // ensure 3 balls
    const base=balls[0]||{x:paddle.x,y:paddle.y-20,vx:120,vy:-380,r:8};
    while(balls.length<3){
      const off=(balls.length-1)*0.35;
      const sp=400;
      balls.push({x:base.x,y:base.y,vx:Math.sin(off)*sp,vy:-Math.cos(off)*sp,r:8,stuck:false,trail:[]});
    }
    floaters.push({x:p.x,y:p.y-14,text:'MULTI-BALL!',life:1.4,vy:-30,color:'#ff2d95'});
    beep(880,0.15,'sine',0.1);
  }
  spawnParticles(p.x,p.y,p.type==='W'?'#00ff9d':'#ff2d95',18);
}
function gameOver(){
  state='over'; beep(160,0.4,'sawtooth',0.1);
  showOverlay('GAME OVER','The ball slipped past you…<br>Final score: <b style="color:#fff">'+score+'</b> &nbsp;•&nbsp; Best: <b style="color:#fff">'+best+'</b>','↻&nbsp; Try again');
}
function showOverlay(title, text, btn){
  ovTitle.innerHTML=title; ovText.innerHTML=text; startBtn.innerHTML=btn;
  overlay.classList.remove('hide');
}
function hideOverlay(){ overlay.classList.add('hide'); }

function startHuman(){
  if(state==='demo'){ resetGame(); state='ready'; hideOverlay(); }
  else if(state==='over'||state==='win'){ resetGame(); state='ready'; hideOverlay(); }
  else if(state==='ready'){ launchBall(); }
  else if(state==='paused'){ state='playing'; hideOverlay(); }
  else if(state==='playing'){ launchBall(); }
}
startBtn.addEventListener('click', e=>{e.stopPropagation(); startHuman();});
overlay.addEventListener('click', ()=>{ if(state!=='paused') startHuman(); });

canvas.addEventListener('mousemove', e=>{
  const r=canvas.getBoundingClientRect();
  mouseX=(e.clientX-r.left)/r.width*W;
  paddle.x=mouseX;
});
canvas.addEventListener('click', ()=>{ if(state==='demo') startHuman(); else launchBall(); });
canvas.addEventListener('touchmove', e=>{
  e.preventDefault();
  const r=canvas.getBoundingClientRect();
  mouseX=(e.touches[0].clientX-r.left)/r.width*W; paddle.x=mouseX;
},{passive:false});
canvas.addEventListener('touchstart', e=>{ if(state==='demo') startHuman(); else launchBall(); },{passive:true});

window.addEventListener('keydown', e=>{
  if(['ArrowLeft','ArrowRight',' '].includes(e.key)) e.preventDefault();
  keys[e.key]=true;
  if(e.key==='p'||e.key==='P'){
    if(state==='playing'){state='paused'; showOverlay('PAUSED','Press P or click to resume.','▶&nbsp; Resume');}
    else if(state==='paused'){state='playing'; hideOverlay();}
    return;
  }
  if(e.key==='r'||e.key==='R'){ if(state==='over'||state==='win'||state==='paused') startHuman(); return; }
  if(e.key===' '||e.key==='ArrowUp'||e.key==='Enter'){ startHuman(); }
});
window.addEventListener('keyup', e=>{ keys[e.key]=false; });

let last=performance.now();
function update(now){
  const dt=Math.min(0.033,(now-last)/1000); last=now;
  // paddle keyboard
  if(state==='playing'||state==='ready'||state==='demo'){
    if(keys['ArrowLeft']||keys['a']||keys['A']) paddle.x-=paddle.speed*dt;
    if(keys['ArrowRight']||keys['d']||keys['D']) paddle.x+=paddle.speed*dt;
    paddle.x=Math.max(paddle.w/2,Math.min(W-paddle.w/2,paddle.x));
  }
  if(state==='demo'){
    demoTimer+=dt;
    const target=balls[0];
    if(target){
      if(target.stuck && demoTimer>1.2){ launchBall(); }
      const desired=target.x + (target.vx*0.35);
      paddle.x += (desired-paddle.x)*Math.min(1,dt*7);
      paddle.x=Math.max(paddle.w/2,Math.min(W-paddle.w/2,paddle.x));
    }
  }
  if(state==='playing'||state==='demo'){
    // wide expiry
    if(wideActive && now>wideUntil){ wideActive=false; paddle.w=paddle.baseW; updateHUD(); }
    if(wideActive) updateHUD();
    balls.forEach(b=>{ if(!b.stuck) moveBall(b,dt); else {b.x=paddle.x; b.y=paddle.y-14;} });
    // remove fallen
    balls=balls.filter(b=>b.y<H+20);
    if(balls.length===0){
      lives--; beep(140,0.3,'sawtooth',0.1); shake=6; updateHUD();
      if(lives<=0 && state==='playing'){ gameOver(); }
      else if(lives<=0 && state==='demo'){ lives=3; resetGame(); }
      else { resetBallOnPaddle(); if(state==='playing') state='ready'; }
    }
    powerups.forEach(p=>{ p.y+=p.vy*dt; p.rot+=dt*3; });
    powerups=powerups.filter(p=>{
      if(p.y>H+20) return false;
      if(p.y+p.h/2>=paddle.y && p.y-p.h/2<=paddle.y+paddle.h && Math.abs(p.x-paddle.x)<=paddle.w/2+p.w/2){
        catchPowerup(p); return false;
      }
      return true;
    });
  }
  particles.forEach(p=>{p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=300*dt;p.life-=dt;});
  particles=particles.filter(p=>p.life>0);
  floaters.forEach(f=>{f.y+=f.vy*dt;f.life-=dt;});
  floaters=floaters.filter(f=>f.life>0);
  if(shake>0) shake*=0.85; if(shake<0.3) shake=0;
  if(flash>0) flash-=dt;
}
function drawBrick(b){
  const g=ctx.createLinearGradient(b.x,b.y,b.x,b.y+b.h);
  g.addColorStop(0,b.hue[1]); g.addColorStop(1,b.hue[0]);
  ctx.fillStyle=g;
  roundRect(b.x,b.y,b.w,b.h,5); ctx.fill();
  ctx.strokeStyle='rgba(255,255,255,.35)'; ctx.lineWidth=1; roundRect(b.x,b.y,b.w,b.h,5); ctx.stroke();
  ctx.fillStyle='rgba(255,255,255,.18)'; roundRect(b.x+3,b.y+3,b.w-6,6,3); ctx.fill();
  if(b.maxHp===2){
    ctx.fillStyle='rgba(0,0,0,.85)'; ctx.font='900 9px system-ui'; ctx.textAlign='center';
    ctx.fillText('◆ 2', b.x+b.w-13, b.y+10);
    if(b.hp===1){
      ctx.strokeStyle='rgba(0,0,0,.65)'; ctx.lineWidth=1.6; ctx.beginPath();
      ctx.moveTo(b.x+8,b.y+4); ctx.lineTo(b.x+b.w*0.4,b.y+b.h*0.5); ctx.lineTo(b.x+b.w*0.3,b.y+b.h-4);
      ctx.moveTo(b.x+b.w*0.65,b.y+3); ctx.lineTo(b.x+b.w*0.5,b.y+b.h*0.55); ctx.lineTo(b.x+b.w*0.8,b.y+b.h-3);
      ctx.moveTo(b.x+b.w*0.4,b.y+b.h*0.5); ctx.lineTo(b.x+b.w*0.55,b.y+b.h*0.45);
      ctx.stroke();
      ctx.fillStyle='rgba(255,255,255,.9)'; ctx.font='800 8px system-ui'; ctx.fillText('CRACKED',b.x+b.w/2,b.y+b.h-5);
    }
  }
  if(b.power){
    const cx=b.x+b.w/2, cy=b.y+b.h/2;
    ctx.save(); ctx.shadowColor=b.power==='W'?'#00ff9d':'#ff2d95'; ctx.shadowBlur=8;
    ctx.fillStyle=b.power==='W'?'#062e22':'#2e0620'; ctx.strokeStyle=b.power==='W'?'#00ff9d':'#ff2d95'; ctx.lineWidth=1.5;
    roundRect(cx-11,cy-8,22,16,8); ctx.fill(); ctx.stroke();
    ctx.fillStyle=b.power==='W'?'#00ff9d':'#ff6bb5'; ctx.font='900 12px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
    ctx.fillText(b.power, cx, cy+1); ctx.restore(); ctx.textBaseline='alphabetic';
  }
}
function roundRect(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 draw(){
  ctx.save();
  if(shake) ctx.translate((Math.random()-0.5)*shake,(Math.random()-0.5)*shake);
  // background dots
  ctx.fillStyle='rgba(255,255,255,.05)';
  for(let x=20;x<W;x+=40) for(let y=20;y<H;y+=40) ctx.fillRect(x,y,2,2);
  // bricks
  bricks.forEach(b=>{ if(b.alive) drawBrick(b); });
  // powerups
  powerups.forEach(p=>{
    ctx.save(); ctx.translate(p.x,p.y);
    ctx.shadowColor=p.type==='W'?'#00ff9d':'#ff2d95'; ctx.shadowBlur=14;
    ctx.fillStyle=p.type==='W'?'#0a2e22':'#2e0a22'; ctx.strokeStyle=p.type==='W'?'#00ff9d':'#ff2d95'; ctx.lineWidth=2;
    roundRect(-p.w/2,-p.h/2,p.w,p.h,10); ctx.fill(); ctx.stroke();
    ctx.fillStyle=p.type==='W'?'#00ff9d':'#ff6bb5'; ctx.font='900 14px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
    ctx.fillText(p.type==='W'?'⬌ W':'◎ M',0,1); ctx.restore();
  });
  // paddle
  ctx.save();
  ctx.shadowColor='#00e5ff'; ctx.shadowBlur=16;
  const pg=ctx.createLinearGradient(paddle.x-paddle.w/2,0,paddle.x+paddle.w/2,0);
  pg.addColorStop(0,'#00b0ff'); pg.addColorStop(.5,'#00e5ff'); pg.addColorStop(1,'#7c4dff');
  ctx.fillStyle=pg; roundRect(paddle.x-paddle.w/2,paddle.y,paddle.w,paddle.h,7); ctx.fill();
  ctx.fillStyle='rgba(255,255,255,.7)'; roundRect(paddle.x-paddle.w/2+6,paddle.y+3,paddle.w-12,4,2); ctx.fill();
  ctx.restore();
  // balls
  balls.forEach(b=>{
    b.trail.forEach((t,i)=>{ ctx.globalAlpha=i/b.trail.length*0.25; ctx.fillStyle='#00e5ff'; ctx.beginPath(); ctx.arc(t.x,t.y,b.r*(i/b.trail.length),0,Math.PI*2); ctx.fill(); });
    ctx.globalAlpha=1; ctx.save(); ctx.shadowColor='#fff'; ctx.shadowBlur=16;
    ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#00e5ff'; ctx.beginPath(); ctx.arc(b.x-2,b.y-2,3,0,Math.PI*2); ctx.fill(); ctx.restore();
  });
  // particles
  particles.forEach(p=>{ ctx.globalAlpha=Math.max(0,p.life/p.max); ctx.fillStyle=p.color; ctx.fillRect(p.x-p.size/2,p.y-p.size/2,p.size,p.size); });
  ctx.globalAlpha=1;
  // floaters
  floaters.forEach(f=>{ ctx.globalAlpha=Math.min(1,f.life); ctx.fillStyle=f.color||'#fff'; ctx.font='900 14px system-ui'; ctx.textAlign='center'; ctx.fillText(f.text,f.x,f.y); });
  ctx.globalAlpha=1;
  if(flash>0){ ctx.fillStyle=`rgba(255,255,255,${flash*1.2})`; ctx.fillRect(0,0,W,H); }
  if(state==='paused'){ ctx.fillStyle='rgba(0,0,0,.25)'; ctx.fillRect(0,0,W,H); }
  ctx.restore();
}
function loop(now){ update(now); draw(); requestAnimationFrame(loop); }

resetGame();
state='demo';
// demo starts with ball already flying for a lively screenshot
balls[0].stuck=false; balls[0].vx=120; balls[0].vy=-380; msg.classList.add('hide');
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
Reasoning trace (839 tokens)
Implementing Breakout with substep physics, AI demo mode, powerups, particles, and responsive canvas layout.Adding HUD, game states, AI paddle logic, substep collision, multiball and wide powerup mechanics, and responsive canvas rendering.