Muse Ember
by MetaReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 35.4 s
- Cost
- $0.0100
- Output tokens
- 19,832
- Spent thinking
- 9,125 tokens
- Speed
- 595.6 tok/s
- Console errors
- 0
- First token after
- 2.1 s
- First visible text after
- 23.6 s
- Prompt tokens
- 526
- Code size
- 31.7 KB
- Finish reason
- stop
- Served by
- Meta
- Model id
meta/muse-ember- Reasoning sent
model default- 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 · 752 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 Arcade</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
background:
radial-gradient(900px 500px at 15% -10%, rgba(56,189,248,.25), transparent 60%),
radial-gradient(900px 500px at 90% 0%, rgba(232,121,249,.20), transparent 55%),
radial-gradient(700px 600px at 50% 110%, rgba(99,102,241,.18), transparent 60%),
#060813;
color:#eef2ff;
min-height:100vh;
}
.page{max-width:920px;margin:0 auto;padding:12px 12px 18px}
header{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between}
.logo{font-weight:900;letter-spacing:.06em;font-size:clamp(22px,4vw,32px);line-height:1;text-shadow:0 0 24px rgba(56,189,248,.55)}
.logo span{display:block;font-size:11px;letter-spacing:.42em;color:#7dd3fc;font-weight:800;margin-top:4px}
.hud{display:flex;flex-wrap:wrap;gap:8px}
.stat{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:14px;padding:7px 12px;min-width:92px;text-align:center;backdrop-filter:blur(6px)}
.stat label{display:block;font-size:10px;letter-spacing:.22em;opacity:.65;font-weight:800}
.stat strong{font-size:20px;line-height:1.15;color:#fff}
.stat strong.hearts{color:#fb7185;letter-spacing:.08em;font-size:18px}
.actions{display:flex;gap:8px;flex-wrap:wrap}
button{font:inherit;color:#fff;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.16);border-radius:999px;padding:9px 14px;cursor:pointer;font-weight:800;transition:.15s}
button:hover{transform:translateY(-1px);background:rgba(255,255,255,.14)}
button.primary{background:linear-gradient(135deg,#06b6d4,#6366f1 55%,#d946ef);border:none;box-shadow:0 10px 30px rgba(99,102,241,.45);padding:13px 26px;font-size:16px}
button.primary:hover{filter:brightness(1.1)}
.stage{position:relative;margin:14px auto 10px;width:100%;max-width:820px}
canvas{width:100%;height:auto;display:block;border-radius:18px;border:1px solid rgba(255,255,255,.14);
background:#0b1026;box-shadow:0 24px 70px rgba(0,0,0,.65),0 0 0 6px rgba(255,255,255,.03),0 0 45px rgba(56,189,248,.18);cursor:pointer}
.overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;border-radius:18px;padding:16px}
.hidden{display:none!important}
#startOverlay{background:linear-gradient(180deg,rgba(5,8,20,.18),rgba(5,8,20,.55));cursor:pointer}
#pauseOverlay,#overOverlay,#winOverlay{background:rgba(4,6,16,.72);backdrop-filter:blur(3px)}
.panel{background:rgba(9,13,32,.9);border:1px solid rgba(255,255,255,.16);border-radius:22px;padding:26px 28px;text-align:center;max-width:540px;width:100%;box-shadow:0 24px 70px rgba(0,0,0,.6)}
.panel h1{margin:0 0 6px;font-size:clamp(36px,7vw,58px);letter-spacing:.04em;line-height:.95;
background:linear-gradient(90deg,#67e8f9,#a5b4fc,#f0abfc);-webkit-background-clip:text;background-clip:text;color:transparent}
.panel h2{margin:0 0 8px;font-size:clamp(28px,5vw,42px);letter-spacing:.03em}
.panel p{margin:8px 0 14px;color:#c7d2fe;line-height:1.5;font-size:15px}
.kicker{display:inline-block;background:rgba(250,204,21,.16);color:#fde68a;border:1px solid rgba(250,204,21,.35);padding:5px 10px;border-radius:999px;font-size:11px;letter-spacing:.2em;font-weight:900;margin-bottom:12px}
.hint{font-size:13px;color:#a5b4fc;margin-top:14px;line-height:1.6}
.hint b{color:#fff}
.bigscore{font-size:34px;font-weight:900;margin:6px 0 14px}
.demoBadge{position:absolute;top:10px;left:50%;transform:translateX(-50%);background:rgba(250,204,21,.95);color:#111827;
font-size:11px;font-weight:900;letter-spacing:.16em;padding:6px 12px;border-radius:999px;box-shadow:0 8px 24px rgba(0,0,0,.35);z-index:5;white-space:nowrap}
.wideWrap{position:absolute;top:10px;right:10px;z-index:5;background:rgba(8,12,28,.82);border:1px solid rgba(103,232,249,.45);
border-radius:999px;padding:7px 11px;display:flex;align-items:center;gap:8px;font-size:11px;font-weight:900;letter-spacing:.08em;opacity:0;transition:opacity .2s;pointer-events:none}
.wideWrap.on{opacity:1}
.wideBar{width:74px;height:8px;background:rgba(255,255,255,.14);border-radius:999px;overflow:hidden}
.wideFill{height:100%;width:100%;background:linear-gradient(90deg,#22d3ee,#a78bfa);border-radius:999px}
footer{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;align-items:center;margin-top:10px;color:#cbd5e1;font-size:13px;line-height:1.5;text-align:center}
.chip{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.12);border-radius:999px;padding:6px 10px}
.chip b{color:#fff}
.dot{display:inline-block;width:11px;height:11px;border-radius:3px;vertical-align:-1px;margin-right:5px}
@media (max-width:640px){
.page{padding:10px 8px 14px}
header{justify-content:center}
.logo{text-align:center}
.hud{justify-content:center}
.stat{min-width:76px;padding:6px 9px}
.stat strong{font-size:17px}
.actions{justify-content:center}
.panel{padding:20px 16px}
footer{font-size:12px}
.wideBar{width:52px}
}
</style>
</head>
<body>
<div class="page">
<header>
<div class="logo">🧱 BREAKOUT<span>NEON ARCADE</span></div>
<div class="hud" aria-label="Game status">
<div class="stat"><label>SCORE</label><strong id="score">0</strong></div>
<div class="stat"><label>BEST</label><strong id="best">0</strong></div>
<div class="stat"><label>LIVES</label><strong id="lives" class="hearts">♥♥♥</strong></div>
<div class="stat"><label>BALLS</label><strong id="ballsCount">1</strong></div>
</div>
<div class="actions">
<button id="pauseBtn" type="button">⏸ Pause</button>
<button id="restartBtn" type="button">↻ Restart</button>
<button id="soundBtn" type="button">🔊 Sound</button>
</div>
</header>
<div class="stage" id="stage">
<canvas id="game" width="800" height="600" aria-label="Breakout game canvas"></canvas>
<div class="demoBadge" id="demoBadge">● DEMO — AI PLAYING</div>
<div class="wideWrap" id="wideWrap"><span>↔ WIDE</span><div class="wideBar"><div class="wideFill" id="wideFill"></div></div><span id="wideTime">10s</span></div>
<div class="overlay" id="startOverlay">
<div class="panel">
<div class="kicker">✦ 8 ROWS · CRACKED STEEL · POWER-UPS ✦</div>
<h1>BREAKOUT</h1>
<p>Break every brick, catch the falling icons, and don't let the ball slip past your paddle. Hit the paddle off-centre for sharper angles!</p>
<button class="primary js-start" type="button">▶ Play</button>
<div class="hint"><b>Click or press a key to start</b><br>Mouse / ← → move · Click / Space launch · P pause · R restart</div>
</div>
</div>
<div class="overlay hidden" id="pauseOverlay">
<div class="panel">
<h2>⏸ Paused</h2>
<p>Take a breath. The bricks aren't going anywhere.</p>
<button class="primary" id="resumeBtn" type="button">Resume</button>
<div class="hint">Press <b>P</b> or click Resume</div>
</div>
</div>
<div class="overlay hidden" id="overOverlay">
<div class="panel">
<div class="kicker" style="background:rgba(251,113,133,.15);color:#fecdd3;border-color:rgba(251,113,133,.4)">GAME OVER</div>
<h2>Out of lives</h2>
<p>Final score</p>
<div class="bigscore" id="finalScore">0</div>
<p id="overBest">Best: 0</p>
<button class="primary js-restart" type="button">↻ Play again</button>
<div class="hint">Press <b>Enter</b>, <b>Space</b> or click to restart</div>
</div>
</div>
<div class="overlay hidden" id="winOverlay">
<div class="panel">
<div class="kicker" style="background:rgba(110,231,90,.15);color:#bbf7d0;border-color:rgba(110,231,90,.4)">🏆 VICTORY</div>
<h2>Level cleared!</h2>
<p>You smashed every brick. Final score</p>
<div class="bigscore" id="winScore">0</div>
<p id="winBest">Best: 0</p>
<button class="primary js-restart" type="button">↻ Play again</button>
<div class="hint">Press <b>Enter</b>, <b>Space</b> or click to restart</div>
</div>
</div>
</div>
<footer>
<span class="chip"><b>Mouse / ← →</b> move paddle</span>
<span class="chip"><b>Click / Space</b> launch</span>
<span class="chip"><span class="dot" style="background:#ff4d6d"></span>Normal brick · 1 hit</span>
<span class="chip"><span class="dot" style="background:#64748b"></span>Steel brick · 2 hits, cracks</span>
<span class="chip"><b>↔</b> Wide paddle · 10s <b>●●●</b> Multi-ball ×3</span>
</footer>
</div>
<script>
(() => {
"use strict";
const W = 800, H = 600;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const livesEl = document.getElementById('lives');
const ballsEl = document.getElementById('ballsCount');
const startOverlay = document.getElementById('startOverlay');
const pauseOverlay = document.getElementById('pauseOverlay');
const overOverlay = document.getElementById('overOverlay');
const winOverlay = document.getElementById('winOverlay');
const demoBadge = document.getElementById('demoBadge');
const wideWrap = document.getElementById('wideWrap');
const wideFill = document.getElementById('wideFill');
const wideTimeEl = document.getElementById('wideTime');
const pauseBtn = document.getElementById('pauseBtn');
const BASE_W = 112, WIDE_W = 178;
const paddle = { x: W/2 - BASE_W/2, y: H - 38, w: BASE_W, h: 14 };
let balls = [], bricks = [], particles = [], powerups = [], texts = [];
let mode = 'attract'; // attract | ready | playing | paused | over | win
let prevMode = 'playing';
let score = 0, best = 0, lives = 3;
let wideTimer = 0, gameTime = 0, shake = 0;
let keys = {}, mouseX = W/2, mouseControl = false;
let muted = false, audioCtx = null;
const stars = Array.from({length:80},()=>({x:Math.random()*W,y:Math.random()*H,r:Math.random()*1.6+.4,a:Math.random()*.5+.15}));
const ROWS = 8, COLS = 10;
const COLORS = ['#ff4d6d','#ff8a3d','#ffd23f','#6ee75a','#2dd4bf','#38bdf8','#818cf8','#e879f9'];
const POWER_MAP = {
'0,2':'wide','0,7':'multi','2,5':'wide','3,2':'multi','4,8':'wide','5,4':'multi','6,1':'wide','7,6':'multi'
};
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 clamp(v,a,b){return Math.max(a,Math.min(b,v));}
function rand(a,b){return a+Math.random()*(b-a);}
function initAudio(){
if(muted) return;
try{
if(!audioCtx) audioCtx = new (window.AudioContext||window.webkitAudioContext)();
if(audioCtx.state==='suspended') audioCtx.resume();
}catch(e){}
}
function tone(freq,dur=0.07,type='square',vol=0.035,slideTo=null){
if(muted || !audioCtx) return;
try{
const t = audioCtx.currentTime;
const o = audioCtx.createOscillator(), g = audioCtx.createGain();
o.type = type; o.frequency.setValueAtTime(freq,t);
if(slideTo) o.frequency.exponentialRampToValueAtTime(slideTo,t+dur);
g.gain.setValueAtTime(vol,t); g.gain.exponentialRampToValueAtTime(0.0001,t+dur);
o.connect(g).connect(audioCtx.destination); o.start(t); o.stop(t+dur+0.02);
}catch(e){}
}
function createBall(x,y,rad,speed=5.4){
return { x, y, vx: Math.cos(rad)*speed, vy: Math.sin(rad)*speed, speed, r: 8, trail: [] };
}
function launchedBall(){
const b = createBall(paddle.x+paddle.w/2, paddle.y-10, -Math.PI/2 + rand(-0.18,0.18), 5.4);
return b;
}
function readyBall(){
return { x: paddle.x+paddle.w/2, y: paddle.y-10, vx:0, vy:0, speed:5.4, r:8, trail:[] };
}
function initBricks(){
bricks = [];
const margin = 40, gap = 6, top = 64;
const bw = (W - margin*2 - gap*(COLS-1)) / COLS;
const bh = 22;
for(let r=0;r<ROWS;r++){
for(let c=0;c<COLS;c++){
const key = r+','+c;
const power = POWER_MAP[key] || null;
let tough = false;
if(!power){
if(r===0 && c%3===0) tough=true;
if(r===1 && c%3===1) tough=true;
if(r===2 && c%4===2) tough=true;
if(r===3 && c%3===0) tough=true;
if(r===4 && c%4===1) tough=true;
if(r===5 && c%3===2) tough=true;
if(r===6 && c%4===0) tough=true;
if(r===7 && c%5===2) tough=true;
}
bricks.push({
x: margin + c*(bw+gap), y: top + r*(bh+gap), w: bw, h: bh,
row:r, col:c, color: COLORS[r], tough, power,
hp: tough?2:1, maxHp: tough?2:1, alive:true, flash:0
});
}
}
}
function updateHUD(){
scoreEl.textContent = score;
bestEl.textContent = best;
livesEl.textContent = '♥'.repeat(Math.max(0,lives)) + '♡'.repeat(Math.max(0,3-lives));
ballsEl.textContent = balls.length;
}
function hideOverlays(){
startOverlay.classList.add('hidden');
pauseOverlay.classList.add('hidden');
overOverlay.classList.add('hidden');
winOverlay.classList.add('hidden');
demoBadge.classList.add('hidden');
}
function show(el){ el.classList.remove('hidden'); }
function resetDemo(){
score = 0; lives = 3; wideTimer = 0; paddle.w = BASE_W; paddle.x = W/2 - paddle.w/2;
initBricks(); particles=[]; powerups=[]; texts=[];
balls = [launchedBall()];
mode = 'attract';
demoBadge.classList.remove('hidden');
updateHUD(); updateWideUI();
}
function startGame(){
initAudio();
score = 0; lives = 3; wideTimer = 0; paddle.w = BASE_W; paddle.x = W/2 - paddle.w/2;
initBricks(); particles=[]; powerups=[]; texts=[];
balls = [readyBall()];
if(score>best) best=score;
mode = 'ready';
hideOverlays();
pauseBtn.textContent = '⏸ Pause';
updateHUD(); updateWideUI();
tone(520,.08,'square',.04); setTimeout(()=>tone(760,.1,'square',.04),70);
}
function launch(){
if(mode!=='ready') return;
const b = balls[0];
const rel = clamp(((b.x - (paddle.x+paddle.w/2)) / (paddle.w/2)), -1, 1);
const ang = rel * (28*Math.PI/180);
b.vx = Math.sin(ang)*b.speed;
b.vy = -Math.cos(ang)*b.speed;
mode = 'playing';
tone(440,.07,'square',.04);
}
function gameOver(){
mode='over';
if(score>best) best=score;
document.getElementById('finalScore').textContent = score;
document.getElementById('overBest').textContent = 'Best: '+best;
show(overOverlay); updateHUD();
tone(320,.18,'sawtooth',.05,140); setTimeout(()=>tone(220,.25,'sawtooth',.05,90),180);
}
function victory(){
score += lives*200 + 500;
if(score>best) best=score;
mode='win';
document.getElementById('winScore').textContent = score;
document.getElementById('winBest').textContent = 'Best: '+best+' · Lives bonus included ✓';
show(winOverlay); updateHUD();
[523,659,784,1046].forEach((f,i)=>setTimeout(()=>tone(f,.14,'square',.05),i*110));
}
function togglePause(){
if(mode==='playing' || mode==='ready'){
prevMode = mode; mode='paused'; show(pauseOverlay); pauseBtn.textContent='▶ Resume';
} else if(mode==='paused'){
mode = prevMode || 'playing'; pauseOverlay.classList.add('hidden'); pauseBtn.textContent='⏸ Pause';
}
}
function spawnParticles(x,y,color,n=14,spread=3.2){
for(let i=0;i<n;i++){
const a = Math.random()*Math.PI*2, s = rand(0.6,spread);
particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-1,life:rand(.45,.9),max:.9,color,size:rand(2,5),rot:Math.random()*6,vr:rand(-.3,.3)});
}
}
function addText(x,y,str,color='#fff'){
texts.push({x,y,str,color,life:1});
}
function updateWideUI(){
if(wideTimer>0){
wideWrap.classList.add('on');
wideFill.style.width = (wideTimer/10*100)+'%';
wideTimeEl.textContent = Math.ceil(wideTimer)+'s';
} else {
wideWrap.classList.remove('on');
}
}
function aiMove(dt){
if(!balls.length) return;
// target the lowest ball (closest to paddle), prefer falling balls
let target = balls[0];
let bestY = -1;
for(const b of balls){ if(b.y>bestY){bestY=b.y; target=b;} }
let tx = target.x - paddle.w/2;
// small human-like error so demo looks organic but still competent
tx += Math.sin(gameTime*1.4)*10;
const dx = tx - paddle.x;
const maxStep = 500*dt;
paddle.x += clamp(dx, -maxStep, maxStep);
paddle.x = clamp(paddle.x, 8, W-8-paddle.w);
}
function playerMove(dt){
const sp = 540*dt;
if(keys['ArrowLeft'] || keys['Left'] || keys['a'] || keys['A']){ paddle.x -= sp; mouseControl=false; }
if(keys['ArrowRight'] || keys['Right'] || keys['d'] || keys['D']){ paddle.x += sp; mouseControl=false; }
if(mouseControl){
paddle.x = clamp(mouseX - paddle.w/2, 8, W-8-paddle.w);
}
paddle.x = clamp(paddle.x, 8, W-8-paddle.w);
}
function normalizeBall(b){
const m = Math.hypot(b.vx,b.vy) || 1;
b.vx = b.vx/m*b.speed; b.vy = b.vy/m*b.speed;
// prevent near-horizontal loops
const minVy = b.speed*0.32;
if(Math.abs(b.vy) < minVy){
b.vy = (b.vy<0?-1:1)*minVy;
const sx = b.vx<0?-1:1;
b.vx = sx*Math.sqrt(Math.max(0,b.speed*b.speed - b.vy*b.vy));
}
}
function hitBrick(b, brick){
brick.hp--; brick.flash = 1;
if(brick.hp>0){
// cracked, still alive
score += 10;
spawnParticles(b.x,b.y,'#cbd5e1',6,2.2);
tone(190,.06,'sawtooth',.04);
addText(brick.x+brick.w/2, brick.y, '+10', '#e2e8f0');
} else {
brick.alive = false;
const pts = brick.power ? 80 : (brick.tough ? 120 : 50);
score += pts;
shake = Math.min(6, shake+2.2);
spawnParticles(brick.x+brick.w/2, brick.y+brick.h/2, brick.color, 18, 3.6);
if(brick.tough) spawnParticles(brick.x+brick.w/2, brick.y+brick.h/2, '#94a3b8', 8, 2.4);
tone(brick.tough?340:520,.06,'square',.045);
addText(brick.x+brick.w/2, brick.y+brick.h/2, '+'+pts, '#fde68a');
if(brick.power){
powerups.push({x:brick.x+brick.w/2, y:brick.y+brick.h/2, vy:135, type:brick.power, t:0});
tone(700,.08,'square',.04);
}
}
if(score>best) best=score;
updateHUD();
}
function updateBalls(dt){
const dt60 = dt*60;
for(let bi=balls.length-1; bi>=0; bi--){
const b = balls[bi];
const dist = Math.hypot(b.vx,b.vy)*dt60;
const steps = Math.max(1, Math.ceil(dist/2.6));
const sx = b.vx*dt60/steps, sy = b.vy*dt60/steps;
for(let s=0;s<steps;s++){
b.x += sx; b.y += sy;
// walls — never tunnel
if(b.x - b.r < 0){ b.x = b.r; b.vx = Math.abs(b.vx); tone(180,.04,'square',.02); }
if(b.x + b.r > W){ b.x = W-b.r; b.vx = -Math.abs(b.vx); tone(180,.04,'square',.02); }
if(b.y - b.r < 0){ b.y = b.r; b.vy = Math.abs(b.vy); tone(180,.04,'square',.02); }
// paddle
if(b.vy>0 && b.x+b.r>paddle.x && b.x-b.r<paddle.x+paddle.w && b.y+b.r>=paddle.y && b.y-b.r<=paddle.y+paddle.h+4){
const rel = clamp((b.x-(paddle.x+paddle.w/2))/(paddle.w/2), -1, 1);
const ang = rel * (65*Math.PI/180); // centre = straight up, edges = sharp
b.speed = Math.min(8, b.speed+0.03);
b.vx = Math.sin(ang)*b.speed;
b.vy = -Math.cos(ang)*b.speed;
b.y = paddle.y - b.r - 0.6;
spawnParticles(b.x, paddle.y, '#67e8f9', 5, 1.6);
tone(260,.06,'square',.04);
}
// bricks
for(const brick of bricks){
if(!brick.alive) continue;
const cx = clamp(b.x, brick.x, brick.x+brick.w);
const cy = clamp(b.y, brick.y, brick.y+brick.h);
const dx = b.x-cx, dy = b.y-cy;
if(dx*dx+dy*dy < b.r*b.r){
// minimal penetration axis determines bounce
const left = (b.x+b.r)-brick.x;
const right = (brick.x+brick.w)-(b.x-b.r);
const top = (b.y+b.r)-brick.y;
const bottom = (brick.y+brick.h)-(b.y-b.r);
const m = Math.min(left,right,top,bottom);
if(m===left){ b.x = brick.x-b.r; b.vx = -Math.abs(b.vx); }
else if(m===right){ b.x = brick.x+brick.w+b.r; b.vx = Math.abs(b.vx); }
else if(m===top){ b.y = brick.y-b.r; b.vy = -Math.abs(b.vy); }
else { b.y = brick.y+brick.h+b.r; b.vy = Math.abs(b.vy); }
normalizeBall(b);
hitBrick(b, brick);
break;
}
}
}
b.trail.unshift({x:b.x,y:b.y,life:1});
if(b.trail.length>12) b.trail.pop();
if(b.y - b.r > H){
balls.splice(bi,1);
}
}
if(balls.length===0){
if(mode==='attract'){
balls = [launchedBall()];
} else if(mode==='playing'){
lives--; updateHUD();
tone(300,.25,'sawtooth',.05,90);
if(lives<=0){ gameOver(); }
else {
wideTimer=0; paddle.w=BASE_W; updateWideUI();
balls=[readyBall()]; mode='ready';
paddle.x = clamp(paddle.x, 8, W-8-paddle.w);
}
}
}
if(mode==='attract' || mode==='playing'){
if(bricks.every(br=>!br.alive)){
if(mode==='attract'){ initBricks(); balls=[launchedBall()]; score=0; updateHUD(); }
else victory();
}
}
updateHUD();
}
function applyPowerup(p){
tone(660,.08,'square',.05); setTimeout(()=>tone(880,.12,'square',.05),80);
if(p.type==='wide'){
paddle.w = WIDE_W;
paddle.x = clamp(paddle.x - (WIDE_W-BASE_W)/2, 8, W-8-paddle.w);
wideTimer = 10;
addText(paddle.x+paddle.w/2, paddle.y-18, 'WIDE PADDLE! 10s', '#67e8f9');
} else if(p.type==='multi'){
const src = [...balls];
for(const b of src){
if(balls.length>=3) break;
const rad = Math.atan2(b.vy,b.vx);
const off = (balls.length%2===0)? 0.45 : -0.45;
const nb = createBall(b.x,b.y, rad+off, b.speed);
nb.vx = Math.cos(rad+off)*nb.speed; nb.vy = Math.sin(rad+off)*nb.speed;
normalizeBall(nb);
balls.push(nb);
if(balls.length>=3) break;
}
addText(paddle.x+paddle.w/2, paddle.y-18, 'MULTI-BALL! ×'+balls.length, '#f0abfc');
score += 30;
}
updateHUD(); updateWideUI();
}
function updatePowerups(dt){
for(let i=powerups.length-1;i>=0;i--){
const p = powerups[i];
p.t += dt; p.y += p.vy*dt; p.x += Math.sin(p.t*3)*12*dt;
// catch
if(p.y+10>=paddle.y && p.y-10<=paddle.y+paddle.h && p.x+16>=paddle.x && p.x-16<=paddle.x+paddle.w){
applyPowerup(p); powerups.splice(i,1); continue;
}
if(p.y>H+20) powerups.splice(i,1);
}
}
function update(dt){
gameTime += dt;
if(wideTimer>0){
wideTimer -= dt;
if(wideTimer<=0){ wideTimer=0; paddle.w=BASE_W; }
updateWideUI();
}
if(mode==='attract') aiMove(dt);
else if(mode==='playing' || mode==='ready') playerMove(dt);
if(mode==='ready' && balls.length){
balls[0].x = paddle.x+paddle.w/2;
balls[0].y = paddle.y-10;
balls[0].trail.length = 0;
}
if(mode==='playing' || mode==='attract'){
updateBalls(dt);
updatePowerups(dt);
}
// particles
for(let i=particles.length-1;i>=0;i--){
const p=particles[i];
p.life-=dt; p.x+=p.vx; p.y+=p.vy; p.vy+=0.12; p.rot+=p.vr;
if(p.life<=0) particles.splice(i,1);
}
for(let i=texts.length-1;i>=0;i--){
const t=texts[i]; t.y-=32*dt; t.life-=dt*0.9;
if(t.life<=0) texts.splice(i,1);
}
for(const br of bricks) if(br.flash>0) br.flash=Math.max(0,br.flash-dt*4);
for(const b of balls) for(const tr of b.trail) tr.life-=dt*2.2;
shake = Math.max(0, shake - dt*14);
}
function drawBrick(b){
ctx.save();
if(b.tough){
const g = ctx.createLinearGradient(b.x,b.y,b.x,b.y+b.h);
g.addColorStop(0,'#7c8798'); g.addColorStop(.5,'#4b5565'); g.addColorStop(1,'#2f3744');
rr(b.x,b.y,b.w,b.h,5); ctx.fillStyle=g; ctx.fill();
ctx.strokeStyle='rgba(226,232,240,.65)'; ctx.lineWidth=1.2; ctx.stroke();
// rivets
ctx.fillStyle='rgba(255,255,255,.45)';
[[b.x+7,b.y+6],[b.x+b.w-7,b.y+6],[b.x+7,b.y+b.h-6],[b.x+b.w-7,b.y+b.h-6]].forEach(pt=>{ctx.beginPath();ctx.arc(pt[0],pt[1],2,0,7);ctx.fill();});
if(b.hp<b.maxHp){
// crack
ctx.strokeStyle='rgba(5,8,15,.85)'; ctx.lineWidth=1.6; ctx.beginPath();
const cx=b.x+b.w/2, cy=b.y+b.h/2;
ctx.moveTo(cx-18,cy-6); ctx.lineTo(cx-6,cy+2); ctx.lineTo(cx+3,cy-4); ctx.lineTo(cx+16,cy+5);
ctx.moveTo(cx-4,cy+2); ctx.lineTo(cx+1,cy+9);
ctx.stroke();
}
} else {
rr(b.x,b.y,b.w,b.h,5); ctx.fillStyle=b.color; ctx.fill();
ctx.fillStyle='rgba(255,255,255,.28)'; rr(b.x+2,b.y+2,b.w-4,7,4); ctx.fill();
ctx.fillStyle='rgba(0,0,0,.18)'; rr(b.x+2,b.y+b.h-7,b.w-4,5,3); ctx.fill();
ctx.strokeStyle='rgba(0,0,0,.22)'; ctx.lineWidth=1; rr(b.x,b.y,b.w,b.h,5); ctx.stroke();
}
if(b.flash>0){ rr(b.x,b.y,b.w,b.h,5); ctx.fillStyle=`rgba(255,255,255,${b.flash*0.55})`; ctx.fill(); }
if(b.power){
const cx=b.x+b.w/2, cy=b.y+b.h/2;
const pulse = 1 + Math.sin(gameTime*4 + b.row)*0.12;
ctx.save();
ctx.shadowColor='#fff'; ctx.shadowBlur=10;
ctx.beginPath(); ctx.arc(cx,cy,9*pulse,0,7); ctx.fillStyle='#fff'; ctx.fill();
ctx.restore();
ctx.fillStyle = b.power==='wide' ? '#0369a1' : '#86198f';
ctx.font='900 11px system-ui, sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
if(b.power==='wide'){ ctx.fillText('↔',cx,cy+0.5); }
else {
ctx.beginPath();
[-5,0,5].forEach(off=>{ctx.arc(cx+off,cy,2.1,0,7);ctx.fill();});
}
}
ctx.restore();
}
function draw(){
ctx.save();
if(shake>0) ctx.translate(rand(-shake,shake), rand(-shake,shake));
// bg
const bg = ctx.createLinearGradient(0,0,0,H);
bg.addColorStop(0,'#0d1330'); bg.addColorStop(.55,'#0a0f26'); bg.addColorStop(1,'#120a22');
ctx.fillStyle=bg; ctx.fillRect(-10,-10,W+20,H+20);
for(const s of stars){ ctx.globalAlpha=s.a; ctx.fillStyle='#c7d2fe'; ctx.beginPath(); ctx.arc(s.x,s.y,s.r,0,7); ctx.fill(); }
ctx.globalAlpha=0.05; ctx.strokeStyle='#93c5fd'; ctx.lineWidth=1;
for(let x=0;x<=W;x+=40){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke();}
for(let y=0;y<=H;y+=40){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();}
ctx.globalAlpha=1;
// bricks
for(const b of bricks) if(b.alive) drawBrick(b);
// particles
for(const p of particles){
ctx.save(); ctx.globalAlpha=Math.max(0,p.life/p.max); ctx.translate(p.x,p.y); ctx.rotate(p.rot);
ctx.fillStyle=p.color; ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size); ctx.restore();
}
// powerups
for(const p of powerups){
ctx.save();
const isWide = p.type==='wide';
ctx.shadowColor = isWide?'#22d3ee':'#e879f9'; ctx.shadowBlur=14;
rr(p.x-17,p.y-10,34,20,10);
ctx.fillStyle = isWide ? '#0891b2' : '#a21caf'; ctx.fill();
ctx.strokeStyle='rgba(255,255,255,.85)'; ctx.lineWidth=1.4; ctx.stroke();
ctx.shadowBlur=0; ctx.fillStyle='#fff'; ctx.font='900 12px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
if(isWide) ctx.fillText('↔',p.x,p.y+0.5);
else { ctx.beginPath(); [-6,0,6].forEach(o=>{ctx.arc(p.x+o,p.y,2.2,0,7);ctx.fill();}); }
ctx.restore();
}
// paddle
ctx.save();
ctx.shadowColor = wideTimer>0 ? '#facc15' : '#22d3ee'; ctx.shadowBlur=16;
const pg = ctx.createLinearGradient(paddle.x,0,paddle.x+paddle.w,0);
if(wideTimer>0){ pg.addColorStop(0,'#f59e0b'); pg.addColorStop(.5,'#fde68a'); pg.addColorStop(1,'#f59e0b'); }
else { pg.addColorStop(0,'#06b6d4'); pg.addColorStop(.5,'#67e8f9'); pg.addColorStop(1,'#6366f1'); }
rr(paddle.x,paddle.y,paddle.w,paddle.h,7); ctx.fillStyle=pg; ctx.fill();
ctx.shadowBlur=0;
ctx.fillStyle='rgba(255,255,255,.55)'; rr(paddle.x+3,paddle.y+2,paddle.w-6,4,2); ctx.fill();
ctx.restore();
// balls + trail
for(const b of balls){
for(const tr of b.trail){
if(tr.life<=0) continue;
ctx.globalAlpha=tr.life*0.35; ctx.fillStyle='#93c5fd';
ctx.beginPath(); ctx.arc(tr.x,tr.y,b.r*tr.life*0.8,0,7); ctx.fill();
}
ctx.globalAlpha=1;
ctx.save(); ctx.shadowColor='#fff'; ctx.shadowBlur=14;
ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,7); ctx.fillStyle='#fff'; ctx.fill(); ctx.restore();
ctx.beginPath(); ctx.arc(b.x-2,b.y-2,2.2,0,7); ctx.fillStyle='#dbeafe'; ctx.fill();
}
// floating texts
ctx.textAlign='center'; ctx.textBaseline='middle';
for(const t of texts){
ctx.globalAlpha=Math.max(0,t.life); ctx.font='900 15px system-ui,sans-serif';
ctx.fillStyle=t.color; ctx.fillText(t.str,t.x,t.y);
}
ctx.globalAlpha=1;
if(mode==='ready'){
const b=balls[0];
ctx.save();
ctx.strokeStyle='rgba(255,255,255,.5)'; ctx.setLineDash([6,7]); ctx.lineWidth=2;
ctx.beginPath(); ctx.moveTo(b.x,b.y-10); ctx.lineTo(b.x,b.y-70); ctx.stroke(); ctx.setLineDash([]);
ctx.font='800 13px system-ui,sans-serif'; ctx.fillStyle='rgba(255,255,255,.92)';
ctx.fillText('CLICK OR PRESS SPACE TO LAUNCH', W/2, paddle.y-84);
ctx.restore();
}
ctx.restore();
}
let last = performance.now();
function loop(now){
const dt = Math.min(0.033, (now-last)/1000 || 0.016);
last = now;
if(mode!=='paused') update(dt);
draw();
requestAnimationFrame(loop);
}
// Input
function canvasMouseX(e){
const rect = canvas.getBoundingClientRect();
return (e.clientX - rect.left) * (W / rect.width);
}
canvas.addEventListener('mousemove', e=>{
mouseX = canvasMouseX(e); mouseControl = true;
});
canvas.addEventListener('touchmove', e=>{
if(e.touches.length){ mouseX = canvasMouseX(e.touches[0]); mouseControl=true; }
e.preventDefault();
}, {passive:false});
canvas.addEventListener('click', ()=>{
initAudio();
if(mode==='ready') launch();
else if(mode==='attract') startGame();
});
canvas.addEventListener('touchstart', ()=>{ initAudio(); if(mode==='ready') launch(); }, {passive:true});
startOverlay.addEventListener('click', ()=>{ startGame(); });
pauseOverlay.addEventListener('click', ()=>{ if(mode==='paused') togglePause(); });
document.querySelectorAll('.js-restart').forEach(el=>el.addEventListener('click', (e)=>{ e.stopPropagation(); startGame(); }));
document.querySelectorAll('.js-start').forEach(el=>el.addEventListener('click', (e)=>{ e.stopPropagation(); startGame(); }));
window.addEventListener('keydown', e=>{
const k = e.key;
if(k==='ArrowLeft'||k==='ArrowRight'||k===' '||k==='Spacebar') e.preventDefault();
keys[k]=true; keys[e.code]=true;
initAudio();
if(mode==='attract' || mode==='over' || mode==='win'){
// any key starts / restarts (except modifier-only keys)
if(k && k!=='Shift' && k!=='Control' && k!=='Alt' && k!=='Meta'){ startGame(); return; }
}
if(k==='p'||k==='P'||k==='Escape'){ togglePause(); return; }
if(k==='r'||k==='R'){ if(mode!=='attract') startGame(); return; }
if(k===' '||k==='Spacebar'||k==='Enter'){ if(mode==='ready') launch(); else if(mode==='paused') togglePause(); }
});
window.addEventListener('keyup', e=>{ keys[e.key]=false; keys[e.code]=false; });
pauseBtn.addEventListener('click', ()=>{ initAudio(); if(mode==='attract'){ startGame(); return; } togglePause(); });
document.getElementById('resumeBtn').addEventListener('click', togglePause);
document.getElementById('restartBtn').addEventListener('click', ()=>{ initAudio(); startGame(); });
document.getElementById('soundBtn').addEventListener('click', function(){
muted=!muted; this.textContent = muted?'🔇 Muted':'🔊 Sound'; if(!muted){ initAudio(); tone(660,.07,'square',.04); }
});
window.addEventListener('pointerdown', initAudio, {once:true});
resetDemo();
updateHUD();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Reasoning trace (9,125 tokens)
Balancing Breakout mechanics, responsive canvas layout, demo mode activation, and single-file HTML constraints.Adding substepped swept collision, HUD layout, game states, power-ups, particles, and WebAudio beeps.Implementing swept substep collision, brick HP, power-up drops, and input handling.Calculating AABB-circle penetration depth for velocity reflection and repositioning.Extending the state machine to support attract mode overlays and multi-ball spawning with capped instances.Adding substep collision handling with penetration correction and minimum vertical velocity enforcement.Implementing input listeners, rendering pipeline, audio cues, and level layout with tough bricks and powerups.Adding responsive CSS sizing, overlay styling, and input handling for demo and gameplay modes.Implementing substepped ball physics, AI paddle targeting with error, and rendering helpers for bricks and effects.Adding rendering helpers for HUD, overlays, and restart controls with demo mode reset logic.