Muse Ember
by MetaReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 28.5 s
- Cost
- $0.0063
- Output tokens
- 12,493
- Spent thinking
- 3,101 tokens
- Speed
- 476.9 tok/s
- Console errors
- 0
- First token after
- 2.3 s
- First visible text after
- 12.3 s
- Prompt tokens
- 526
- Code size
- 27.1 KB
- Finish reason
- stop
- Served by
- Meta
- Model id
meta/muse-ember- Reasoning sent
{"effort":"medium"}- 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 · 586 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEON BREAKOUT</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
font-family:ui-rounded,system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;
color:#eef2ff;background:
radial-gradient(900px 500px at 20% -10%,rgba(124,58,237,.45),transparent 60%),
radial-gradient(900px 500px at 90% 0%,rgba(8,145,178,.35),transparent 60%),
radial-gradient(700px 600px at 50% 120%,rgba(236,72,153,.25),transparent 60%),
#070812;
min-height:100vh;display:flex;justify-content:center;align-items:flex-start;padding:14px;
}
.card{
width:min(980px,100%);background:rgba(255,255,255,.06);
border:1px solid rgba(255,255,255,.12);border-radius:22px;
box-shadow:0 24px 70px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.08);
padding:14px 16px 12px;backdrop-filter:blur(10px);
}
.top{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
h1{
font-size:clamp(22px,3vw,34px);letter-spacing:.08em;line-height:1;
background:linear-gradient(90deg,#a5b4fc,#f0abfc,#67e8f9);-webkit-background-clip:text;background-clip:text;color:transparent;
text-shadow:0 0 26px rgba(168,85,247,.12);
}
h1 small{display:block;font-size:10px;letter-spacing:.34em;color:#94a3b8;-webkit-text-fill-color:#94a3b8;margin-top:4px}
.buttons{display:flex;gap:8px;flex-wrap:wrap}
button{
border:1px solid rgba(255,255,255,.18);background:linear-gradient(180deg,#334155,#1e293b);color:#fff;
padding:9px 14px;border-radius:999px;font-weight:800;cursor:pointer;letter-spacing:.03em;
box-shadow:0 6px 18px rgba(0,0,0,.35);transition:.15s;
}
button:hover{transform:translateY(-1px);border-color:rgba(165,180,252,.8)}
button.primary{background:linear-gradient(135deg,#7c3aed,#ec4899);border-color:transparent}
.hud{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:12px 0 10px}
.box{
background:rgba(2,6,23,.55);border:1px solid rgba(255,255,255,.09);border-radius:14px;
padding:8px 11px;min-width:0;
}
.label{font-size:9px;letter-spacing:.22em;color:#94a3b8;font-weight:800}
.value{font-size:clamp(16px,2.3vw,24px);font-weight:900;line-height:1.15;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hearts{color:#fb7185;text-shadow:0 0 12px rgba(251,113,133,.7)}
.bar{height:8px;background:rgba(255,255,255,.1);border-radius:99px;overflow:hidden;margin-top:6px}
.bar i{display:block;height:100%;width:0%;background:linear-gradient(90deg,#22d3ee,#a78bfa);border-radius:99px;transition:width .1s linear}
.wrap{position:relative;border-radius:16px;overflow:hidden;border:1px solid rgba(255,255,255,.14);box-shadow:0 14px 40px rgba(0,0,0,.5),inset 0 0 0 1px rgba(0,0,0,.25);background:#0b1026}
canvas{display:block;width:100%;height:auto;aspect-ratio:920/560;touch-action:none;cursor:none}
.overlay{
position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;
background:radial-gradient(circle at center,rgba(15,23,42,.46),rgba(2,6,23,.82));padding:20px;z-index:5;
}
.overlay.hidden{display:none}
.badge{font-size:11px;letter-spacing:.3em;font-weight:900;color:#f0abfc;border:1px solid rgba(240,171,252,.45);padding:6px 10px;border-radius:999px;background:rgba(88,28,135,.25)}
.overlay h2{font-size:clamp(30px,6vw,58px);letter-spacing:.05em;margin:12px 0 8px;line-height:.95}
.overlay p{max-width:560px;color:#cbd5e1;font-size:clamp(13px,2vw,16px);line-height:1.45}
.overlay .bigbtn{margin-top:16px;font-size:16px;padding:13px 24px}
.kbd{display:inline-block;background:#1e293b;border:1px solid #475569;border-bottom-width:3px;border-radius:6px;padding:1px 7px;font-weight:800;color:#fff;font-size:12px}
.legend{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:10px;color:#cbd5e1;font-size:12.5px;line-height:1.5}
.legend b{color:#fff}
.pill{display:inline-flex;align-items:center;gap:6px;background:rgba(2,6,23,.45);border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:5px 9px;margin:2px 0}
.dot{width:13px;height:13px;border-radius:4px;display:inline-block;vertical-align:middle}
.iconpill{width:20px;height:20px;border-radius:50%;background:#fff;color:#111827;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:900;vertical-align:middle}
#pauseTag{position:absolute;top:12px;left:50%;transform:translateX(-50%);background:rgba(2,6,23,.8);border:1px solid rgba(255,255,255,.2);padding:7px 12px;border-radius:999px;font-weight:900;letter-spacing:.12em;display:none;z-index:4}
@media(max-width:640px){
body{padding:8px}.card{padding:10px;border-radius:16px}
.hud{grid-template-columns:repeat(2,1fr)}
.legend{font-size:11.5px}
button{padding:8px 11px}
}
</style>
</head>
<body>
<main class="card">
<div class="top">
<h1>NEON BREAKOUT<small>BRICK BREAKER</small></h1>
<div class="buttons">
<button id="pauseBtn">⏸ Pause</button>
<button id="restartBtn" class="primary">↻ Restart</button>
</div>
</div>
<section class="hud" aria-label="Game status">
<div class="box"><div class="label">SCORE</div><div class="value" id="score">0</div></div>
<div class="box"><div class="label">LIVES</div><div class="value hearts" id="lives">♥♥♥</div></div>
<div class="box"><div class="label">BRICKS LEFT</div><div class="value" id="left">70</div><div class="bar"><i id="prog"></i></div></div>
<div class="box"><div class="label">WIDE PADDLE <span id="wideSec"></span></div><div class="value" id="ballsInfo">1 BALL</div><div class="bar"><i id="wideBar"></i></div></div>
</section>
<div class="wrap" id="wrap">
<canvas id="game" width="920" height="560"></canvas>
<div id="pauseTag">PAUSED — PRESS P TO RESUME</div>
<div class="overlay" id="overlay">
<div class="badge" id="badge">DEMO MODE • AI PLAYING</div>
<h2 id="title">NEON<br>BREAKOUT</h2>
<p id="desc">Watch the AI play, or take control.<br>Move with <b>mouse</b> or <span class="kbd">←</span> <span class="kbd">→</span> • <span class="kbd">Space</span> launch • <span class="kbd">P</span> pause</p>
<button class="bigbtn primary" id="startBtn">▶ CLICK OR PRESS A KEY TO START</button>
</div>
</div>
<div class="legend">
<div>
<span class="pill"><span class="dot" style="background:#f472b6"></span> Normal brick</span>
<span class="pill"><span class="dot" style="background:#cbd5e1;border:1px solid #64748b"></span> Steel brick = <b>2 hits</b> (cracks)</span>
<span class="pill"><span class="iconpill">W</span> Wide paddle · 10s</span>
<span class="pill"><span class="iconpill">×3</span> Multi-ball</span>
</div>
<div style="align-self:center"><b>Center hit</b> = straight up · <b>Edge hit</b> = sharp angle</div>
</div>
</main>
<script>
(() => {
"use strict";
const W = 920, H = 560;
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
const overlay = document.getElementById("overlay");
const badge = document.getElementById("badge"), title = document.getElementById("title"), desc = document.getElementById("desc"), startBtn = document.getElementById("startBtn");
const scoreEl = document.getElementById("score"), livesEl = document.getElementById("lives"), leftEl = document.getElementById("left"), progEl = document.getElementById("prog");
const wideBar = document.getElementById("wideBar"), wideSec = document.getElementById("wideSec"), ballsInfo = document.getElementById("ballsInfo");
const pauseTag = document.getElementById("pauseTag");
const COLS = 10, ROWS = 7;
const brickMarginX = 20, brickTop = 52, brickGap = 6;
const brickW = (W - brickMarginX*2 - (COLS-1)*brickGap) / COLS;
const brickH = 23;
const rowColors = ["#a78bfa","#f472b6","#fb923c","#facc15","#4ade80","#22d3ee","#60a5fa"];
const rowDark = ["#6d28d9","#be185d","#c2410c","#a16207","#15803d","#0e7490","#2563eb"];
// Tough pattern (2 = steel / 2 HP) and power-up placement
const toughPattern = [
[2,1,2,1,1,1,2,1,2,1],
[1,2,1,1,2,1,1,2,1,1],
[1,1,2,1,1,1,2,1,1,2],
[1,1,1,2,1,1,1,2,1,1],
[2,1,1,1,1,1,1,1,1,1],
[1,1,1,1,2,1,1,1,2,1],
[1,1,1,1,1,1,1,1,1,1]
];
const powerMap = new Map([
["0,5","multi"],["1,2","wide"],["2,7","multi"],["3,1","wide"],["4,4","multi"],["5,8","wide"]
]);
const stars = Array.from({length:70},()=>({x:Math.random()*W,y:Math.random()*H,r:Math.random()*1.6+.3,a:Math.random()*.5+.15}));
let state = "demo"; // demo | playing | over | win
let paused = false;
let score = 0, lives = 3;
let bricks = [], balls = [], particles = [], powerups = [], floaters = [];
let paddle, keys = {}, mouseX = W/2, usingMouse = false;
let wideUntil = 0, shake = 0, elapsed = 0, lastTime = performance.now(), serveAt = 0;
let actx = null;
function initAudio(){
try{
if(!actx) actx = new (window.AudioContext||window.webkitAudioContext)();
if(actx.state === "suspended") actx.resume();
}catch(e){}
}
function beep(freq=440,dur=.06,type="square",vol=.035){
if(!actx) return;
try{
const o=actx.createOscillator(), g=actx.createGain();
o.type=type; o.frequency.value=freq;
g.gain.setValueAtTime(vol,actx.currentTime);
g.gain.exponentialRampToValueAtTime(.0001,actx.currentTime+dur);
o.connect(g); g.connect(actx.destination); o.start(); o.stop(actx.currentTime+dur);
}catch(e){}
}
function makeBrick(r,c){
const hp = toughPattern[r][c];
return {
x: brickMarginX + c*(brickW+brickGap),
y: brickTop + r*(brickH+brickGap),
w: brickW, h: brickH, row:r, col:c,
hp, maxHp: hp, alive:true,
color: hp===2 ? "#cbd5e1" : rowColors[r],
dark: hp===2 ? "#64748b" : rowDark[r],
power: powerMap.get(r+","+c) || null,
seed: (r*31+c*17)%100
};
}
function resetGame(){
score=0; lives=3; elapsed=0; wideUntil=0; shake=0;
particles=[]; powerups=[]; floaters=[];
bricks=[];
for(let r=0;r<ROWS;r++) for(let c=0;c<COLS;c++) bricks.push(makeBrick(r,c));
paddle = {x:W/2-60, y:H-36, w:120, normalW:120, wideW:196, h:14, speed:560};
balls = [newBall(W/2, paddle.y-10)];
serveAt = performance.now()+900;
paused=false; pauseTag.style.display="none";
updateHUD();
}
function newBall(x,y){
return {x,y,vx:0,vy:0,r:8,speed:400,stuck:true,trail:[]};
}
function launchBall(b, angleDeg){
const a = (angleDeg ?? (Math.random()*30-15)) * Math.PI/180;
b.vx = Math.sin(a)*b.speed;
b.vy = -Math.cos(a)*b.speed;
b.stuck=false;
}
function setBallAngle(b, rel){
const maxA = Math.PI*0.38; // ~68 degrees
const a = rel*maxA;
b.vx = Math.sin(a)*b.speed;
b.vy = -Math.cos(a)*b.speed;
}
function showOverlay(kind){
overlay.classList.remove("hidden");
if(kind==="start"){
badge.textContent="DEMO MODE • AI PLAYING";
title.innerHTML="NEON<br>BREAKOUT";
desc.innerHTML='Watch the AI play, or take control.<br>Move with <b>mouse</b> or <span class="kbd">←</span> <span class="kbd">→</span> • <span class="kbd">Space</span> launch • <span class="kbd">P</span> pause';
startBtn.textContent="▶ CLICK OR PRESS A KEY TO START";
}else if(kind==="over"){
badge.textContent="GAME OVER";
title.innerHTML="GAME<br>OVER";
desc.innerHTML=`Final score: <b style="color:#fff;font-size:22px">${score}</b> • Time: <b style="color:#fff">${fmtTime(elapsed)}</b><br>Press <span class="kbd">R</span> or click restart to play again.`;
startBtn.textContent="↻ RESTART GAME";
}else if(kind==="win"){
badge.textContent="VICTORY!";
title.innerHTML="YOU<br>WIN! 🏆";
desc.innerHTML=`All bricks cleared! Score: <b style="color:#fff;font-size:22px">${score}</b> • Time: <b style="color:#fff">${fmtTime(elapsed)}</b><br>Press <span class="kbd">R</span> or click restart to play again.`;
startBtn.textContent="↻ PLAY AGAIN";
}
}
function hideOverlay(){ overlay.classList.add("hidden"); }
function fmtTime(s){ const m=Math.floor(s/60), sec=Math.floor(s%60); return m+":"+(sec<10?"0":"")+sec; }
function startGame(){
initAudio(); resetGame(); state="playing"; hideOverlay(); serveAt=performance.now()+650; beep(660,.08);
}
function burst(x,y,color,n=14,power=1){
for(let i=0;i<n;i++){
const a=Math.random()*Math.PI*2, sp=(60+Math.random()*260)*power;
particles.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-40,life:.5+Math.random()*.55,max:1,size:2+Math.random()*5,color,rot:Math.random()*Math.PI,vr:(Math.random()-.5)*12});
}
}
function floater(x,y,text,color="#fff"){ floaters.push({x,y,text,color,life:1}); }
function damageBrick(b, hitX, hitY){
const wasAlive=b.alive;
b.hp--;
if(b.hp>0){
// first hit on steel: crack + feedback
score += 20; floater(hitX,hitY-8,"+20","#e2e8f0");
burst(hitX,hitY,"#e2e8f0",8,.7); beep(220,.07,"square",.04);
return;
}
b.alive=false;
const pts = b.maxHp===2 ? 100 : 50;
score += pts;
floater(b.x+b.w/2,b.y,"+"+pts, b.maxHp===2?"#fde68a":"#fff");
burst(b.x+b.w/2,b.y+b.h/2,b.color, b.maxHp===2?22:15);
burst(b.x+b.w/2,b.y+b.h/2,"#ffffff",6,.7);
beep(b.maxHp===2?520:680,.09,"square",.045);
shake = Math.max(shake, b.maxHp===2?5:2.5);
if(b.power){
powerups.push({x:b.x+b.w/2,y:b.y+b.h/2,vx:0,vy:150,type:b.power,r:14,rot:0});
floater(b.x+b.w/2,b.y-16, b.power==="wide"?"WIDE!":"MULTI-BALL!","#67e8f9");
}
if(bricks.every(x=>!x.alive)){
state="win"; showOverlay("win"); burst(W/2,H/2,"#facc15",70,1.6); burst(W/2,H/2,"#f472b6",50,1.3); beep(880,.18,"square",.05);
}
}
function collideBrick(b, prevX, prevY){
if(!b.alive) return false;
const cx = Math.max(b.x, Math.min(b.x+b.w, b.x));
const cy = Math.max(b.y, Math.min(b.y+b.h, b.y));
const dx = b.x-cx, dy=b.y-cy;
if(dx*dx+dy*dy > b.r*b.r) return false;
let fromLeft=false, fromRight=false, fromTop=false, fromBottom=false;
if(prevX + b.r <= b.x) fromLeft=true;
else if(prevX - b.r >= b.x+b.w) fromRight=true;
else if(prevY + b.r <= b.y) fromTop=true;
else if(prevY - b.r >= b.y+b.h) fromBottom=true;
if(fromLeft){ b.vx=-Math.abs(b.vx); b.x=b.x-b.r-.5; }
else if(fromRight){ b.vx=Math.abs(b.vx); b.x=b.x+b.w+b.r+.5; }
else if(fromTop){ b.vy=-Math.abs(b.vy); b.y=b.y-b.r-.5; }
else if(fromBottom){ b.vy=Math.abs(b.vy); b.y=b.y+b.h+b.r+.5; }
else {
// fallback: smallest penetration
const leftPen = (b.x+b.r)-b.x, rightPen=(b.x+b.w)-(b.x-b.r);
const topPen=(b.y+b.r)-b.y, botPen=(b.y+b.h)-(b.y-b.r);
const m=Math.min(leftPen,rightPen,topPen,botPen);
if(m===leftPen){b.vx=-Math.abs(b.vx);b.x=b.x-b.r-.5;}
else if(m===rightPen){b.vx=Math.abs(b.vx);b.x=b.x+b.w+b.r+.5;}
else if(m===topPen){b.vy=-Math.abs(b.vy);b.y=b.y-b.r-.5;}
else {b.vy=Math.abs(b.vy);b.y=b.y+b.h+b.r+.5;}
}
b.speed=Math.min(580,b.speed+2.5);
const sp=Math.hypot(b.vx,b.vy)||1; b.vx=b.vx/sp*b.speed; b.vy=b.vy/sp*b.speed;
if(Math.abs(b.vy) < b.speed*.22){ b.vy = (b.vy<0?-1:1)*b.speed*.22; const s2=Math.hypot(b.vx,b.vy); b.vx=b.vx/s2*b.speed; b.vy=b.vy/s2*b.speed; }
damageBrick(b,b.x,b.y);
return true;
}
function spawnMulti(from){
if(balls.length>=3) return;
const baseA=Math.atan2(from.vy,from.vx);
[-0.32,0.32].forEach(off=>{
if(balls.length>=3) return;
const nb=newBall(from.x,from.y);
const a=baseA+off;
nb.vx=Math.cos(a)*nb.speed; nb.vy=Math.sin(a)*nb.speed;
if(nb.vy>-40) nb.vy=-Math.abs(nb.vy)-40;
const s=Math.hypot(nb.vx,nb.vy); nb.vx=nb.vx/s*nb.speed; nb.vy=nb.vy/s*nb.speed;
nb.stuck=false; balls.push(nb);
});
}
function update(dt){
if(paused) return;
if(state==="playing") elapsed+=dt;
const now=performance.now();
const isWide = now < wideUntil;
paddle.w = isWide ? paddle.wideW : paddle.normalW;
// Paddle movement
if(state==="demo"){
const targetBall = balls.reduce((best,b)=> (!best || b.y>best.y)?b:best, null);
if(targetBall){
const target = targetBall.x + targetBall.vx*0.18 - paddle.w/2;
const diff = target - paddle.x;
const step = Math.sign(diff)*Math.min(Math.abs(diff), paddle.speed*0.92*dt);
paddle.x += step;
}
}else{
if(usingMouse){
const target = mouseX - paddle.w/2;
paddle.x += (target-paddle.x)*Math.min(1,dt*18);
}
if(keys.ArrowLeft||keys.KeyA) paddle.x -= paddle.speed*dt;
if(keys.ArrowRight||keys.KeyD) paddle.x += paddle.speed*dt;
}
paddle.x = Math.max(0, Math.min(W-paddle.w, paddle.x));
// Serve / auto launch
balls.forEach(b=>{
if(b.stuck){
b.x = paddle.x+paddle.w/2; b.y = paddle.y-b.r-2;
if(now>=serveAt) launchBall(b);
}
});
// Balls with sub-stepping to never tunnel
for(let i=balls.length-1;i>=0;i--){
const b=balls[i];
if(b.stuck) continue;
const dist=Math.hypot(b.vx,b.vy)*dt;
const steps=Math.max(1,Math.ceil(dist/3));
const sdt=dt/steps;
for(let s=0;s<steps;s++){
const prevX=b.x, prevY=b.y;
b.x+=b.vx*sdt; b.y+=b.vy*sdt;
// walls
if(b.x-b.r<0){b.x=b.r;b.vx=Math.abs(b.vx);beep(300,.03,"square",.02);}
if(b.x+b.r>W){b.x=W-b.r;b.vx=-Math.abs(b.vx);beep(300,.03,"square",.02);}
if(b.y-b.r<0){b.y=b.r;b.vy=Math.abs(b.vy);beep(300,.03,"square",.02);}
// paddle (only when coming from above)
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 && prevY+b.r<=paddle.y+8){
const rel=Math.max(-1,Math.min(1,((b.x-(paddle.x+paddle.w/2))/(paddle.w/2))));
setBallAngle(b,rel);
b.y=paddle.y-b.r-.5;
burst(b.x,paddle.y,"#67e8f9",5,.5); beep(440,.05,"square",.03);
}
// bricks
for(const br of bricks){
if(br.alive && collideBrick(b,prevX,prevY)) break;
}
}
b.trail.push({x:b.x,y:b.y}); if(b.trail.length>10) b.trail.shift();
if(b.y-b.r>H+20){
balls.splice(i,1);
if(balls.length===0){
lives--; beep(140,.22,"sawtooth",.05); shake=7;
if(lives<=0){
state="over"; showOverlay("over");
}else{
balls=[newBall(paddle.x+paddle.w/2,paddle.y-10)];
serveAt=performance.now()+ (state==="demo"?700:1100);
}
}
}
}
// Power-ups
for(let i=powerups.length-1;i>=0;i--){
const p=powerups[i]; p.y+=p.vy*dt; p.rot+=dt*2;
if(p.y>H+24){powerups.splice(i,1);continue;}
if(p.y+p.r>paddle.y && p.y-p.r<paddle.y+paddle.h && Math.abs(p.x-(paddle.x+paddle.w/2))<paddle.w/2+p.r){
if(p.type==="wide"){ wideUntil=performance.now()+10000; floater(paddle.x+paddle.w/2,paddle.y-18,"WIDE PADDLE!","#67e8f9"); score+=150; beep(760,.12,"square",.05); }
else { score+=150; spawnMulti(balls[0]||newBall(W/2,200)); floater(paddle.x+paddle.w/2,paddle.y-18,"MULTI-BALL!","#f0abfc"); beep(880,.12,"square",.05); }
burst(p.x,p.y,p.type==="wide"?"#67e8f9":"#f0abfc",16,.8);
powerups.splice(i,1);
}
}
// Particles & floaters
particles.forEach(p=>{p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=420*dt;p.life-=dt;p.rot+=p.vr*dt;});
particles=particles.filter(p=>p.life>0);
floaters.forEach(f=>{f.y-=34*dt;f.life-=dt;});
floaters=floaters.filter(f=>f.life>0);
if(shake>0) shake=Math.max(0,shake-dt*18);
updateHUD();
}
function roundRect(x,y,w,h,r){
ctx.beginPath();
ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath();
}
function drawBrick(b){
if(!b.alive) return;
const isSteel=b.maxHp===2;
const g=ctx.createLinearGradient(b.x,b.y,b.x,b.y+b.h);
g.addColorStop(0,isSteel?"#f1f5f9":b.color); g.addColorStop(1,isSteel?"#94a3b8":b.dark);
ctx.save();
ctx.shadowColor=b.color; ctx.shadowBlur=isSteel?4:10;
ctx.fillStyle=g; roundRect(b.x,b.y,b.w,b.h,5); ctx.fill();
ctx.shadowBlur=0;
ctx.strokeStyle="rgba(255,255,255,.45)"; ctx.lineWidth=1; roundRect(b.x+.5,b.y+.5,b.w-1,b.h-1,5); ctx.stroke();
ctx.fillStyle="rgba(255,255,255,.22)"; roundRect(b.x+3,b.y+2,b.w-6,5,3); ctx.fill();
if(isSteel){
// rivets / steel label
ctx.fillStyle="#475569"; ctx.font="900 10px system-ui"; ctx.textAlign="center"; ctx.textBaseline="middle";
if(b.hp===2) ctx.fillText("STEEL",b.x+b.w/2,b.y+b.h/2+1);
// crack after first hit
if(b.hp===1){
ctx.strokeStyle="rgba(15,23,42,.85)"; ctx.lineWidth=1.7; ctx.beginPath();
ctx.moveTo(b.x+b.w*.28,b.y+3); ctx.lineTo(b.x+b.w*.42,b.y+b.h*.45); ctx.lineTo(b.x+b.w*.34,b.y+b.h-3);
ctx.moveTo(b.x+b.w*.42,b.y+b.h*.45); ctx.lineTo(b.x+b.w*.62,b.y+b.h*.38); ctx.lineTo(b.x+b.w*.72,b.y+b.h-3);
ctx.moveTo(b.x+b.w*.42,b.y+b.h*.45); ctx.lineTo(b.x+b.w*.55,b.y+3); ctx.stroke();
}
}
if(b.power){
const cx=b.x+b.w/2, cy=b.y+b.h/2;
ctx.fillStyle="rgba(255,255,255,.96)"; ctx.beginPath(); ctx.arc(cx,cy,8.5,0,Math.PI*2); ctx.fill();
ctx.fillStyle="#111827"; ctx.font="900 9px system-ui"; ctx.textAlign="center"; ctx.textBaseline="middle";
ctx.fillText(b.power==="wide"?"W":"×3",cx,cy+1);
}
ctx.restore();
}
function drawPower(p){
ctx.save(); ctx.translate(p.x,p.y);
ctx.shadowColor=p.type==="wide"?"#22d3ee":"#e879f9"; ctx.shadowBlur=14;
const g=ctx.createLinearGradient(-14,-14,14,14);
g.addColorStop(0,p.type==="wide"?"#a5f3fc":"#f5d0fe"); g.addColorStop(1,p.type==="wide"?"#0891b2":"#a21caf");
ctx.fillStyle=g; roundRect(-14,-14,28,28,9); ctx.fill();
ctx.shadowBlur=0; ctx.strokeStyle="#fff"; ctx.lineWidth=2; roundRect(-14,-14,28,28,9); ctx.stroke();
ctx.fillStyle="#fff"; ctx.font="900 12px system-ui"; ctx.textAlign="center"; ctx.textBaseline="middle";
ctx.fillText(p.type==="wide"?"W":"×3",0,1);
ctx.restore();
}
function draw(){
ctx.save();
if(shake>0) ctx.translate((Math.random()-.5)*shake,(Math.random()-.5)*shake);
// bg
const bg=ctx.createLinearGradient(0,0,0,H); bg.addColorStop(0,"#0f172a"); bg.addColorStop(.55,"#111827"); bg.addColorStop(1,"#0b1026");
ctx.fillStyle=bg; ctx.fillRect(-10,-10,W+20,H+20);
ctx.fillStyle="rgba(255,255,255,.55)";
stars.forEach(s=>{ctx.globalAlpha=s.a*(0.7+0.3*Math.sin(performance.now()/700+s.x));ctx.fillRect(s.x,s.y,s.r,s.r);});
ctx.globalAlpha=1;
// playfield line
ctx.strokeStyle="rgba(148,163,184,.18)"; ctx.beginPath(); ctx.moveTo(0,brickTop-8); ctx.lineTo(W,brickTop-8); ctx.stroke();
bricks.forEach(drawBrick);
powerups.forEach(drawPower);
// particles
particles.forEach(p=>{
ctx.save(); ctx.translate(p.x,p.y); ctx.rotate(p.rot); ctx.globalAlpha=Math.max(0,p.life/p.max); ctx.fillStyle=p.color;
ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size); ctx.restore();
});
// paddle
const isWide=performance.now()<wideUntil;
const pg=ctx.createLinearGradient(paddle.x,paddle.y,paddle.x+paddle.w,paddle.y+paddle.h);
pg.addColorStop(0,"#22d3ee"); pg.addColorStop(.5,"#a78bfa"); pg.addColorStop(1,"#f472b6");
ctx.save(); ctx.shadowColor="#a78bfa"; ctx.shadowBlur=16; ctx.fillStyle=pg; roundRect(paddle.x,paddle.y,paddle.w,paddle.h,8); ctx.fill(); ctx.restore();
ctx.fillStyle="rgba(255,255,255,.5)"; roundRect(paddle.x+4,paddle.y+2,paddle.w-8,4,2); ctx.fill();
// paddle center marker
ctx.fillStyle="rgba(255,255,255,.65)"; ctx.beginPath(); ctx.arc(paddle.x+paddle.w/2,paddle.y+paddle.h/2,3,0,Math.PI*2); ctx.fill();
// balls + trail
balls.forEach(b=>{
b.trail.forEach((t,i)=>{ctx.globalAlpha=i/b.trail.length*.18;ctx.fillStyle="#a5b4fc";ctx.beginPath();ctx.arc(t.x,t.y,b.r*(i/b.trail.length),0,Math.PI*2);ctx.fill();});
ctx.globalAlpha=1; ctx.save(); ctx.shadowColor="#fff"; ctx.shadowBlur=16;
const bg2=ctx.createRadialGradient(b.x-3,b.y-3,1,b.x,b.y,b.r); bg2.addColorStop(0,"#fff"); bg2.addColorStop(1,"#c4b5fd");
ctx.fillStyle=bg2; ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,Math.PI*2); ctx.fill(); ctx.restore();
});
// floaters
floaters.forEach(f=>{ctx.globalAlpha=Math.max(0,f.life);ctx.fillStyle=f.color;ctx.font="900 15px system-ui";ctx.textAlign="center";ctx.shadowColor="#000";ctx.shadowBlur=6;ctx.fillText(f.text,f.x,f.y);ctx.shadowBlur=0;});
ctx.globalAlpha=1;
if(balls.some(b=>b.stuck) && state!=="over" && state!=="win"){
ctx.fillStyle="rgba(255,255,255,.85)"; ctx.font="800 14px system-ui"; ctx.textAlign="center";
ctx.fillText(state==="demo"?"AI launching…":"CLICK / SPACE TO LAUNCH", W/2, H-70);
}
ctx.restore();
}
function updateHUD(){
scoreEl.textContent=score;
livesEl.textContent="♥".repeat(Math.max(0,lives))+"♡".repeat(Math.max(0,3-lives));
const left=bricks.filter(b=>b.alive).length;
leftEl.textContent=left;
progEl.style.width=(bricks.length?((bricks.length-left)/bricks.length*100):0)+"%";
ballsInfo.textContent=balls.length+(balls.length===1?" BALL":" BALLS");
const now=performance.now();
if(now<wideUntil){
const rem=(wideUntil-now)/1000; wideBar.style.width=(rem/10*100)+"%"; wideSec.textContent="• "+rem.toFixed(1)+"s";
}else{ wideBar.style.width="0%"; wideSec.textContent=""; }
}
function loop(now){
const dt=Math.min(.032,(now-lastTime)/1000); lastTime=now;
if(state!=="over" && state!=="win") update(dt);
draw();
requestAnimationFrame(loop);
}
// Input
function canvasX(clientX){ const r=canvas.getBoundingClientRect(); return (clientX-r.left)*W/r.width; }
canvas.addEventListener("pointermove",e=>{ mouseX=canvasX(e.clientX); usingMouse=true; });
canvas.addEventListener("pointerdown",e=>{
initAudio(); mouseX=canvasX(e.clientX); usingMouse=true;
if(state==="demo"){ startGame(); return; }
if(state==="playing" && !paused){ balls.forEach(b=>{if(b.stuck)launchBall(b);}); }
});
overlay.addEventListener("click",e=>{ e.stopPropagation(); if(state!=="playing") startGame(); });
startBtn.addEventListener("click",e=>{e.stopPropagation(); startGame();});
document.getElementById("restartBtn").addEventListener("click",()=>startGame());
document.getElementById("pauseBtn").addEventListener("click",togglePause);
function togglePause(){
if(state!=="playing") return;
paused=!paused; pauseTag.style.display=paused?"block":"none";
document.getElementById("pauseBtn").textContent=paused?"▶ Resume":"⏸ Pause";
}
window.addEventListener("keydown",e=>{
keys[e.code]=true;
if(["ArrowLeft","ArrowRight","Space"].includes(e.code)) e.preventDefault();
initAudio();
if(state==="demo"){ startGame(); }
else if(state==="playing"){
if(e.code==="Space") balls.forEach(b=>{if(b.stuck)launchBall(b);});
if(e.code==="KeyP"||e.code==="Escape") togglePause();
}else if(state==="over"||state==="win"){
if(e.code==="KeyR"||e.code==="Enter"||e.code==="Space") startGame();
}
if(e.code==="ArrowLeft"||e.code==="ArrowRight"||e.code==="KeyA"||e.code==="KeyD") usingMouse=false;
});
window.addEventListener("keyup",e=>{keys[e.code]=false;});
resetGame();
state="demo"; showOverlay("start");
requestAnimationFrame(t=>{lastTime=t; requestAnimationFrame(loop);});
})();
</script>
</body>
</html>Reasoning trace (3,101 tokens)
Designing a responsive single-file vanilla JS Breakout with scaled logical canvas, AI demo start, and specified gameplay features.Adding collision substeps, brick HP states, multi-ball logic, and AI paddle tracking for demo mode.Defining canvas dimensions, brick layout, colors, scoring, and input handling for demo and player modes.Implementing collision resolution, paddle bounce, scoring, HUD, and input handling for demo and player modes.