Hamburger breakoutOne-line
GLM 5.3 Flash
by Z.aiReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 55.7 s
- Cost
- $0.0035
- Output tokens
- 6,859
- Spent thinking
- 1,280 tokens
- Speed
- 124.3 tok/s
- Console errors
- 0
- First token after
- 478 ms
- First visible text after
- 18.1 s
- Prompt tokens
- 357
- Code size
- 16.2 KB
- Finish reason
- stop
- Served by
- Cohere
- Model id
z-ai/glm-5.3-flash- Reasoning sent
{"effort":"low"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 2026
What it was asked
The prompt
“i want a breakout game where the paddle is a hamburger and every brick you break explodes into tons of little hearts”
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 · 524 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Burger Breakout 💕</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
html, body { height:100%; overflow:hidden; }
body {
background: radial-gradient(circle at 50% 20%, #3a1d4e 0%, #1c0f2e 55%, #120820 100%);
display:flex; align-items:center; justify-content:center;
font-family: 'Segoe UI', 'Trebuchet MS', sans-serif;
touch-action: none;
}
#wrap { position:relative; width:min(96vw, 900px); }
canvas {
display:block; width:100%; height:auto;
border-radius:14px;
box-shadow: 0 0 0 3px rgba(255,150,200,.25), 0 20px 60px rgba(0,0,0,.55);
background:#241238;
cursor:none;
}
#overlay {
position:absolute; inset:0;
display:flex; flex-direction:column; align-items:center; justify-content:center;
text-align:center; pointer-events:none;
transition: opacity .35s;
}
#overlay.hidden { opacity:0; }
.title {
font-size: clamp(28px, 6vw, 52px);
font-weight: 900; letter-spacing: 2px;
color:#ffd9ec;
text-shadow: 0 0 18px rgba(255,80,150,.8), 0 4px 0 rgba(0,0,0,.4);
}
.sub {
margin-top: 10px;
font-size: clamp(14px, 2.4vw, 20px);
color:#ffc4de;
}
.blink {
margin-top: 26px;
font-size: clamp(15px, 2.6vw, 22px);
font-weight: 700;
color:#fff;
background: rgba(255,60,130,.85);
padding: 12px 26px;
border-radius: 999px;
box-shadow: 0 0 24px rgba(255,60,130,.7);
animation: pulse 1.1s ease-in-out infinite;
}
@keyframes pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.07)} }
.hint {
margin-top: 18px; font-size: clamp(11px, 1.8vw, 14px);
color: rgba(255,200,225,.75);
}
</style>
</head>
<body>
<div id="wrap">
<canvas id="c" width="900" height="640"></canvas>
<div id="overlay">
<div class="title">🍔 BURGER BREAKOUT 💕</div>
<div class="sub">Smash the candy bricks — every one bursts into a shower of hearts!</div>
<div class="blink" id="blinkMsg">🍔 Click or press a key to start</div>
<div class="hint">Move: mouse / touch / ← → · the AI is demo-playing right now</div>
</div>
</div>
<script>
(function(){
const cv = document.getElementById('c');
const ctx = cv.getContext('2d');
const W = 900, H = 640;
const overlay = document.getElementById('overlay');
const blinkMsg = document.getElementById('blinkMsg');
// ---------- audio ----------
let actx = null;
function beep(freq, dur, type, vol){
try{
if(!actx) return;
const o = actx.createOscillator(), g = actx.createGain();
o.type = type || 'sine'; o.frequency.value = freq;
g.gain.setValueAtTime(vol||0.08, actx.currentTime);
g.gain.exponentialRampToValueAtTime(0.0001, actx.currentTime + dur);
o.connect(g); g.connect(actx.destination);
o.start(); o.stop(actx.currentTime + dur);
}catch(e){}
}
function initAudio(){
if(actx) return;
try{ actx = new (window.AudioContext || window.webkitAudioContext)(); }catch(e){}
}
// ---------- state ----------
let state = 'demo'; // demo | play | over | win
const paddle = { x: W/2, w: 118, h: 36, targetX: W/2 };
const ball = { x: W/2, y: H/2, r: 8, vx: 3.4, vy: -4.4, speed: 5.6 };
let lives = 3, score = 0, bricksLeft = 0, combo = 0;
const keys = {};
// hearts particles
const hearts = [];
const popups = [];
const trail = [];
// ---------- bricks ----------
const COLS = 10, ROWS = 6;
const BW = 76, BH = 24, GAP = 4;
const BX0 = (W - (COLS*BW + (COLS-1)*GAP)) / 2;
const BY0 = 78;
const rowColors = [
['#ff5d8f','#c23b6b'], ['#ff8f5d','#c25b2b'], ['#ffd45d','#c29a2b'],
['#7ddf8f','#3ba04b'], ['#5db9ff','#2b6cc2'], ['#b47dff','#7b3bc2']
];
const rowPts = [70, 60, 50, 40, 30, 20];
let bricks = [];
function buildBricks(){
bricks = [];
for(let r=0;r<ROWS;r++)
for(let c=0;c<COLS;c++)
bricks.push({ x: BX0 + c*(BW+GAP), y: BY0 + r*(BH+GAP), w: BW, h: BH, row: r, alive: true });
bricksLeft = bricks.length;
}
function resetBall(demo){
ball.x = paddle.x;
ball.y = H - 92;
ball.speed = demo ? 5.4 : 5.8;
const a = -Math.PI/2 + (Math.random()*0.6 - 0.3);
ball.vx = Math.cos(a) * ball.speed;
ball.vy = Math.sin(a) * ball.speed;
trail.length = 0;
}
function resetGame(){
buildBricks();
lives = 3; score = 0; combo = 0;
paddle.x = W/2; paddle.targetX = W/2;
resetBall(true);
hearts.length = 0; popups.length = 0;
}
resetGame();
// ---------- heart drawing ----------
function heartPath(s){
ctx.beginPath();
ctx.moveTo(0, s*0.42);
ctx.bezierCurveTo(-s*0.65, -s*0.08, -s*0.42, -s*0.72, 0, -s*0.28);
ctx.bezierCurveTo(s*0.42, -s*0.72, s*0.65, -s*0.08, 0, s*0.42);
ctx.closePath();
}
function burstHearts(x, y, n, big){
const cols = ['#ff4d88','#ff7aa8','#ffa3c2','#ff2e6e','#ff9ecf','#ff5db0'];
for(let i=0;i<n;i++){
const a = Math.random()*Math.PI*2;
const sp = (big?2.5:1.6) + Math.random()*(big?4.5:3.2);
hearts.push({
x, y,
vx: Math.cos(a)*sp, vy: Math.sin(a)*sp - (big?2.5:1.8),
s: (big?7:4) + Math.random()*(big?10:7),
rot: Math.random()*Math.PI*2,
vr: (Math.random()-0.5)*0.25,
life: 1, decay: 0.008 + Math.random()*0.010,
col: cols[(Math.random()*cols.length)|0]
});
}
if(hearts.length > 900) hearts.splice(0, hearts.length - 900);
}
// ---------- burger paddle ----------
function drawBurger(px, py, w, t){
const x = px - w/2;
const bob = Math.sin(t*0.006)*1.2;
ctx.save();
ctx.translate(0, bob);
// shadow
ctx.fillStyle = 'rgba(0,0,0,.35)';
ctx.beginPath();
ctx.ellipse(px, py + 34, w*0.5, 6, 0, 0, Math.PI*2);
ctx.fill();
// bottom bun
ctx.fillStyle = '#e8a54b';
roundRect(x+6, py+18, w-12, 15, 7); ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,.15)';
roundRect(x+6, py+18, w-12, 6, 4); ctx.fill();
// patty
ctx.fillStyle = '#6b3f23';
roundRect(x+2, py+10, w-4, 11, 5); ctx.fill();
ctx.fillStyle = 'rgba(0,0,0,.2)';
for(let i=0;i<5;i++){ ctx.beginPath(); ctx.arc(x+14+i*(w-28)/4, py+15.5, 1.6, 0, Math.PI*2); ctx.fill(); }
// cheese drips
ctx.fillStyle = '#ffc93c';
ctx.beginPath();
ctx.moveTo(x+4, py+11);
ctx.lineTo(x+w-4, py+11);
ctx.lineTo(x+w-4, py+13);
for(let i=4;i>=0;i--){
const dx = x+4 + i*(w-8)/4;
ctx.lineTo(dx + (w-8)/8, py + 16 + (i%2?4:2));
ctx.lineTo(dx, py+13);
}
ctx.closePath(); ctx.fill();
// lettuce wave
ctx.fillStyle = '#5fc94a';
ctx.beginPath();
ctx.moveTo(x+2, py+10);
ctx.lineTo(x+w-2, py+10);
ctx.lineTo(x+w-2, py+6);
const n = 7;
for(let i=0;i<=n;i++){
const dx = x+w-2 - i*(w-4)/n;
ctx.quadraticCurveTo(dx - (w-4)/(2*n), py + (i%2? 0.5 : 4.5), dx - (w-4)/n, py+6);
}
ctx.closePath(); ctx.fill();
// top bun
ctx.fillStyle = '#f2b45c';
ctx.beginPath();
ctx.moveTo(x+4, py+7);
ctx.quadraticCurveTo(x+4, py-9, px, py-9);
ctx.quadraticCurveTo(x+w-4, py-9, x+w-4, py+7);
ctx.closePath(); ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,.28)';
ctx.beginPath();
ctx.ellipse(px - w*0.18, py-1, w*0.2, 3.5, -0.12, 0, Math.PI*2);
ctx.fill();
// sesame seeds
ctx.fillStyle = '#fff2d6';
const seeds = [[-0.3,-4],[-0.05,-7],[0.22,-4],[0.36,-1],[-0.17,-0.5],[0.05,-2]];
for(const [sx, sy] of seeds){
ctx.save();
ctx.translate(px + sx*w, py + sy);
ctx.rotate(sx*1.2);
ctx.beginPath(); ctx.ellipse(0, 0, 2.6, 1.5, 0, 0, Math.PI*2); ctx.fill();
ctx.restore();
}
ctx.restore();
}
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();
}
// ---------- physics ----------
const PY = H - 74; // paddle top surface y
function update(dt, t){
// paddle control
if(state === 'demo'){
const bias = Math.sin(t*0.0011)*30;
paddle.targetX = ball.x + bias;
paddle.x += (paddle.targetX - paddle.x) * 0.08;
} else {
if(keys['ArrowLeft'] || keys['a']) paddle.x -= 9;
if(keys['ArrowRight'] || keys['d']) paddle.x += 9;
paddle.x += (paddle.targetX - paddle.x) * 0.35;
}
paddle.x = Math.max(paddle.w/2 + 6, Math.min(W - paddle.w/2 - 6, paddle.x));
if(state === 'over' || state === 'win'){
updateParticles(dt);
return;
}
// ball
ball.x += ball.vx * dt;
ball.y += ball.vy * dt;
trail.push({x:ball.x, y:ball.y});
if(trail.length > 14) trail.shift();
// walls
if(ball.x < ball.r + 4){ ball.x = ball.r + 4; ball.vx = Math.abs(ball.vx); beep(300, .06, 'square', .04); }
if(ball.x > W - ball.r - 4){ ball.x = W - ball.r - 4; ball.vx = -Math.abs(ball.vx); beep(300, .06, 'square', .04); }
if(ball.y < ball.r + 4){ ball.y = ball.r + 4; ball.vy = Math.abs(ball.vy); beep(300, .06, 'square', .04); }
// paddle bounce
if(ball.vy > 0 && ball.y + ball.r >= PY && ball.y - ball.r <= PY + 30 &&
ball.x >= paddle.x - paddle.w/2 - ball.r && ball.x <= paddle.x + paddle.w/2 + ball.r){
ball.y = PY - ball.r;
const off = (ball.x - paddle.x) / (paddle.w/2);
const ang = -Math.PI/2 + off * 1.05;
ball.vx = Math.cos(ang) * ball.speed;
ball.vy = Math.sin(ang) * ball.speed;
combo = 0;
beep(180, .08, 'square', .05);
burstHearts(ball.x, PY, 4, false);
}
// bottom
if(ball.y > H + 30){
lives--;
beep(120, .3, 'sawtooth', .07);
if(lives <= 0){
state = 'over';
blinkMsg.textContent = '💀 Game Over — click or press a key to retry';
overlay.classList.remove('hidden');
burstHearts(ball.x, H - 20, 60, true);
} else {
resetBall(state === 'demo');
}
return;
}
// brick collisions
for(const b of bricks){
if(!b.alive) continue;
const nx = Math.max(b.x, Math.min(ball.x, b.x + b.w));
const ny = Math.max(b.y, Math.min(ball.y, b.y + b.h));
const dx = ball.x - nx, dy = ball.y - ny;
if(dx*dx + dy*dy <= ball.r*ball.r){
b.alive = false; bricksLeft--;
combo++;
const pts = rowPts[b.row] * Math.min(combo, 5);
score += pts;
popups.push({ x: b.x + b.w/2, y: b.y, txt: '+' + pts, life: 1, col: rowColors[b.row][0] });
// bounce direction
const hitVert = Math.abs(dy) > Math.abs(dx);
if(hitVert) ball.vy = dy < 0 ? -Math.abs(ball.vy) : Math.abs(ball.vy);
else ball.vx = dx < 0 ? -Math.abs(ball.vx) : Math.abs(ball.vx);
ball.speed = Math.min(ball.speed + 0.06, 8.5);
const sp = Math.hypot(ball.vx, ball.vy);
ball.vx *= ball.speed/sp; ball.vy *= ball.speed/sp;
burstHearts(b.x + b.w/2, b.y + b.h/2, 22, false);
burstHearts(b.x + b.w/2, b.y + b.h/2, 3, true);
beep(520 + b.row*60, .09, 'triangle', .06);
if(bricksLeft <= 0){
state = 'win';
blinkMsg.textContent = '💖 YOU WIN! Click or press a key to play again';
overlay.classList.remove('hidden');
for(let i=0;i<10;i++) burstHearts(Math.random()*W, Math.random()*H*0.6, 14, true);
}
break;
}
}
updateParticles(dt);
}
function updateParticles(dt){
for(let i=hearts.length-1;i>=0;i--){
const h = hearts[i];
h.vy += 0.13*dt;
h.vx *= 0.995;
h.x += h.vx*dt; h.y += h.vy*dt;
h.rot += h.vr*dt;
h.life -= h.decay*dt;
if(h.life <= 0 || h.y > H + 40) hearts.splice(i,1);
}
for(let i=popups.length-1;i>=0;i--){
const p = popups[i];
p.y -= 0.8*dt; p.life -= 0.02*dt;
if(p.life <= 0) popups.splice(i,1);
}
}
// ---------- render ----------
function render(t){
// background
ctx.fillStyle = '#241238';
ctx.fillRect(0, 0, W, H);
const g = ctx.createRadialGradient(W/2, H*0.25, 40, W/2, H*0.25, 620);
g.addColorStop(0, 'rgba(255,90,160,0.10)');
g.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = g;
ctx.fillRect(0, 0, W, H);
// subtle grid dots
ctx.fillStyle = 'rgba(255,255,255,0.03)';
for(let y=30;y<H;y+=40) for(let x=20;x<W;x+=40){ ctx.fillRect(x, y, 2, 2); }
// bricks
for(const b of bricks){
if(!b.alive) continue;
const [c1, c2] = rowColors[b.row];
ctx.fillStyle = c2;
roundRect(b.x+2, b.y+3, b.w, b.h, 6); ctx.fill();
ctx.fillStyle = c1;
roundRect(b.x, b.y, b.w, b.h, 6); ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,.35)';
roundRect(b.x+4, b.y+3, b.w-8, 6, 4); ctx.fill();
// tiny heart icon on brick
ctx.save();
ctx.translate(b.x + b.w/2, b.y + b.h/2 + 1);
ctx.fillStyle = 'rgba(255,255,255,.55)';
heartPath(5); ctx.fill();
ctx.restore();
}
// ball trail
for(let i=0;i<trail.length;i++){
const p = trail[i], a = i/trail.length;
ctx.fillStyle = `rgba(255,220,80,${a*0.35})`;
ctx.beginPath(); ctx.arc(p.x, p.y, ball.r*a, 0, Math.PI*2); ctx.fill();
}
// ball (meatball)
ctx.save();
ctx.shadowColor = 'rgba(255,200,60,.9)'; ctx.shadowBlur = 14;
ctx.fillStyle = '#b06a2c';
ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2); ctx.fill();
ctx.restore();
ctx.fillStyle = 'rgba(255,255,255,.35)';
ctx.beginPath(); ctx.arc(ball.x-2.5, ball.y-2.5, ball.r*0.4, 0, Math.PI*2); ctx.fill();
// hearts particles
for(const h of hearts){
ctx.save();
ctx.globalAlpha = Math.max(0, Math.min(1, h.life*1.4));
ctx.translate(h.x, h.y);
ctx.rotate(h.rot);
ctx.fillStyle = h.col;
heartPath(h.s); ctx.fill();
ctx.restore();
}
// popups
ctx.textAlign = 'center';
for(const p of popups){
ctx.globalAlpha = p.life;
ctx.font = 'bold 17px Trebuchet MS, sans-serif';
ctx.fillStyle = p.col;
ctx.fillText(p.txt, p.x, p.y);
ctx.globalAlpha = 1;
}
// burger paddle
drawBurger(paddle.x, PY, paddle.w, t);
// HUD
ctx.textAlign = 'left';
ctx.font = 'bold 20px Trebuchet MS, sans-serif';
ctx.fillStyle = '#ffd9ec';
ctx.fillText('SCORE ' + score, 20, 38);
ctx.textAlign = 'right';
ctx.fillText('LIVES', W - 110, 38);
for(let i=0;i<3;i++){
ctx.save();
ctx.translate(W - 84 + i*30, 31);
if(i < lives){ ctx.fillStyle = '#ff4d88'; ctx.shadowColor='#ff4d88'; ctx.shadowBlur=8; }
else { ctx.fillStyle = 'rgba(255,255,255,.15)'; }
heartPath(11); ctx.fill();
ctx.restore();
}
if(state === 'demo'){
ctx.textAlign = 'center';
ctx.font = 'italic 15px Trebuchet MS, sans-serif';
ctx.fillStyle = 'rgba(255,200,225,.65)';
ctx.fillText('AI demo mode — grab a bite whenever you like 🍔', W/2, H - 14);
}
}
// ---------- loop ----------
let last = performance.now();
function frame(t){
const dt = Math.min(2.5, (t - last)/16.67);
last = t;
update(dt, t);
render(t);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
// ---------- input ----------
function startPlay(){
initAudio();
if(actx && actx.state === 'suspended') actx.resume();
resetGame();
state = 'play';
overlay.classList.add('hidden');
resetBall(false);
beep(660, .15, 'triangle', .07);
}
window.addEventListener('keydown', e => {
keys[e.key] = true;
if(state === 'demo' || state === 'over' || state === 'win'){
if(['ArrowLeft','ArrowRight','a','d',' '].includes(e.key)) e.preventDefault();
startPlay();
}
});
window.addEventListener('keyup', e => { keys[e.key] = false; });
function pointerX(e){
const rect = cv.getBoundingClientRect();
const cx = (e.touches ? e.touches[0].clientX : e.clientX);
return (cx - rect.left) / rect.width * W;
}
cv.addEventListener('mousemove', e => {
paddle.targetX = pointerX(e);
});
cv.addEventListener('mousedown', () => {
if(state !== 'play') startPlay();
});
cv.addEventListener('touchstart', e => {
e.preventDefault();
paddle.targetX = pointerX(e);
if(state !== 'play') startPlay();
}, {passive:false});
cv.addEventListener('touchmove', e => {
e.preventDefault();
paddle.targetX = pointerX(e);
}, {passive:false});
overlay.classList.remove('hidden');
})();
</script>
</body>
</html>Reasoning trace (1,280 tokens)
Make a Breakout game: paddle is a hamburger (drawn with canvas or CSS), bricks explode into hearts particles. Self-playing demo mode behind "Click or press a key to start" overlay. Single HTML file, canvas-based.
Plan: Canvas 2D. Paddle drawn as a burger (bun top with sesame seeds, lettuce, patty, bun bottom). Bricks colored rows, on break spawn many heart particles (drawn hearts via bezier or two circles + triangle). Ball. Demo AI controls paddle. On click/keydown start real play (or restart). Keep everything visible quickly.
Write compact but polished code. Handle resize, 1280x800 and 375px. Use fixed logical size scaled via canvas.
Implementation details:
- Logical canvas e.g. 800x600, scale to fit with CSS.
- Bricks grid ~ 10 cols x 5 rows.
- Hearts particles: x,y,vx,vy,rot,vr,size,life; gravity; drawn with path: heart shape function.
- Burger paddle: draw at paddle position; width ~110. Layers: bottom bun (rounded rect tan), patty (brown), lettuce (wavy green), top bun (dome tan with sesame seeds white ellipses). Ball could be a tomato slice? Fun: ball is a red meatball or cheese? Keep ball simple glowing white/cheese. Let's make ball a cheese ball (yellow circle). On paddle hit, small bounce.
- Score, lives, title overlay.
- Demo mode: AI tracks ball x with lerp.
- Start overlay: "🍔 BREAKOUT DELUXE — Click or press a key to start". Behind it demo runs.
- After game over/ win: message; click to restart into play mode.
- Mouse/touch controls paddle too.
Ensure no errors, requestAnimationFrame loop.
Heart drawing: use classic param: ctx.moveTo(0, r*0.3); bezier curves. Standard:
function drawHeart(ctx,s){ ctx.beginPath(); ctx.moveTo(0,s*0.35); ctx.bezierCurveTo(s,-s*0.6, s*0.7,-s, 0,-s*0.35)... } Simpler known formula:
ctx.moveTo(0, s*0.3);
ctx.bezierCurveTo(0, 0, -s*0.5, -s*0.3, -s*0.5, 0)? Let's just use well-known heart path scaled:
Heart with size s:
beginPath();
moveTo(x, y + s*0.25);
bezierCurveTo(x, y, x - s*0.5, y - s*0.35, x - s*0.5, y + s*0.05)? Getting fiddly; use the common one:
function heartPath(c, s){
c.beginPath();
c.moveTo(0, s*0.4);
c.bezierCurveTo(-s*0.6, -s*0.1, -s*0.4, -s*0.7, 0, -s*0.25);
c.bezierCurveTo(s*0.4, -s*0.7, s*0.6, -s*0.1, 0, s*0.4);
c.closePath();
}
That's a decent heart with cusp at bottom (0,0.4s) and lobes up top, dip at (0,-0.25s). Good.
Particles: per brick ~ 18 hearts, colors pink/red variations, gravity 0.15, fade out.
Also combo text? Keep simple: score popups "+" maybe. Keep it manageable.
Sounds: WebAudio beeps optional — small blip on brick break. Wrap in try, create AudioContext on first user gesture. Could skip to avoid errors; add safe small sound with lazy init on start click.
Brick colors per row: candy palette.
Lives: 3 hearts displayed as icons.
Game over → show overlay "Game Over — click to play again", demo resumes? Simpler: after game over, restart demo mode after overlay click restarts play.
Structure:
state = 'demo' | 'play' | 'over' | 'win'
Overlay div (HTML) with pointer-events none except message? Use HTML overlay centered text; canvas behind. Click anywhere / keydown: if state demo or over/win → start play (reset). Overlay hides during play.
Ball-brick collision: simple AABB vs circle approx: find nearest point.
Ball speed increase slightly per brick. Ball reset when falls below bottom; lose life; in demo AI just continues.
Win: all bricks cleared → confetti hearts burst everywhere, overlay "You win!".
Write the code.
Paddle burger drawing at (px, py), width w, height ~34. Draw centered on paddle.x, bottom area y = H-40.
Layers top to bottom: top bun dome height ~14, sesame; lettuce wave ~5; patty ~8; tomato? bottom bun ~10 with rounded. Total ~36. Collision top of paddle = py (top surface) for bounce.
Paddle movement: keyboard arrows, mouse move, touch. AI in demo: paddle.x += clamp(ball.x - paddle.x)*speed.
Ball collision with paddle: if ball moving down and intersects paddle rect → reflect vy upward with angle based on hit offset.
Code sizes fine. Let's write.
Canvas logical: W=900, H=640; CSS: canvas max-width 100%, aspect ratio preserved via CSS width: min(96vw, 900px), height auto. Compute scale for pointer coords via getBoundingClientRect.
Bricks: cols=10, rows=6, brick w=76, h=24, top offset 70, gap 4. 10*76+9*4=796 fits in 900 with margin 52.
Hearts particles count per brick: 16 plus 4 bigger. Cap total particles ~600 for perf.
Score popups: small "+50" text floating.
Ball trail: store last positions, draw fading circles — nice.
Write final HTML.