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
41.6 s
Cost
$0.011
Output tokens
22,277
Spent thinking
12,000 tokens
Speed
577.9 tok/s
Console errors
0
First token after
3.1 s
First visible text after
30.1 s
Prompt tokens
526
Code size
28.7 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"max"}
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 · 387 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout — Neon Brick Breaker</title>
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  html,body{height:100%}
  body{
    font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
    background:
      radial-gradient(900px 500px at 20% -10%,rgba(59,130,246,.35),transparent 60%),
      radial-gradient(800px 500px at 90% 0%,rgba(236,72,153,.22),transparent 55%),
      linear-gradient(180deg,#070b1d 0%,#0b1026 55%,#070a18 100%);
    color:#e2e8f0;min-height:100vh;padding:16px 14px 18px;
  }
  .wrap{max-width:960px;margin:0 auto}
  .topbar{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:12px}
  .logo{font-size:32px;font-weight:900;letter-spacing:.06em;line-height:1}
  .logo span{background:linear-gradient(90deg,#22d3ee,#a78bfa,#f472b6);-webkit-background-clip:text;background-clip:text;color:transparent}
  .subtitle{font-size:11px;letter-spacing:.22em;color:#94a3b8;font-weight:700;margin-top:4px}
  .hud{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
  .pill{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.11);border-radius:999px;padding:7px 12px;font-size:11px;letter-spacing:.12em;font-weight:800;color:#94a3b8;display:flex;align-items:center;gap:7px}
  .pill b{color:#fff;font-size:15px;letter-spacing:0;font-variant-numeric:tabular-nums}
  .pill .hearts{color:#ff4d6d;font-size:16px;letter-spacing:2px}
  .pill .hearts .dim{color:#334155}
  #widePill{display:none;color:#fde68a;border-color:rgba(251,191,36,.35);background:rgba(251,191,36,.10)}
  #widePill b{color:#fde68a;font-size:12px}
  .bar{width:62px;height:7px;background:rgba(255,255,255,.14);border-radius:999px;overflow:hidden}
  .bar i{display:block;height:100%;width:100%;background:linear-gradient(90deg,#f59e0b,#fde68a);border-radius:999px}
  .frame{position:relative;border-radius:20px;overflow:hidden;border:1px solid rgba(255,255,255,.14);box-shadow:0 24px 60px rgba(0,0,0,.55),0 0 0 1px rgba(34,211,238,.08),0 0 42px rgba(59,130,246,.18);background:#0a1128}
  canvas{display:block;width:100%;height:auto;aspect-ratio:3/2;touch-action:none;cursor:none;background:#0a1128}
  #toast{position:absolute;top:14px;left:50%;transform:translateX(-50%) translateY(-8px);opacity:0;transition:.25s;pointer-events:none;z-index:6;background:linear-gradient(90deg,#0891b2,#7c3aed);color:#fff;font-weight:900;font-size:13px;letter-spacing:.06em;padding:9px 16px;border-radius:999px;box-shadow:0 10px 24px rgba(0,0,0,.35);white-space:nowrap}
  #toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
  #overlay{position:absolute;inset:0;z-index:5;display:flex;pointer-events:auto}
  #overlay.demo{align-items:flex-end;padding:14px;background:linear-gradient(180deg,rgba(2,6,23,0) 52%,rgba(2,6,23,.78) 100%)}
  #overlay.center{align-items:center;justify-content:center;padding:18px;background:rgba(2,6,23,.64);backdrop-filter:blur(3px)}
  .demoCard{width:100%;display:flex;justify-content:space-between;align-items:center;gap:14px;background:rgba(7,13,32,.82);border:1px solid rgba(255,255,255,.14);border-radius:16px;padding:13px 15px;backdrop-filter:blur(8px)}
  .demoCard h1{font-size:26px;letter-spacing:.05em;line-height:1}
  .demoCard h1 span{background:linear-gradient(90deg,#22d3ee,#a78bfa,#f472b6);-webkit-background-clip:text;background-clip:text;color:transparent}
  .badge{display:inline-block;font-size:10px;letter-spacing:.18em;font-weight:900;color:#67e8f9;background:rgba(34,211,238,.12);border:1px solid rgba(34,211,238,.35);padding:4px 8px;border-radius:999px;margin-bottom:6px}
  .demoCard p{font-size:12.5px;color:#cbd5e1;line-height:1.45;margin-top:5px}
  .demoCard p.hint{color:#94a3b8;font-size:11.5px}
  .card{background:linear-gradient(180deg,rgba(15,23,42,.97),rgba(7,13,32,.97));border:1px solid rgba(255,255,255,.16);border-radius:20px;padding:26px 24px;text-align:center;max-width:420px;width:100%;box-shadow:0 24px 60px rgba(0,0,0,.5)}
  .card .emoji{font-size:42px;line-height:1}
  .card h2{font-size:36px;letter-spacing:.05em;margin:10px 0 6px}
  .card h2 span{background:linear-gradient(90deg,#22d3ee,#a78bfa);-webkit-background-clip:text;background-clip:text;color:transparent}
  .card p{color:#cbd5e1;font-size:14px;line-height:1.55}
  .card .final{font-size:15px;margin:12px 0;color:#fff}
  .card .final b{color:#fde68a;font-size:22px}
  .btn{appearance:none;border:0;cursor:pointer;font:inherit;font-weight:900;letter-spacing:.04em;border-radius:999px;padding:12px 22px;color:#fff;background:linear-gradient(90deg,#06b6d4,#8b5cf6);box-shadow:0 10px 24px rgba(8,145,178,.35);transition:transform .12s,filter .12s}
  .btn:hover{transform:translateY(-1px);filter:brightness(1.08)}
  .btn:active{transform:translateY(0) scale(.98)}
  .btn.secondary{background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.16);box-shadow:none;color:#e2e8f0}
  .controls{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-top:12px}
  .btnRow{display:flex;gap:8px;flex-wrap:wrap}
  .smallBtn{padding:9px 15px;font-size:13px}
  .hints{font-size:12.5px;color:#94a3b8;line-height:1.5}
  .hints b{color:#e2e8f0}
  .legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:10px;font-size:11.5px;color:#94a3b8;align-items:center}
  .legend .dot{display:inline-block;width:12px;height:12px;border-radius:4px;vertical-align:-2px;margin-right:5px}
  .dot.normal{background:linear-gradient(180deg,#feca57,#ff9f43)}
  .dot.tough{background:linear-gradient(180deg,#94a3b8,#475569)}
  .dot.wide{background:#f59e0b;border-radius:999px}
  .dot.multi{background:#2dd4bf;border-radius:999px}
  @media(max-width:640px){
    body{padding:10px 8px 14px}
    .logo{font-size:26px}
    .pill{padding:6px 10px}
    .demoCard{flex-direction:column;align-items:stretch;text-align:center}
    .demoCard .btn{width:100%}
    .demoCard h1{font-size:22px}
    .hints{width:100%}
  }
</style>
</head>
<body>
<div class="wrap">
  <header class="topbar">
    <div>
      <div class="logo">BREAK<span>OUT</span></div>
      <div class="subtitle">NEON BRICK BREAKER</div>
    </div>
    <div class="hud" aria-label="Scoreboard">
      <div class="pill">SCORE <b id="score">0</b></div>
      <div class="pill">BEST <b id="hi">0</b></div>
      <div class="pill">LIVES <span class="hearts" id="lives">♥♥♥</span></div>
      <div class="pill">BRICKS <b id="bricksLeft">70</b></div>
      <div class="pill" id="widePill">WIDE <span class="bar"><i id="wideBar"></i></span> <b id="wideTime">10s</b></div>
    </div>
  </header>

  <div class="frame" id="frame">
    <canvas id="game" width="900" height="600" aria-label="Breakout game canvas"></canvas>
    <div id="toast"></div>
    <div id="overlay" class="demo"></div>
  </div>

  <div class="controls">
    <div class="btnRow">
      <button class="btn secondary smallBtn" id="pauseBtn" type="button">⏸ Pause</button>
      <button class="btn secondary smallBtn" id="restartBtn" type="button">↺ Restart</button>
      <button class="btn secondary smallBtn" id="soundBtn" type="button">🔊 Sound</button>
    </div>
    <div class="hints"><b>Mouse / Touch</b> or <b>← →</b> move &nbsp;•&nbsp; <b>Space / Click</b> launch &nbsp;•&nbsp; <b>P</b> pause &nbsp;•&nbsp; <b>R</b> restart</div>
  </div>
  <div class="legend">
    <span><i class="dot normal"></i>Normal brick</span>
    <span><i class="dot tough"></i>Steel brick — 2 hits, cracks on hit 1</span>
    <span><i class="dot wide"></i><b>W</b> Wide paddle · 10s</span>
    <span><i class="dot multi"></i><b>×3</b> Multi-ball</span>
  </div>
</div>

<script>
"use strict";
const canvas=document.getElementById('game');
const ctx=canvas.getContext('2d');
const overlay=document.getElementById('overlay');
const toastEl=document.getElementById('toast');
const scoreEl=document.getElementById('score');
const hiEl=document.getElementById('hi');
const livesEl=document.getElementById('lives');
const bricksEl=document.getElementById('bricksLeft');
const widePill=document.getElementById('widePill');
const wideBar=document.getElementById('wideBar');
const wideTime=document.getElementById('wideTime');
const pauseBtn=document.getElementById('pauseBtn');
const restartBtn=document.getElementById('restartBtn');
const soundBtn=document.getElementById('soundBtn');

const W=900,H=600,BALL_R=8,BASE_SPEED=440,MAX_SPEED=545;
const rowColors=['#ff5d5d','#ff9f43','#feca57','#1dd1a1','#54a0ff','#9b6bff','#ff6bb5'];
const toughPattern=[
 [0,0,0,1,0,0,0,1,0,0],
 [0,1,0,0,1,1,0,0,1,0],
 [1,0,0,0,0,0,0,0,0,1],
 [0,0,1,0,0,0,1,0,0,0],
 [0,1,0,0,0,0,0,0,1,0],
 [1,0,0,1,0,0,1,0,0,1],
 [0,0,1,0,0,0,0,1,0,0]
];
const powerMap={"0,4":"multi","1,2":"wide","2,5":"multi","3,7":"wide","4,3":"multi","5,5":"wide","6,4":"multi"};

let state='demo'; // demo | playing | paused | over | win
let score=0,hi=0,lives=3,bricksLeft=0,currentSpeed=BASE_SPEED,time=0,shake=0,pendingReset=false;
let demoLaunchTimer=0,soundOn=true,audioCtx=null,toastTimer=null;
let paddle={x:W/2-56,w:112,baseW:112,h:14,y:H-32,wideTimer:0};
let balls=[],bricks=[],particles=[],powerups=[],floats=[];
let keys={},mouseX=null,mouseActive=false;
const stars=[]; for(let i=0;i<80;i++) stars.push({x:Math.random()*W,y:Math.random()*H,r:Math.random()*1.6+0.4,a:Math.random()*0.5+0.12,s:Math.random()*6.28});

function clamp(v,a,b){return v<a?a:v>b?b:v;}
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 ensureAudio(){ if(!audioCtx){ try{audioCtx=new (window.AudioContext||window.webkitAudioContext)();}catch(e){} } if(audioCtx&&audioCtx.state==='suspended') audioCtx.resume(); }
function beep(freq,dur,type,vol){ if(!soundOn||!audioCtx) return; try{ const o=audioCtx.createOscillator(),g=audioCtx.createGain(); o.type=type||'square'; o.frequency.value=freq; g.gain.value=vol||0.045; g.gain.exponentialRampToValueAtTime(0.0001,audioCtx.currentTime+(dur||0.07)); o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime+(dur||0.07)); }catch(e){} }

function buildBricks(){
  bricks=[]; const cols=10,rows=7,gap=8,margin=22,top=62,bh=26;
  const bw=(W-margin*2-gap*(cols-1))/cols;
  for(let r=0;r<rows;r++) for(let c=0;c<cols;c++){
    const tough=!!toughPattern[r][c];
    const power=powerMap[r+","+c]||null;
    bricks.push({x:margin+c*(bw+gap),y:top+r*(bh+gap),w:bw,h:bh,row:r,col:c,hp:tough?2:1,maxHp:tough?2:1,tough: tough,power:power,color:rowColors[r],alive:true,flash:0});
  }
  bricksLeft=bricks.length;
}
function makeStuckBall(){ return {x:paddle.x+paddle.w/2,y:paddle.y-BALL_R-2,vx:0,vy:0,stuck:true,trail:[]}; }
function initLevel(){
  score=0;lives=3;currentSpeed=BASE_SPEED;shake=0;pendingReset=false;
  paddle.w=paddle.baseW;paddle.wideTimer=0;paddle.x=W/2-paddle.w/2;
  particles=[];powerups=[];floats=[];buildBricks();balls=[makeStuckBall()];demoLaunchTimer=0;updateHUD();
}
function resetDemo(){ initLevel(); state='demo'; renderOverlay(); }
function startGame(){ ensureAudio(); initLevel(); state='playing'; renderOverlay(); launchBall(); showToast('GO! BREAK ALL BRICKS'); }
function launchBall(){
  const b=balls.find(x=>x.stuck); if(!b) return;
  const ang=(Math.random()*0.7-0.35); b.vx=Math.sin(ang)*currentSpeed; b.vy=-Math.cos(ang)*currentSpeed; b.stuck=false; beep(520,0.08,'square',0.05);
}
function showToast(txt){ toastEl.textContent=txt; toastEl.classList.add('show'); if(toastTimer) clearTimeout(toastTimer); toastTimer=setTimeout(()=>toastEl.classList.remove('show'),2100); }
function togglePause(){
  if(state==='playing'){ state='paused'; renderOverlay(); }
  else if(state==='paused'){ state='playing'; renderOverlay(); }
}
function gameOver(){ if(score>hi)hi=score; state='over'; renderOverlay(); beep(140,0.35,'sawtooth',0.06); }
function victory(){ if(score>hi)hi=score; state='win'; renderOverlay(); beep(880,0.15,'square',0.05); setTimeout(()=>beep(1100,0.18,'square',0.05),120);
  for(let i=0;i<90;i++){ particles.push({x:Math.random()*W,y:Math.random()*180,vx:(Math.random()-0.5)*360,vy:Math.random()*220-40,life:1.2+Math.random()*0.8,max:2,size:2+Math.random()*3,color:rowColors[i%rowColors.length]}); }
}
function renderOverlay(){
  if(state==='demo'){
    overlay.className='demo'; overlay.style.display='flex';
    overlay.innerHTML='<div class="demoCard"><div><span class="badge">● DEMO MODE — AI IS PLAYING</span><h1>BREAK<span>OUT</span></h1><p>Catch the <b>W</b> for a wider paddle and <b>×3</b> for multi-ball. Steel bricks crack after one hit.</p><p class="hint">Click or press any key to start &nbsp;•&nbsp; Mouse / ← → to move &nbsp;•&nbsp; Space to launch</p></div><button class="btn" data-action="start" type="button">▶ Start Game</button></div>';
  } else if(state==='paused'){
    overlay.className='center'; overlay.style.display='flex';
    overlay.innerHTML='<div class="card"><div class="emoji">⏸️</div><h2>PAUSED</h2><p>Take a breath. The bricks aren\'t going anywhere.</p><p class="final">Score <b>'+score+'</b></p><button class="btn" data-action="resume" type="button">▶ Resume</button> <button class="btn secondary" data-action="restart" type="button">↺ Restart</button></div>';
  } else if(state==='over'){
    overlay.className='center'; overlay.style.display='flex';
    overlay.innerHTML='<div class="card"><div class="emoji">💥</div><h2>GAME <span>OVER</span></h2><p>Out of lives — but every brick you cracked still counts.</p><p class="final">Final score <b>'+score+'</b> &nbsp;•&nbsp; Best '+hi+'</p><button class="btn" data-action="restart" type="button">↺ Play Again</button></div>';
  } else if(state==='win'){
    overlay.className='center'; overlay.style.display='flex';
    overlay.innerHTML='<div class="card"><div class="emoji">🏆</div><h2>VICTORY!</h2><p>You smashed every brick. Clean break!</p><p class="final">Final score <b>'+score+'</b> &nbsp;•&nbsp; Best '+hi+'</p><button class="btn" data-action="restart" type="button">↺ Play Again</button></div>';
  } else { overlay.style.display='none'; overlay.innerHTML=''; }
  pauseBtn.textContent = state==='paused' ? '▶ Resume' : '⏸ Pause';
}
overlay.addEventListener('click',e=>{
  const a=e.target.closest('[data-action]'); const act=a?a.dataset.action:null;
  if(state==='demo'){ startGame(); return; }
  if(act==='start'||act==='restart'){ startGame(); return; }
  if(act==='resume'){ togglePause(); return; }
});
canvas.addEventListener('click',()=>{ if(state==='playing') launchBall(); else if(state==='paused') togglePause(); });
canvas.addEventListener('mousemove',e=>{ const r=canvas.getBoundingClientRect(); mouseX=(e.clientX-r.left)*(W/r.width); mouseActive=true; });
canvas.addEventListener('touchstart',e=>{ e.preventDefault(); const t=e.touches[0],r=canvas.getBoundingClientRect(); mouseX=(t.clientX-r.left)*(W/r.width); mouseActive=true; if(state==='playing') launchBall(); },{passive:false});
canvas.addEventListener('touchmove',e=>{ e.preventDefault(); const t=e.touches[0],r=canvas.getBoundingClientRect(); mouseX=(t.clientX-r.left)*(W/r.width); mouseActive=true; },{passive:false});
window.addEventListener('keydown',e=>{
  keys[e.code]=true;
  if(['ArrowLeft','ArrowRight','Space'].includes(e.code)) e.preventDefault();
  if(e.code==='ArrowLeft'||e.code==='ArrowRight'||e.code==='KeyA'||e.code==='KeyD') mouseActive=false;
  if(state==='demo'){ startGame(); return; }
  if(state==='over'||state==='win'){ if(e.code==='Enter'||e.code==='Space'||e.code==='KeyR') startGame(); return; }
  if(e.code==='KeyP'||e.code==='Escape'){ togglePause(); return; }
  if(e.code==='Space'){ if(state==='playing') launchBall(); else if(state==='paused') togglePause(); return; }
  if(e.code==='KeyR'&&state==='playing') startGame();
});
window.addEventListener('keyup',e=>{ keys[e.code]=false; });
pauseBtn.addEventListener('click',()=>{ ensureAudio(); if(state==='demo') startGame(); else togglePause(); });
restartBtn.addEventListener('click',()=>{ ensureAudio(); startGame(); });
soundBtn.addEventListener('click',()=>{ soundOn=!soundOn; soundBtn.textContent=soundOn?'🔊 Sound':'🔇 Muted'; if(soundOn){ensureAudio();beep(660,0.07);} });

function setPaddleWidth(newW){ const cx=paddle.x+paddle.w/2; paddle.w=newW; paddle.x=clamp(cx-paddle.w/2,0,W-paddle.w); }
function spawnParticles(b){
  const n=b.tough?14:18; const col=b.tough?'#cbd5e1':b.color;
  for(let i=0;i<n;i++){ const a=Math.random()*6.283,sp=70+Math.random()*240; particles.push({x:b.x+b.w/2,y:b.y+b.h/2,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-60,life:0.45+Math.random()*0.5,max:0.95,size:2+Math.random()*3.2,color:col}); }
}
function addFloat(x,y,text,color){ floats.push({x:x,y:y,text:text,life:0.9,max:0.9,color:color||'#fff'}); }
function damageBrick(b){
  b.hp--; b.flash=0.12;
  if(b.hp>0){ score+=10; addFloat(b.x+b.w/2,b.y+b.h/2,'CRACK!','#fde68a'); beep(210,0.06,'square',0.05); spawnParticles(b); return; }
  b.alive=false; bricksLeft--; const pts=b.tough?100:50; score+=pts;
  addFloat(b.x+b.w/2,b.y+b.h/2,'+'+pts,b.tough?'#fde68a':'#fff');
  spawnParticles(b); shake=Math.min(5,shake+2.2); beep(b.tough?520:700,0.06,'square',0.05);
  currentSpeed=Math.min(MAX_SPEED,currentSpeed+2);
  balls.forEach(bl=>{ if(!bl.stuck){ const m=Math.hypot(bl.vx,bl.vy)||1; bl.vx=bl.vx/m*currentSpeed; bl.vy=bl.vy/m*currentSpeed; }});
  if(b.power){ powerups.push({x:b.x+b.w/2,baseX:b.x+b.w/2,y:b.y+b.h/2,type:b.power,t:0}); beep(900,0.09,'sine',0.05); }
  if(bricksLeft<=0){ if(state==='demo') pendingReset=true; else victory(); }
}
function applyPowerup(type){
  beep(980,0.12,'sine',0.06);
  if(type==='wide'){ paddle.wideTimer=10; setPaddleWidth(172); showToast('✦ WIDE PADDLE — 10s'); addFloat(paddle.x+paddle.w/2,paddle.y-18,'WIDE!','#fde68a'); }
  else if(type==='multi'){
    if(balls.length<3){ const sp=currentSpeed; const mk=(s)=>({x:paddle.x+paddle.w/2,y:paddle.y-BALL_R-2,vx:Math.sin(s)*sp,vy:-Math.cos(s)*sp,stuck:false,trail:[]}); balls.push(mk(-0.62),mk(0.62)); }
    showToast('✦ MULTI-BALL — 3 BALLS!'); addFloat(paddle.x+paddle.w/2,paddle.y-18,'×3 BALLS','#67e8f9');
  }
}
function handleLifeLoss(){
  lives--; beep(150,0.25,'sawtooth',0.06); shake=7;
  if(lives<=0){ if(state==='demo') pendingReset=true; else gameOver(); }
  else { balls=[makeStuckBall()]; if(state==='demo') demoLaunchTimer=0; }
}
function updatePaddle(dt){
  if(state==='demo'){
    let target=W/2-paddle.w/2;
    const active=balls.filter(b=>!b.stuck);
    if(active.length){ let best=active[0]; active.forEach(b=>{ if(b.y>best.y) best=b; }); target=best.x-paddle.w/2; }
    const maxStep=640*dt; const diff=target-paddle.x; paddle.x+=clamp(diff,-maxStep,maxStep);
  } else {
    let moved=false;
    if(keys['ArrowLeft']||keys['KeyA']){ paddle.x-=600*dt; moved=true; }
    if(keys['ArrowRight']||keys['KeyD']){ paddle.x+=600*dt; moved=true; }
    if(moved) mouseActive=false;
    else if(mouseActive&&mouseX!==null){ const target=mouseX-paddle.w/2; paddle.x+=(target-paddle.x)*Math.min(1,dt*20); }
  }
  paddle.x=clamp(paddle.x,0,W-paddle.w);
  balls.forEach(b=>{ if(b.stuck){ b.x=paddle.x+paddle.w/2; b.y=paddle.y-BALL_R-2; }});
}
function updateBalls(dt){
  for(let bi=balls.length-1;bi>=0;bi--){
    const ball=balls[bi]; if(ball.stuck) continue;
    const dist=Math.hypot(ball.vx,ball.vy)*dt; const steps=Math.max(1,Math.ceil(dist/4)); const stepDt=dt/steps;
    for(let s=0;s<steps;s++){
      const prevX=ball.x,prevY=ball.y;
      ball.x+=ball.vx*stepDt; ball.y+=ball.vy*stepDt;
      if(ball.x<BALL_R){ ball.x=BALL_R; ball.vx=Math.abs(ball.vx); beep(330,0.04,'square',0.03); }
      if(ball.x>W-BALL_R){ ball.x=W-BALL_R; ball.vx=-Math.abs(ball.vx); beep(330,0.04,'square',0.03); }
      if(ball.y<BALL_R){ ball.y=BALL_R; ball.vy=Math.abs(ball.vy); beep(330,0.04,'square',0.03); }
      // paddle
      if(ball.vy>0 && ball.y+BALL_R>=paddle.y && ball.y+BALL_R<=paddle.y+paddle.h+12 && ball.x>=paddle.x-BALL_R && ball.x<=paddle.x+paddle.w+BALL_R && prevY+BALL_R<=paddle.y+10){
        const rel=clamp((ball.x-(paddle.x+paddle.w/2))/(paddle.w/2),-1,1);
        const ang=rel*1.08; // ~62deg max
        ball.vx=Math.sin(ang)*currentSpeed; ball.vy=-Math.cos(ang)*currentSpeed; ball.y=paddle.y-BALL_R-0.5;
        beep(440,0.05,'square',0.045);
        particles.push({x:ball.x,y:paddle.y,vx:(Math.random()-0.5)*120,vy:-40,life:0.25,max:0.25,size:2,color:'#67e8f9'});
      }
      // bricks — first hit only per sub-step
      for(const b of bricks){
        if(!b.alive) continue;
        const cx=clamp(ball.x,b.x,b.x+b.w), cy=clamp(ball.y,b.y,b.y+b.h);
        const dx=ball.x-cx, dy=ball.y-cy;
        if(dx*dx+dy*dy>BALL_R*BALL_R) continue;
        if(prevY+BALL_R<=b.y+0.6){ ball.y=b.y-BALL_R; ball.vy=-Math.abs(ball.vy); }
        else if(prevY-BALL_R>=b.y+b.h-0.6){ ball.y=b.y+b.h+BALL_R; ball.vy=Math.abs(ball.vy); }
        else if(prevX+BALL_R<=b.x+0.6){ ball.x=b.x-BALL_R; ball.vx=-Math.abs(ball.vx); }
        else if(prevX-BALL_R>=b.x+b.w-0.6){ ball.x=b.x+b.w+BALL_R; ball.vx=Math.abs(ball.vx); }
        else {
          const ddx=ball.x-(b.x+b.w/2), ddy=ball.y-(b.y+b.h/2);
          const ox=(b.w/2+BALL_R)-Math.abs(ddx), oy=(b.h/2+BALL_R)-Math.abs(ddy);
          if(ox<oy){ ball.vx=ddx>0?Math.abs(ball.vx):-Math.abs(ball.vx); ball.x=ddx>0?b.x+b.w+BALL_R:b.x-BALL_R; }
          else { ball.vy=ddy>0?Math.abs(ball.vy):-Math.abs(ball.vy); ball.y=ddy>0?b.y+b.h+BALL_R:b.y-BALL_R; }
        }
        damageBrick(b); break;
      }
      if(ball.y-BALL_R>H+12) break;
    }
    ball.trail.push({x:ball.x,y:ball.y}); if(ball.trail.length>12) ball.trail.shift();
    if(ball.y-BALL_R>H+12){ balls.splice(bi,1); if(balls.length===0) handleLifeLoss(); }
  }
}
function updatePowerups(dt){
  for(let i=powerups.length-1;i>=0;i--){ const p=powerups[i]; p.t+=dt; p.y+=155*dt; p.x=p.baseX+Math.sin(p.t*4)*10;
    if(p.y+11>=paddle.y && p.y-11<=paddle.y+paddle.h && p.x>=paddle.x-14 && p.x<=paddle.x+paddle.w+14){ applyPowerup(p.type); powerups.splice(i,1); continue; }
    if(p.y>H+24) powerups.splice(i,1);
  }
}
function updateParticles(dt){ for(let i=particles.length-1;i>=0;i--){ const p=particles[i]; p.life-=dt; if(p.life<=0){particles.splice(i,1);continue;} p.vy+=620*dt; p.x+=p.vx*dt; p.y+=p.vy*dt; } }
function updateFloats(dt){ for(let i=floats.length-1;i>=0;i--){ const f=floats[i]; f.life-=dt; f.y-=34*dt; if(f.life<=0) floats.splice(i,1); } }
function update(dt){
  if(paddle.wideTimer>0){ paddle.wideTimer-=dt; if(paddle.wideTimer<=0){ paddle.wideTimer=0; setPaddleWidth(paddle.baseW); } }
  bricks.forEach(b=>{ if(b.flash>0)b.flash-=dt; });
  updatePaddle(dt);
  if(state==='demo'&&balls.some(b=>b.stuck)){ demoLaunchTimer+=dt; if(demoLaunchTimer>0.75) launchBall(); }
  updateBalls(dt); updatePowerups(dt); updateParticles(dt); updateFloats(dt);
  shake=Math.max(0,shake-dt*18);
  if(pendingReset){ pendingReset=false; resetDemo(); }
}
function drawBrick(b){
  ctx.save();
  rr(b.x,b.y,b.w,b.h,6);
  if(b.tough){ const g=ctx.createLinearGradient(b.x,b.y,b.x,b.y+b.h); g.addColorStop(0,'#94a3b8'); g.addColorStop(0.5,'#64748b'); g.addColorStop(1,'#334155'); ctx.fillStyle=g; ctx.fill(); ctx.strokeStyle='rgba(226,232,240,.75)'; ctx.lineWidth=1.5; ctx.stroke();
    ctx.fillStyle='rgba(255,255,255,.5)'; [[b.x+7,b.y+7],[b.x+b.w-7,b.y+7],[b.x+7,b.y+b.h-7],[b.x+b.w-7,b.y+b.h-7]].forEach(pt=>{ctx.beginPath();ctx.arc(pt[0],pt[1],2.2,0,6.283);ctx.fill();});
  } else { const g=ctx.createLinearGradient(b.x,b.y,b.x,b.y+b.h); g.addColorStop(0,b.color); g.addColorStop(1,'rgba(0,0,0,.32)'); ctx.fillStyle=g; ctx.fill(); ctx.strokeStyle='rgba(255,255,255,.28)'; ctx.lineWidth=1; ctx.stroke(); }
  ctx.fillStyle='rgba(255,255,255,.22)'; rr(b.x+3,b.y+3,b.w-6,5,3); ctx.fill();
  if(b.flash>0){ ctx.fillStyle='rgba(255,255,255,'+(b.flash*4)+')'; rr(b.x,b.y,b.w,b.h,6); ctx.fill(); }
  if(b.tough&&b.hp===1){ const cx=b.x+b.w/2, cy=b.y+b.h/2; ctx.strokeStyle='rgba(2,6,23,.8)'; ctx.lineWidth=1.7; ctx.beginPath(); ctx.moveTo(cx-13,cy-8); ctx.lineTo(cx-4,cy-1); ctx.lineTo(cx+3,cy-7); ctx.lineTo(cx+12,cy+1); ctx.moveTo(cx-4,cy-1); ctx.lineTo(cx-1,cy+7); ctx.moveTo(cx+3,cy-7); ctx.lineTo(cx+7,cy+3); ctx.stroke(); }
  if(b.power){ const cx=b.x+b.w/2, cy=b.y+b.h/2+0.5; const pulse=1+Math.sin(time*5+b.col)*0.08; ctx.save(); ctx.translate(cx,cy); ctx.scale(pulse,pulse); ctx.shadowColor=b.power==='wide'?'#f59e0b':'#2dd4bf'; ctx.shadowBlur=10; ctx.fillStyle=b.power==='wide'?'#fde68a':'#99f6e4'; ctx.beginPath(); ctx.arc(0,0,10.5,0,6.283); ctx.fill(); ctx.shadowBlur=0; ctx.strokeStyle='rgba(2,6,23,.35)'; ctx.stroke(); ctx.fillStyle='#0f172a'; ctx.font='900 11px ui-sans-serif,system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText(b.power==='wide'?'W':'×3',0,0.5); ctx.restore(); }
  ctx.restore();
}
function render(){
  ctx.save(); if(shake>0) ctx.translate((Math.random()-0.5)*shake,(Math.random()-0.5)*shake);
  const bg=ctx.createLinearGradient(0,0,0,H); bg.addColorStop(0,'#0d1533'); bg.addColorStop(0.55,'#0a1128'); bg.addColorStop(1,'#070b1d'); ctx.fillStyle=bg; ctx.fillRect(-10,-10,W+20,H+20);
  stars.forEach(s=>{ ctx.globalAlpha=s.a*(0.6+0.4*Math.sin(time*1.5+s.s)); ctx.fillStyle='#cbd5e1'; ctx.beginPath(); ctx.arc(s.x,s.y,s.r,0,6.283); ctx.fill(); }); ctx.globalAlpha=1;
  ctx.fillStyle='rgba(255,255,255,.025)'; rr(12,50,W-24,272,14); ctx.fill();
  ctx.strokeStyle='rgba(103,232,249,.14)'; ctx.lineWidth=2; ctx.beginPath(); ctx.moveTo(2,0); ctx.lineTo(2,H); ctx.moveTo(W-2,0); ctx.lineTo(W-2,H); ctx.stroke();
  bricks.forEach(b=>{ if(b.alive) drawBrick(b); });
  // powerups
  powerups.forEach(p=>{ ctx.save(); ctx.translate(p.x,p.y); ctx.shadowColor=p.type==='wide'?'#f59e0b':'#2dd4bf'; ctx.shadowBlur=14; const g=ctx.createLinearGradient(0,-11,0,11); if(p.type==='wide'){g.addColorStop(0,'#fde68a');g.addColorStop(1,'#d97706');}else{g.addColorStop(0,'#5eead4');g.addColorStop(1,'#0d9488');} ctx.fillStyle=g; rr(-18,-11,36,22,11); ctx.fill(); ctx.shadowBlur=0; ctx.strokeStyle='rgba(255,255,255,.6)'; ctx.stroke(); ctx.fillStyle='#0f172a'; ctx.font='900 12px ui-sans-serif,system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText(p.type==='wide'?'W':'×3',0,0.5); ctx.restore(); });
  // paddle
  ctx.save(); ctx.shadowColor=paddle.wideTimer>0?'#f59e0b':'#22d3ee'; ctx.shadowBlur=18;
  const pg=ctx.createLinearGradient(paddle.x,0,paddle.x+paddle.w,0); if(paddle.wideTimer>0){pg.addColorStop(0,'#f59e0b');pg.addColorStop(0.5,'#fde68a');pg.addColorStop(1,'#f59e0b');}else{pg.addColorStop(0,'#0891b2');pg.addColorStop(0.5,'#67e8f9');pg.addColorStop(1,'#0891b2');}
  ctx.fillStyle=pg; rr(paddle.x,paddle.y,paddle.w,paddle.h,7); ctx.fill(); ctx.shadowBlur=0; ctx.strokeStyle='rgba(255,255,255,.55)'; ctx.stroke();
  ctx.fillStyle='rgba(255,255,255,.5)'; rr(paddle.x+5,paddle.y+3,paddle.w-10,4,2); ctx.fill(); ctx.restore();
  // balls + trails
  balls.forEach(b=>{ b.trail.forEach((t,i)=>{ ctx.globalAlpha=(i/b.trail.length)*0.22; ctx.fillStyle='#67e8f9'; ctx.beginPath(); ctx.arc(t.x,t.y,BALL_R*(i/b.trail.length)*0.85,0,6.283); ctx.fill(); }); ctx.globalAlpha=1;
    ctx.save(); ctx.shadowColor='#fff'; ctx.shadowBlur=14; const rg=ctx.createRadialGradient(b.x-2,b.y-2,1,b.x,b.y,BALL_R); rg.addColorStop(0,'#ffffff'); rg.addColorStop(1,'#a5f3fc'); ctx.fillStyle=rg; ctx.beginPath(); ctx.arc(b.x,b.y,BALL_R,0,6.283); 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;
  // floats
  floats.forEach(f=>{ ctx.globalAlpha=Math.max(0,f.life/f.max); ctx.fillStyle=f.color; ctx.font='900 14px ui-sans-serif,system-ui,sans-serif'; ctx.textAlign='center'; ctx.fillText(f.text,f.x,f.y); }); ctx.globalAlpha=1;
  if(paddle.wideTimer>0){ ctx.fillStyle='rgba(7,13,32,.75)'; rr(W/2-92,12,184,24,12); ctx.fill(); ctx.strokeStyle='rgba(251,191,36,.5)'; ctx.stroke(); ctx.fillStyle='#fde68a'; ctx.font='800 11px ui-sans-serif,system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText('WIDE PADDLE  '+Math.ceil(paddle.wideTimer)+'s',W/2,24.5); }
  if(state==='playing'&&balls.some(b=>b.stuck)){ ctx.fillStyle='rgba(7,13,32,.82)'; rr(W/2-150,H-118,300,34,17); ctx.fill(); ctx.strokeStyle='rgba(255,255,255,.18)'; ctx.stroke(); ctx.fillStyle='#fff'; ctx.font='800 13px ui-sans-serif,system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText('SPACE / CLICK TO LAUNCH',W/2,H-101); }
  ctx.restore();
}
function updateHUD(){
  scoreEl.textContent=score; hiEl.textContent=Math.max(hi,score); bricksEl.textContent=bricksLeft;
  livesEl.innerHTML='♥'.repeat(Math.max(0,lives))+'<span class="dim">'+'♥'.repeat(Math.max(0,3-lives))+'</span>';
  if(paddle.wideTimer>0){ widePill.style.display='flex'; wideTime.textContent=Math.ceil(paddle.wideTimer)+'s'; wideBar.style.width=(paddle.wideTimer/10*100)+'%'; } else widePill.style.display='none';
}
let last=0;
function loop(ts){ requestAnimationFrame(loop); if(!last)last=ts; const dt=Math.min((ts-last)/1000,0.033); last=ts; time+=dt;
  if(state==='demo'||state==='playing') update(dt);
  else if(state==='win'||state==='over'){ updateParticles(dt); updateFloats(dt); shake=Math.max(0,shake-dt*18); }
  render(); updateHUD();
}
initLevel(); state='demo'; renderOverlay(); requestAnimationFrame(loop);
</script>
</body>
</html>
Reasoning trace (12,000 tokens)
Designing vanilla JS canvas Breakout with paddle controls, angled bounces, mixed bricks, power-ups, collision handling, particles, lives, score, and demo-mode overlay.Adding responsive 1280x800 canvas scaling to 375px with attract-mode overlay and HUD layout.Defining collision substeps, brick HP states, power-up spawning, and HUD state management.Adding precise AABB collision resolution with substeps and demo-mode auto-reset logic.Defining UI states, rendering details, input handling, and sound with DOM HUD updates.Implementing substepped ball physics, brick damage with flash and cracks, powerup effects, and canvas rendering with HUD sync.Adjusting overlay layout to a bottom banner and defining start, pause, restart, and demo reset behavior.Adding paddle angle bounce, speed normalization, input handling, and responsive CSS layout.Implementing overlay rendering, audio initialization, brick patterns, and main game loop logic.Adding ball trail, shake decay, canvas UI text, and brick collision with substep handling.