Muse Ember
by MetaReasoningClean
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 • <b>Space / Click</b> launch • <b>P</b> pause • <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 • Mouse / ← → to move • 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> • 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> • 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.