Models 44

Xing4.0 29B A4B (free)

by XingChenReasoning1 console error
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
7 min 6 s
Cost
$0
Output tokens
24,669
Spent thinking
14,953 tokens
Speed
58.2 tok/s
Console errors
1
First token after
1.9 s
First visible text after
5 min 9 s
Prompt tokens
376
Code size
19.9 KB
Finish reason
stop
Served by
Phala
Model id
xingchen-agi/xing4.0-29b-a4b:free
Reasoning sent
model default
Max output
32,000 tokens
Generated
Nov 9, 2026

Console errors

Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.

shakeX is not defined

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 · 745 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hamburger Breakout</title>
<style>
  * { margin:0; padding:0; box-sizing:border-box; }
  html, body { width:100%; height:100%; overflow:hidden; background:#1a1208; }
  body {
    display:flex; justify-content:center; align-items:center;
    font-family:'Segoe UI', system-ui, sans-serif;
  }
  canvas {
    display:block;
    background:#1a1208;
    border-radius:12px;
    box-shadow:0 0 50px rgba(255,170,70,0.18);
    cursor:pointer;
    touch-action:none;
  }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
"use strict";
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let W = 0, H = 0;

function resize() {
  W = canvas.width = window.innerWidth;
  H = canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);

/* ---------- tiny audio ---------- */
let actx = null;
function snd(freq, dur, type='triangle', vol=0.07) {
  try {
    if (!actx) actx = new (window.AudioContext || window.webkitAudioContext)();
    const o = actx.createOscillator(), g = actx.createGain();
    o.type = type; o.frequency.value = freq;
    g.gain.setValueAtTime(vol, 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) {}
}

/* ---------- config ---------- */
const C = {
  paddleW: 140, paddleH: 48,
  ballR: 8,
  rows: 6, cols: 12,
  brickH: 20, pad: 4, gap: 5,
  startSpeed: 5.5, inc: 0.15, maxSpeed: 13,
  lives: 3,
};

/* ---------- globals ---------- */
let paddle, ball, bricks, hearts, particles, floats;
let score, lives, level, frame, state, started, demoMode, paused, shaking, shakeT;

/* ---------- helpers ---------- */
function clamp(v, a, b){ return v < a ? a : (v > b ? b : v); }
function roundRect(x, y, w, h, r) {
  r = Math.min(r, w/2, h/2);
  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();
}

/* ---------- init ---------- */
function buildBricks() {
  const cols = C.cols, rows = C.rows;
  const bw = Math.min(62, (W - 50 - (cols-1)*(C.gap+C.pad)) / cols);
  bricks = [];
  const bTop = 60;
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      const hue = ((r*47 + c*29) % 360);
      bricks.push({
        x: 25 + c*(bw+C.gap),
        y: bTop + r*(C.brickH+C.pad),
        w: bw, h: C.brickH,
        hue: hue,
        alive: true,
        hit: 0,
      });
    }
  }
}

function resetBall() {
  const sp = C.startSpeed + (level-1)*C.inc;
  const ang = -Math.PI/2 + (Math.random()-0.5)*0.5;
  ball.vx = Math.cos(ang)*sp;
  ball.vy = Math.sin(ang)*sp;
  ball.x = paddle.x;
  ball.y = paddle.y - 18;
  ball.trail = [];
}

function reset() {
  buildBricks();
  hearts = []; particles = []; floats = [];
  paddle = {
    x: W/2, y: H-68, w: C.paddleW, h: C.paddleH,
    vx: 0, bobPhase: 0, hitFlash: 0, targetX: W/2,
  };
  ball = { x: W/2, y: H-98, vx:0, vy:0, r: C.ballR, trail:[] };
  score = 0; lives = C.lives; level = 1; frame = 0;
  state = 'menu';
  started = false; demoMode = true; paused = false;
  shaking = 0; shakeT = 0;
}

/* ---------- input ---------- */
let mouseInside = false;
canvas.addEventListener('mouseenter', () => mouseInside = true);
canvas.addEventListener('mouseleave', () => mouseInside = false);
canvas.addEventListener('mousemove', e => {
  if (started && !demoMode) {
    const r = canvas.getBoundingClientRect();
    paddle.targetX = e.clientX - r.left;
  }
});
canvas.addEventListener('mousedown', e => {
  if (state === 'menu') {
    started = true; state = 'play';
    snd(660, 0.1, 'triangle', 0.08);
  } else if (started && demoMode) {
    demoMode = false;
    snd(440, 0.06, 'square', 0.05);
  } else if (state === 'lost') {
    started = false; demoMode = true; reset();
    snd(660, 0.1, 'triangle', 0.08);
  }
});
window.addEventListener('keydown', e => {
  if (!started && (e.key === ' ' || e.key === 'Enter')) {
    started = true; state = 'play';
    snd(660, 0.1, 'triangle', 0.08);
    e.preventDefault();
  }
  if (started && !demoMode && (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A')) {
    paddle.vx -= 4.5; e.preventDefault();
  }
  if (started && !demoMode && (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D')) {
    paddle.vx += 4.5; e.preventDefault();
  }
  if (state === 'lost' && (e.key === 'r' || e.key === 'R')) {
    started = false; demoMode = true; reset();
    snd(660, 0.1, 'triangle', 0.08);
  }
});

/* ---------- demo AI ---------- */
function aiControl() {
  const dx = ball.x - paddle.x;
  const force = clamp(dx, -7, 7) * 0.35;
  paddle.vx += force * 0.06;
  paddle.vx *= 0.78;
}

/* ---------- update ---------- */
function update() {
  frame++;
  if (paused) return;

  if (state === 'menu') {
    // gentle idle anim
    paddle.bobPhase += 0.06;
    return;
  }

  // paddle motion
  if (demoMode) {
    aiControl();
  } else if (mouseInside) {
    paddle.vx += (paddle.targetX - paddle.x) * 0.3;
    paddle.vx *= 0.7;
  } else {
    paddle.vx *= 0.7;
  }
  paddle.x += paddle.vx;
  paddle.vx *= 0.82;
  paddle.x = clamp(paddle.x, paddle.w/2, W - paddle.w/2);
  paddle.y = H - 68 + Math.sin(paddle.bobPhase) * 1.2;
  if (paddle.hitFlash > 0) paddle.hitFlash--;

  // ball motion
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.trail.push({x: ball.x, y: ball.y});
  if (ball.trail.length > 12) ball.trail.shift();

  // walls
  if (ball.x < ball.r) { ball.x = ball.r; ball.vx = Math.abs(ball.vx); snd(520,0.05,'square',0.03); }
  else if (ball.x > W - ball.r) { ball.x = W - ball.r; ball.vx = -Math.abs(ball.vx); snd(520,0.05,'square',0.03); }
  if (ball.y < ball.r) { ball.y = ball.r; ball.vy = Math.abs(ball.vy); snd(520,0.05,'square',0.03); }

  // paddle collision
  if (ball.vy > 0) {
    const dx = ball.x - paddle.x;
    const dy = ball.y - paddle.y;
    if (Math.abs(dx) < paddle.w/2 + ball.r*0.6 && Math.abs(dy) < paddle.h/2 + ball.r*0.8) {
      ball.y = paddle.y - ball.r;
      const hit = dx / (paddle.w/2);
      const ang = -Math.PI/2 + clamp(hit, -0.4, 0.4) * (Math.PI/2.6);
      const sp = Math.hypot(ball.vx, ball.vy);
      ball.vx = Math.cos(ang) * sp;
      ball.vy = Math.sin(ang) * sp;
      paddle.hitFlash = 8;
      snd(720, 0.06, 'triangle', 0.05);
    }
  }

  // brick collisions
  for (const b of bricks) {
    if (!b.alive) continue;
    if (ball.x > b.x && ball.x < b.x+b.w && ball.y > b.y && ball.y < b.y+b.h) {
      const ox = Math.min(ball.x - b.x, b.x + b.w - ball.x);
      const oy = Math.min(ball.y - b.y, b.y + b.h - ball.y);
      if (ox < oy) ball.vx = -ball.vx;
      else ball.vy = -ball.vy;
      b.alive = false;
      score += 10 * level;
      spawnHearts(b.x + b.w/2, b.y + b.h/2, b.hue, b.h);
      shake(7, 9);
      snd(300 + Math.random()*120, 0.08, 'sawtooth', 0.05);
      break;
    }
  }

  // fall off
  if (ball.y > H + 20) {
    lives--;
    if (lives <= 0) {
      state = 'lost';
      snd(180, 0.6, 'sawtooth', 0.1);
    } else {
      resetBall();
      snd(220, 0.15, 'triangle', 0.06);
    }
  }

  // level clear
  if (bricks.every(b => !b.alive)) {
    level++;
    buildBricks();
    resetBall();
    demoMode = true; // resume demo after level transition
    showFloat(W/2, H/2 - 20, `LEVEL ${level}`, '#ffd93d', 80);
    snd(659, 0.12, 'triangle', 0.08);
    setTimeout(()=>snd(784, 0.12, 'triangle', 0.08), 110);
    setTimeout(()=>snd(988, 0.18, 'triangle', 0.08), 220);
  }

  // hearts
  for (let i = hearts.length-1; i >= 0; i--) {
    const h = hearts[i];
    h.x += h.vx;
    h.y += h.vy;
    h.vy += 0.13;
    h.vx *= 0.99;
    h.rot += h.rotSp;
    h.life--;
    if (h.life <= 0) hearts.splice(i, 1);
  }

  // floats
  for (let i = floats.length-1; i >= 0; i--) {
    floats[i].t++;
    floats[i].y -= 0.7;
    if (floats[i].t > floats[i].dur) floats.splice(i, 1);
  }

  if (shaking > 0) {
    shakeT--;
    const k = shakeT / 9;
    shakeX = (Math.random()-0.5) * shaking * k;
    shakeY = (Math.random()-0.5) * shaking * k;
    if (shakeT <= 0) { shaking = 0; shakeX = shakeY = 0; }
  }
}

function spawnHearts(x, y, hueBase, satMul) {
  const count = 16 + Math.floor(Math.random()*4);
  for (let i = 0; i < count; i++) {
    const ang = Math.random() * Math.PI * 2;
    const sp = 1.5 + Math.random()*4;
    hearts.push({
      x: x + (Math.random()-0.5)*18,
      y: y + (Math.random()-0.5)*14,
      vx: Math.cos(ang)*sp,
      vy: Math.sin(ang)*sp - 1.5,
      life: 55 + Math.random()*35,
      maxLife: 90,
      size: 6 + Math.random()*7,
      rot: Math.random()*Math.PI*2,
      rotSp: (Math.random()-0.5)*0.35,
      hue: (hueBase + Math.floor(Math.random()*satMul)) % 360,
    });
  }
}

function showFloat(x, y, text, color, dur=80) {
  floats.push({x, y, text, color, t:0, dur});
}

function shake(amt, t) {
  shaking = Math.max(shaking, amt);
  shakeT = Math.max(shakeT, t);
}

/* ---------- render helpers ---------- */
function drawHeart(h) {
  const lifeRatio = Math.max(0, h.life / h.maxLife);
  const s = Math.max(0.6, h.size * (0.4 + lifeRatio*0.7));
  if (s < 0.5) return;
  ctx.save();
  ctx.translate(h.x, h.y);
  ctx.rotate(h.rot);
  ctx.globalAlpha = Math.min(1, lifeRatio*1.2);
  ctx.shadowColor = `hsl(${h.hue}, 80%, 55%)`;
  ctx.shadowBlur = 12;
  ctx.fillStyle = `hsl(${h.hue}, 75%, 58%)`;
  ctx.beginPath();
  const s2 = Math.max(1, s);
  ctx.moveTo(0, s2*0.3);
  ctx.bezierCurveTo(-s2*0.55, -s2*0.05, -s2*0.55, -s2*0.65, 0, -s2*0.32);
  ctx.bezierCurveTo(s2*0.55, -s2*0.65, s2*0.55, -s2*0.05, 0, s2*0.3);
  ctx.closePath();
  ctx.fill();
  ctx.shadowBlur = 0;
  // shine
  ctx.fillStyle = 'rgba(255,255,255,0.55)';
  ctx.beginPath();
  ctx.ellipse(-s2*0.18, -s2*0.22, s2*0.13, s2*0.07, -0.4, 0, Math.PI*2);
  ctx.fill();
  ctx.restore();
}

function drawLettuce(x, y, w, h) {
  ctx.beginPath();
  ctx.moveTo(x, y + h);
  const steps = 10;
  for (let i = 0; i <= steps; i++) {
    const px = x + (w * i / steps);
    const py = y + h + Math.sin(i * 1.6) * h * 0.35;
    ctx.lineTo(px, py);
  }
  ctx.lineTo(x + w, y);
  ctx.lineTo(x, y);
  ctx.closePath();
  ctx.fill();
  // leaf veins
  ctx.strokeStyle = 'rgba(20,70,0,0.25)';
  ctx.lineWidth = 1;
  for (let i = 0; i <= steps; i++) {
    const px = x + (w * i / steps);
    const py = y + h + Math.sin(i*1.6)*h*0.35;
    ctx.beginPath();
    ctx.moveTo(px, y);
    ctx.lineTo(px, py + h*0.12);
    ctx.stroke();
  }
}

function drawPaddle(p) {
  const x = p.x, y = p.y, w = p.w, h = p.h;

  ctx.save();

  // shadow under
  ctx.fillStyle = 'rgba(0,0,0,0.4)';
  roundRect(x - w/2 + 3, y + h + 2, w, 9, 6);
  ctx.fill();

  // ----- bottom bun -----
  ctx.fillStyle = '#b5723a';
  roundRect(x - w/2, y + h*0.52, w, h*0.42, 9);
  ctx.fill();

  // bottom bun sheen
  ctx.fillStyle = 'rgba(255,220,150,0.25)';
  roundRect(x - w/2 + 3, y + h*0.56, w - 6, h*0.15, 5);
  ctx.fill();

  // ----- patty -----
  ctx.fillStyle = '#5a2d0f';
  roundRect(x - w/2 + 3, y + h*0.40, w - 6, h*0.26, 5);
  ctx.fill();

  // grill marks
  ctx.strokeStyle = 'rgba(0,0,0,0.35)';
  ctx.lineWidth = 1;
  for (let i = 0; i < 3; i++) {
    const mx = x - w/2 + 12 + i * (w-24)/3;
    ctx.beginPath();
    ctx.moveTo(mx, y + h*0.43);
    ctx.lineTo(mx + 7, y + h*0.49);
    ctx.stroke();
  }
  // seasoning specks
  ctx.fillStyle = 'rgba(255,240,180,0.5)';
  for (let i = 0; i < 6; i++) {
    const sx = x - w/2 + 8 + Math.random()*0.6*(w-16);
    const sy = y + h*0.44 + Math.random()*0.6*(h*0.24);
    ctx.beginPath();
    ctx.arc(sx, sy, 0.9, 0, Math.PI*2);
    ctx.fill();
  }

  // ----- lettuce -----
  drawLettuce(x - w/2, y + h*0.30, w, h*0.14);

  // ----- top bun -----
  ctx.fillStyle = '#f0a850';
  ctx.beginPath();
  ctx.moveTo(x - w/2, y + h*0.5);
  ctx.quadraticCurveTo(x - w/2, y + h*0.08, x, y + h*0.06);
  ctx.quadraticCurveTo(x + w/2, y + h*0.08, x + w/2, y + h*0.5);
  ctx.closePath();
  ctx.fill();

  // top bun highlight
  ctx.fillStyle = 'rgba(255,230,160,0.35)';
  ctx.beginPath();
  ctx.ellipse(x, y + h*0.16, w*0.42, h*0.14, 0, 0, Math.PI*2);
  ctx.fill();

  // sesame seeds
  ctx.fillStyle = '#fff5e0';
  const seeds = [
    [x-w*0.30, y+h*0.12], [x-w*0.12, y+h*0.07], [x+w*0.12, y+h*0.09],
    [x+w*0.30, y+h*0.12], [x-w*0.22, y+h*0.20], [x+w*0.22, y+h*0.22],
    [x, y+h*0.10],
  ];
  for (const [sx, sy] of seeds) {
    ctx.beginPath();
    ctx.ellipse(sx, sy, 3.2, 2, -0.25, 0, Math.PI*2);
    ctx.fill();
  }

  // hit flash
  if (p.hitFlash > 0) {
    ctx.fillStyle = `rgba(255,255,210,${(p.hitFlash/8)*0.45})`;
    roundRect(x - w/2, y, w, h, 9);
    ctx.fill();
  }

  ctx.restore();
}

function drawBrick(b) {
  const {x, y, w, h, hue} = b;
  const g = ctx.createLinearGradient(x, y, x, y+h);
  g.addColorStop(0, `hsl(${hue}, 72%, 64%)`);
  g.addColorStop(1, `hsl(${(hue+25)%360}, 68%, 42%)`);
  ctx.fillStyle = g;
  roundRect(x, y, w, h, 6);
  ctx.fill();

  // crosshatch like fries/cheese
  ctx.strokeStyle = 'rgba(0,0,0,0.18)';
  ctx.lineWidth = 1;
  const step = 7;
  for (let i = -h; i < w; i += step) {
    ctx.beginPath();
    ctx.moveTo(x+i, y); ctx.lineTo(x+i+h, y+h);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(x, y+i); ctx.lineTo(x+w, y+i+h);
    ctx.stroke();
  }

  // top highlight
  ctx.fillStyle = 'rgba(255,255,255,0.22)';
  roundRect(x+2, y+2, w-4, 3, 2);
  ctx.fill();
}

function drawHUD() {
  ctx.font = 'bold 20px Segoe UI, sans-serif';
  ctx.textAlign = 'left';
  ctx.lineWidth = 3;
  ctx.strokeStyle = 'rgba(0,0,0,0.65)';
  ctx.fillStyle = '#ffe0a0';
  ctx.strokeText(`SCORE: ${score}`, 15, 30);
  ctx.fillText(`SCORE: ${score}`, 15, 30);

  // lives as hearts
  ctx.textAlign = 'right';
  ctx.fillStyle = '#ff6b8a';
  let lifeStr = '';
  for (let i = 0; i < lives; i++) lifeStr += '♥ ';
  ctx.strokeText(lifeStr, W - 15, 30);
  ctx.fillText(lifeStr, W - 15, 30);

  // level
  ctx.font = 'bold 16px Segoe UI, sans-serif';
  ctx.fillStyle = '#ffd93d';
  ctx.strokeText(`LEVEL ${level}`, W/2, 30);
  ctx.fillText(`LEVEL ${level}`, W/2, 30);

  // demo / manual badge
  ctx.font = '12px Segoe UI, sans-serif';
  ctx.textAlign = 'center';
  ctx.fillStyle = demoMode ? 'rgba(255,217,61,0.7)' : 'rgba(120,200,255,0.7)';
  ctx.fillText(demoMode ? '🤖 DEMO MODE' : 'YOUR TURN', W/2, 50);
}

function drawMenuBg() {
  const grad = ctx.createLinearGradient(0, 0, 0, H);
  grad.addColorStop(0, '#3d2817');
  grad.addColorStop(1, '#1a1208');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, W, H);

  // faint picnic dots
  ctx.fillStyle = 'rgba(255,200,100,0.045)';
  for (let i = 0; i < 35; i++) {
    const x = (i*137 + frame*0.4) % W;
    const y = (i*89 + 17) % H;
    ctx.beginPath();
    ctx.arc(x, y, 2.5, 0, Math.PI*2);
    ctx.fill();
  }

  // ground
  ctx.strokeStyle = 'rgba(255,180,80,0.18)';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(0, H-30); ctx.lineTo(W, H-30);
  ctx.stroke();
}

function drawMenu() {
  drawMenuBg();

  // decorative bricks behind title
  ctx.save();
  for (let i = 0; i < 6; i++) {
    const bx = W/2 - 260 + i * 90;
    const by = H*0.22;
    ctx.fillStyle = `hsl(${i*60}, 70%, 55%)`;
    roundRect(bx, by, 80, 18, 5);
    ctx.fill();
  }
  ctx.restore();

  ctx.textAlign = 'center';

  // title
  ctx.font = 'bold 52px Segoe UI, sans-serif';
  const tg = ctx.createLinearGradient(0, H*0.28, 0, H*0.42);
  tg.addColorStop(0, '#ffd93d');
  tg.addColorStop(1, '#ff8c42');
  ctx.fillStyle = tg;
  ctx.strokeStyle = 'rgba(0,0,0,0.75)';
  ctx.lineWidth = 5;
  ctx.strokeText('HAMBURGER BREAKOUT', W/2, H*0.32);
  ctx.fillText('HAMBURGER BREAKOUT', W/2, H*0.32);

  // subtitle
  ctx.font = '18px Segoe UI, sans-serif';
  ctx.fillStyle = '#ffe0a0';
  ctx.fillText('Break the burgers. Watch them explode into HEARTS.', W/2, H*0.42);

  // start prompt
  if (!started) {
    const pulse = 0.65 + Math.sin(frame*0.09)*0.35;
    ctx.font = 'bold 26px Segoe UI, sans-serif';
    ctx.fillStyle = `rgba(255,255,255,${pulse})`;
    ctx.fillText('CLICK OR PRESS A KEY TO START', W/2, H*0.55);
  } else if (demoMode) {
    ctx.font = '16px Segoe UI, sans-serif';
    ctx.fillStyle = '#ffd93d';
    ctx.fillText('DEMO MODE — watch the AI play', W/2, H*0.55);
  }

  // controls
  ctx.font = '14px Segoe UI, sans-serif';
  ctx.fillStyle = 'rgba(255,255,255,0.55)';
  ctx.fillText('Move mouse or ← / → to steer the hamburger', W/2, H*0.66);
  ctx.fillText('Click anytime to take over, or play yourself', W/2, H*0.70);

  // preview hamburger
  previewPaddle();
}

function previewPaddle() {
  const ix = W/2, iy = H*0.80, iw = Math.min(120, W*0.18), ih = 36;
  ctx.save();
  ctx.fillStyle = 'rgba(0,0,0,0.3)';
  roundRect(ix-iw/2+4, iy+ih+2, iw, 7, 5); ctx.fill();
  ctx.fillStyle = '#f0a850';
  ctx.beginPath();
  ctx.ellipse(ix, iy+ih*0.28, iw/2, ih*0.24, 0, 0, Math.PI*2); ctx.fill();
  ctx.fillStyle = '#5a2d0f';
  ctx.fillRect(ix-iw/2+4, iy+ih*0.45, iw-8, ih*0.18);
  ctx.fillStyle = '#6bb24a';
  ctx.beginPath();
  ctx.moveTo(ix-iw/2, iy+ih*0.44);
  ctx.lineTo(ix+iw/2, iy+ih*0.44);
  ctx.lineTo(ix+iw/2, iy+ih*0.50);
  ctx.lineTo(ix-iw/2, iy+ih*0.50);
  ctx.closePath(); ctx.fill();
  ctx.fillStyle = '#fff5e0';
  for (const s of [[ix-iw*0.3, iy+ih*0.22],[ix+iw*0.3, iy+ih*0.22],[ix, iy+ih*0.18]]) {
    ctx.beginPath(); ctx.ellipse(s[0], s[1], 3, 1.8, -0.2, 0, Math.PI*2); ctx.fill();
  }
  ctx.restore();
}

function drawLost() {
  ctx.fillStyle = 'rgba(0,0,0,0.72)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';
  ctx.font = 'bold 58px Segoe UI, sans-serif';
  ctx.fillStyle = '#ff6b6b';
  ctx.strokeStyle = 'rgba(0,0,0,0.75)';
  ctx.lineWidth = 5;
  ctx.strokeText('GAME OVER', W/2, H/2 - 30);
  ctx.fillText('GAME OVER', W/2, H/2 - 30);

  ctx.font = 'bold 24px Segoe UI, sans-serif';
  ctx.fillStyle = '#ffe0a0';
  ctx.fillText(`FINAL SCORE: ${score}`, W/2, H/2 + 22);

  const pulse = 0.7 + Math.sin(frame*0.1)*0.3;
  ctx.font = 'bold 22px Segoe UI, sans-serif';
  ctx.fillStyle = `rgba(255,255,255,${pulse})`;
  ctx.fillText('Click or press R to try again', W/2, H/2 + 62);
}

/* ---------- main render ---------- */
function render() {
  ctx.save();
  if (shakeX !== 0 || shakeY !== 0) ctx.translate(shakeX, shakeY);

  if (state === 'menu') {
    ctx.restore();
    drawMenu();
    return;
  }

  // background
  const grad = ctx.createLinearGradient(0, 0, 0, H);
  grad.addColorStop(0, '#3d2817');
  grad.addColorStop(1, '#1a1208');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, W, H);

  // picnic dots
  ctx.fillStyle = 'rgba(255,200,100,0.045)';
  for (let i = 0; i < 35; i++) {
    const x = (i*137 + frame*0.4) % W;
    const y = (i*89 + 17) % H;
    ctx.beginPath();
    ctx.arc(x, y, 2.5, 0, Math.PI*2);
    ctx.fill();
  }

  // ground
  ctx.strokeStyle = 'rgba(255,180,80,0.18)';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(0, H-30); ctx.lineTo(W, H-30);
  ctx.stroke();

  // bricks
  for (const b of bricks) if (b.alive) drawBrick(b);

  // ball trail
  for (let i = 0; i < ball.trail.length; i++) {
    const t = ball.trail[i];
    const a = (i/ball.trail.length) * 0.35;
    ctx.fillStyle = `rgba(255,240,200,${a})`;
    ctx.beginPath();
    ctx.arc(t.x, t.y, ball.r * (i/ball.trail.length), 0, Math.PI*2);
    ctx.fill();
  }

  // ball
  ctx.save();
  ctx.shadowColor = 'rgba(255,220,150,0.7)';
  ctx.shadowBlur = 16;
  const bg = ctx.createRadialGradient(ball.x-2, ball.y-2, 1, ball.x, ball.y, ball.r);
  bg.addColorStop(0, '#fffbe8');
  bg.addColorStop(1, '#e8c870');
  ctx.fillStyle = bg;
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
  ctx.fill();
  ctx.restore();

  // hearts
  for (const h of hearts) drawHeart(h);

  // paddle
  drawPaddle(paddle);

  // floating texts
  for (const f of floats) {
    const a = 1 - f.t / f.dur;
    ctx.globalAlpha = Math.max(0, a);
    ctx.font = 'bold 22px Segoe UI, sans-serif';
    ctx.textAlign = 'center';
    ctx.strokeStyle = 'rgba(0,0,0,0.6)';
    ctx.lineWidth = 4;
    ctx.strokeText(f.text, f.x, f.y);
    ctx.fillStyle = f.color;
    ctx.fillText(f.text, f.x, f.y);
    ctx.globalAlpha = 1;
  }

  // HUD
  drawHUD();

  ctx.restore();

  if (state === 'lost') drawLost();
}

/* ---------- loop ---------- */
function loop() {
  update();
  render();
  requestAnimationFrame(loop);
}

reset();
loop();
</script>
</body>
</html>
Reasoning trace (14,953 tokens)
The user wants a Breakout game with a hamburger paddle and bricks that break into lots of little hearts. Let me create a fun, polished Breakout game with these specific requirements:

1. Paddle is a hamburger
2. Every brick explodes into lots of little hearts when broken
3. Works in 1280×800 and adapts to 375px
4. Self-playing demo mode behind a "Click or press a key to start" message
5. No external dependencies (CDN allowed but I'll keep it self-contained)
6. Hearts should be animated/visible
7. Must look great in first seconds

Let me design this carefully:

- Canvas-based Breakout game
- Hamburger paddle: draw with layers (bun, lettuce, patty, sesame seeds)
- Bricks break into hearts that fly outward and fade
- Demo mode: AI auto-plays
- Score display, level display
- Nice visual style with colors

Let me code this up. I'll make it really polished with nice animations for the hearts.

Key design decisions:
- Hamburger paddle drawn with canvas paths
- Hearts as particle effects when bricks break
- Smooth 60fps gameplay
- Start screen overlay
- Responsive layout

Let me think about the heart drawing - I'll create a heart shape function and animate them flying outward from each broken brick.

For the demo AI: simple logic to move paddle toward ball x position.

Let me write the complete code:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hamburger Breakout</title>
<style>
  * { margin:0; padding:0; box-sizing:border-box; }
  body { background:#1a1208; display:flex; justify-content:center; align-items:center; min-height:100vh; font-family:'Segoe UI',system-ui,sans-serif; overflow:hidden; }
  canvas { border-radius:12px; box-shadow:0 0 40px rgba(255,180,80,.15); display:block; max-width:100%; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
// ... full game code
</script>
</body>
</html>
```

Let me write the full game logic now. I need to be thorough here.

Game elements:
- Paddle: hamburger shape (top bun, lettuce, patty, bottom bun with sesame seeds)
- Ball: small white/cream circle with shine
- Bricks: colorful, arranged in grid at top
- Hearts: particle effect when brick breaks, fly outward, rotate, fade
- Score, lives, level

Let me think about the hamburger paddle rendering carefully. I'll draw it with multiple shapes stacked:
- Bottom bun (brown ellipse/trapezoid)
- Patty (dark brown rectangle)
- Lettuce (green wavy)
- Top bun (orange/tan dome with sesame seeds)

For the hearts, I'll create a heart path function and animate particles from the brick position outward in random directions, with rotation and fading.

Let me also add:
- Different colored bricks with patterns
- Power-up? Maybe not, keep it simple but the core request is hearts explosion
- Screen shake on brick break? That would be cool. Maybe subtle.
- Sound effects using Web Audio API? Could be nice but might be complex. Let me add simple beep sounds with Web Audio.

Actually, let me keep it focused on what's requested but make it polished. I'll add:
- Web Audio sound effects (simple)
- Particle hearts explosion
- Brief screen flash/shake on brick break
- Smooth animations

Let me write it all out now. This will be a substantial script.

I'll structure it:
1. Setup canvas, resize handling
2. Game state (menu, playing, demo, paused, won, lost)
3. Entity definitions (ball, paddle, bricks, particles/hearts)
4. Update functions
5. Render functions
6. Input handling (mouse + keyboard for paddle)
7. Demo AI
8. Main loop
9. Start screen

Let me code it now. I want to make sure it's visually impressive and fully functional.

For the hamburger paddle, I want it to look appetizing. Let me draw:
- Bottom bun: a trapezoid that's wider at top, narrowing slightly
- Actually, let me do a rounded rectangle base
- Patty layer: dark brown with slight texture
- Lettuce: green wavy edges
- Top bun: orange-tan dome
- Sesame seeds: small white dots on top bun

The paddle will have a slight bobbing animation.

For bricks, I'll make them with:
- Rounded rectangles with color gradient
- Inner pattern (like fries or cheese chunks)
- Different colors per row maybe

When a brick breaks:
- Spawn 12-18 hearts particles
- Each heart is a filled red heart shape
- They fly outward with velocity, spin, gravity pulls them down slightly, fade over time
- Small white/sparkle along with hearts

Let me also add background - maybe a wheat field or picnic table pattern. Simple gradient + some subtle circles (sky/sun).

Actually let me do a warm wood/picnic theme background - warm brown tones.

OK let me write the complete code now.

```javascript
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

let W, H;
function resize() {
  W = canvas.width = window.innerWidth;
  H = canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);

// Game config
const CONFIG = {
  paddleW: 110,
  paddleH: 45,
  paddleSpeed: 9,
  ballR: 8,
  brickRows: 6,
  brickCols: 11,
  brickPad: 6,
  brickGap: 4,
  startSpeed: 5,
  speedInc: 0.12,
  maxSpeed: 11,
  lives: 3,
};

let paddle, ball, bricks, hearts, score, lives, level, running, started, demoMode;
let shakeX = 0, shakeY = 0, shakeT = 0;

function initGame() {
  paddle = {
    x: W/2, y: H - 60,
    w: CONFIG.paddleW, h: CONFIG.paddleH,
    vx: 0, vy: 0,
    baseY: H - 60,
    bob: 0,
    color: '#e8a04c',
  };
  ball = {
    x: W/2, y: H - 100,
    vx: 0, vy: 0, r: CONFIG.ballR,
    trail: [],
    spin: 0,
  };
  // Create bricks
  bricks = [];
  const cols = CONFIG.brickCols;
  const rows = CONFIG.brickRows;
  const bw = (W - 40 - cols * (CONFIG.brickW + CONFIG.brickGap)) / cols; // need to compute brick width
  // Better: compute based on available space
  const totalW = cols * 70 + (cols - 1) * 4; // approximate
  // Let me just use fixed layout for simplicity
  const brickAreaTop = 70;
  const brickW = Math.min(70, (W - 40) / cols - 4);
  const brickH = 22;
  const brickPad = 4;
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      const hue = (r * 47 + c * 19) % 360;
      bricks.push({
        x: 20 + c * (brickW + brickPad),
        y: brickAreaTop + r * (brickH + brickPad),
        w: brickW, h: brickH,
        color: `hsl(${hue}, 70%, 55%)`,
        hp: 1,
        alive: true,
      });
    }
  }
  hearts = [];
  score = 0;
  lives = CONFIG.lives;
  level = 1;
  running = false;
  demoMode = false;
  resetBall();
}

function resetBall() {
  ball.x = paddle.x;
  ball.y = paddle.y - 20;
  const speed = CONFIG.startSpeed + (level - 1) * CONFIG.speedInc;
  const angle = (-Math.PI/2) + (Math.random() - 0.5) * 0.6;
  ball.vx = Math.cos(angle) * speed;
  ball.vy = Math.sin(angle) * speed;
  ball.trail = [];
}

// ... input handling
// ... update functions
// ... render functions including hamburger paddle and hearts

```

This is getting long. Let me write the complete thing carefully and make sure everything works.

Let me also handle the menu state. When page loads, show "Click or press a key to start". The demo starts immediately when user clicks.

Let me finalize and write the complete HTML file. I'll be careful about:
- Heart particle system
- Hamburger paddle drawing
- Demo AI
- Win/lose conditions
- Level progression

Let me write it now.

Actually, I realize I should double check - the task says "CDN URLs explicitly allowed by the task" but this task doesn't require any CDNs. So I'll keep it fully self-contained.

One more consideration - the ball-paddle collision. When paddle hits, bounce up. Add some english/spin based on where it hits.

Let me write the final code:

```javascript
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let W = 0, H = 0;

function resizeCanvas() {
  W = canvas.width = Math.min(window.innerWidth, 1280);
  H = canvas.height = Math.min(window.innerHeight, 800);
}
resizeCanvas();
window.addEventListener('resize', () => {
  resizeCanvas();
  if (paddle && bricks) {
    // reposition things if needed
    // For simplicity, just scale back
  }
});

// Audio
let audioCtx;
function playSound(freq, dur, type='sine', vol=0.1) {
  try {
    if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.type = type;
    osc.frequency.value = freq;
    gain.gain.setValueAtTime(vol, audioCtx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + dur);
    osc.connect(gain);
    gain.connect(audioCtx.destination);
    osc.start();
    osc.stop(audioCtx.currentTime + dur);
  } catch(e) {}
}

// Constants
const C = {
  paddleW: 115,
  paddleH: 42,
  ballR: 8,
  rows: 6,
  cols: 12,
  brickH: 20,
  pad: 4,
  gap: 4,
  startSpeed: 5.5,
  inc: 0.14,
  maxSpeed: 12,
  lives: 3,
};

let paddle, ball, bricks, hearts, particles, score, lives, level, state, started, paused, won, lost, frame;
let shakeX = 0, shakeY = 0;

function reset() {
  const cols = C.cols;
  const brickW = Math.min(62, (W - 40 - (cols-1)*C.gap) / cols);
  bricks = [];
  const bAreaTop = 60;
  for (let r = 0; r < C.rows; r++) {
    for (let c = 0; c < cols; c++) {
      const hue = ((r*43 + c*23) % 360);
      bricks.push({
        x: 20 + c*(brickW+C.gap),
        y: bAreaTop + r*(C.brickH+C.pad),
        w: brickW, h: C.brickH,
        color: `hsl(${hue}, 72%, 56%)`,
        alive: true,
        breakAnim: 0,
      });
    }
  }
  hearts = [];
  particles = [];
  score = 0;
  lives = C.lives;
  level = 1;
  ball = {
    x: W/2, y: H - 90,
    vx: 0, vy: 0, r: C.ballR,
    trail: [],
  };
  paddle = {
    x: W/2, y: H - 65,
    w: C.paddleW, h: C.paddleH,
    vx: 0,
    bobPhase: 0,
    hitFlash: 0,
  };
  state = 'menu';
  frame = 0;
}

// ... etc
```

OK I'm going to write the whole thing now in one go. Let me be efficient and complete.

Let me design the hearts more carefully:
- Each heart has position, velocity, rotation, rotationSpeed, alpha, scale, size
- When brick breaks, spawn 14-16 hearts at brick position with random velocities in all directions (mostly upward since brick is near top)
- Gravity applies slightly downward
- Hearts fade out over ~1 second
- Draw with glow effect

Heart drawing function:
```javascript
function drawHeart(cx, cy, size, alpha, rotation) {
  ctx.save();
  ctx.translate(cx, cy);
  ctx.rotate(rotation);
  ctx.globalAlpha = alpha;
  ctx.fillStyle = '#ff3b5c';
  ctx.beginPath();
  const s = size;
  ctx.moveTo(0, s*0.3);
  ctx.bezierCurveTo(-s*0.5, -s*0.1, -s*0.5, -s*0.6, 0, -s*0.3);
  ctx.bezierCurveTo(s*0.5, -s*0.6, s*0.5, -s*0.1, 0, s*0.3);
  ctx.closePath();
  ctx.fill();
  // White shine
  ctx.fillStyle = 'rgba(255,255,255,0.6)';
  ctx.beginPath();
  ctx.ellipse(-s*0.15, -s*0.25, s*0.15, s*0.1, -0.3, 0, Math.PI*2);
  ctx.fill();
  ctx.restore();
}
```

For the hamburger paddle, here's my plan:
```javascript
function drawPaddle(p) {
  const x = p.x, y = p.y, w = p.w, h = p.h;
  // Subtle bob
  const bobY = Math.sin(p.bobPhase) * 2;
  
  // Shadow
  ctx.save();
  ctx.shadowColor = 'rgba(0,0,0,0.4)';
  ctx.shadowBlur = 15;
  ctx.shadowOffsetY = 4;
  
  // Bottom bun (trapezoid)
  ctx.fillStyle = '#c68642';
  roundRect(x+w*0.1, y+h*0.5, w*0.8, h*0.45, 8);
  ctx.fill();
  
  // Patty
  ctx.fillStyle = '#6b3410';
  roundRect(x+w*0.1, y+h*0.6, w*0.8, h*0.3, 5);
  ctx.fill();
  
  // Lettuce
  ctx.fillStyle = '#6db84f';
  drawLettuce(x, y, w, h);
  
  // Top bun
  ctx.fillStyle = '#f0a850';
  ctx.beginPath();
  ctx.moveTo(x, y+h*0.7);
  ctx.quadraticCurveTo(x, y+h*0.2, x+w*0.5, y+h*0.15);
  ctx.quadraticCurveTo(x+w, y+h*0.2, x+w, y+h*0.55);
  ctx.lineTo(x+w, y+h*0.7);
  ctx.closePath();
  ctx.fill();
  
  // Sesame seeds on top bun
  ctx.fillStyle = '#fff5e0';
  for (let i = 0; i < 5; i++) {
    const sx = x + w*(0.25 + i*0.12);
    const sy = y + h*(0.25 + Math.sin(i)*0.05);
    ctx.beginPath();
    ctx.ellipse(sx, sy, 3, 2, 0.3, 0, Math.PI*2);
    ctx.fill();
  }
  
  ctx.restore();
}
```

Wait, I need to think about this differently. The paddle is the entire thing. Let me make the paddle bigger so it looks like an actual hamburger sitting at the bottom of the screen.

Actually the standard breakout paddle is wide and thin. A hamburger... could be wide like a burger bun with the patty/lettuce on top/bottom. Let me make it wider - like 130px wide and 45px tall. The "meat" would be visible above and below in a stylized way.

Let me reconsider the drawing:
- Top: a dome/arc representing the top bun
- Then visible meat layers
- Bottom: patty with bottom bun hem

But actually a burger has bun on top only typically. For a paddle that you hold/move... it could be a side view of a burger facing left or right. But for gameplay clarity, let me do a front view hamburger block.

Front view hamburger:
- Top bun (orange dome)
- Patty (dark brown rectangle in middle)
- Lettuce (green wavy strip)
- Bottom bun (darker orange, flattened)
- Sesame seeds scattered on top bun

I'll make the whole thing the paddle. It can be ~130×50.

Let me code this properly now. Writing the complete file.

Actually, looking again at the request - "the paddle is a hamburger" - I'll go with the front-facing hamburger interpretation. It's clear and fun.

Let me write the complete solution now. I'll keep all the CSS and JS inline.

Final plan:
- Dark wood/picnic themed background with warm tones
- Canvas-based breakout
- Hamburger paddle with layers
- Hearts explosion on brick break
- Menu overlay with instructions
- Demo mode AI that plays automatically
- Score, lives, level display
- Clean UI text

Let me write it all now.

One more thought - I want the menu to show "CLICK OR PRESS A KEY TO START" prominently. And when playing, show Hamburger Breakout title and brief controls hint.

Also when all bricks cleared, show "LEVEL CLEAR!" and continue to next level with more speed and denser bricks.

When lives run out, show "GAME OVER" with restart button.

OK writing now. Full code:

Let me structure my JavaScript cleanly:

```javascript
// === Setup ===
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let W = 0, H = 0;
function resize() {
  W = canvas.width = window.innerWidth;
  H = canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);

// === Audio ===
let actx = null;
function snd(freq, dur, type='triangle', vol=0.08) {
  try {
    if (!actx) actx = new (window.AudioContext||window.webkitAudioContext)();
    const o = actx.createOscillator(), g = actx.createGain();
    o.type = type; o.frequency.value = freq;
    g.gain.setValueAtTime(vol, actx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.001, actx.currentTime+dur);
    o.connect(g); g.connect(actx.destination);
    o.start(); o.stop(actx.currentTime+dur);
  } catch(e) {}
}

// === Config ===
const C = {
  paddleW: 130, paddleH: 50,
  ballR: 8,
  rows: 6, cols: 12,
  brickH: 20, pad: 4, gap: 5,
  startSpeed: 5.5, inc: 0.15, maxSpeed: 13,
  lives: 3,
};

let paddle, ball, bricks, hearts, particles, floats;
let score, lives, level, frame, state, started, paused, shaking, shakeT;

// === Init ===
function buildBricks() {
  const cols = C.cols, rows = C.rows;
  const bw = Math.min(60, (W - 50 - (cols-1)*(C.gap+C.pad))/cols);
  bricks = [];
  const bTop = 60;
  for (let r=0;r<rows;r++){
    for (let c=0;c<cols;c++){
      const hue = ((r*47 + c*29)%360);
      bricks.push({
        x: 25 + c*(bw+C.gap),
        y: bTop + r*(C.brickH+C.pad),
        w: bw, h: C.brickH,
        hue: hue,
        alive: true,
        hit: 0,
      });
    }
  }
}

function resetBall() {
  const sp = C.startSpeed + (level-1)*C.inc;
  const ang = (-Math.PI/2) + (Math.random()-0.5)*0.5;
  ball.vx = Math.cos(ang)*sp;
  ball.vy = Math.sin(ang)*sp;
  ball.x = paddle.x;
  ball.y = paddle.y - 18;
  ball.trail = [];
}

function reset() {
  buildBricks();
  hearts = []; particles = []; floats = [];
  paddle = { x: W/2, y: H-65, w: C.paddleW, h: C.paddleH, vx:0, bob:0, hitFlash:0, face:1 };
  ball = { x: W/2, y: H-95, vx:0, vy:0, r: C.ballR, trail:[] };
  score = 0; lives = C.lives; level = 1; frame = 0;
  state = 'menu';
  started = false; paused = false;
  shaking = 0; shakeT = 0;
}

// === Input ===
let mouseX = W/2, mouseDown = false;
canvas.addEventListener('mousemove', e => {
  const r = canvas.getBoundingClientRect();
  mouseX = e.clientX - r.left;
});
canvas.addEventListener('mousedown', e => {
  mouseX = e.clientX - canvas.getBoundingClientRect().left;
  mouseDown = true;
  if (!started && state === 'menu') startGame(false); // demo mode
});
canvas.addEventListener('mouseup', () => mouseDown = false);

window.addEventListener('keydown', e => {
  if (e.key === ' ' || e.key === 'Enter') {
    if (!started) startGame(true);
  } else if (e.key === 'p' || e.key === 'P') {
    paused = !paused;
  }
});

function startGame(demo) {
  if (!started) {
    started = true;
    if (demo) demoMode = true;
    state = 'play';
    snd(523, 0.1);
  }
}

let demoMode = true; // starts in demo mode by default

// === Update ===
function update(dt) {
  frame++;
  
  if (state === 'menu') return;
  if (paused) return;
  
  // Demo AI
  if (demoMode) {
    const target = ball.x;
    const dx = target - paddle.x;
    paddle.vx += clamp(dx, -6, 6) * 0.3;
    paddle.vx *= 0.8;
  } else {
    // Player control via mouse (handled in event listeners, but also allow arrow keys)
  }
  
  // Move paddle
  paddle.x += paddle.vx;
  paddle.vx *= 0.7;
  paddle.x = clamp(paddle.x, paddle.w/2, W - paddle.w/2);
  paddle.y = paddle.baseY ?? (H - 65);
  paddle.bobPhase += 0.08;
  if (paddle.hitFlash > 0) paddle.hitFlash--;
  
  // Ball movement
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.trail.push({x: ball.x, y: ball.y});
  if (ball.trail.length > 12) ball.trail.shift();
  
  // Wall collisions
  if (ball.x < ball.r) { ball.x = ball.r; ball.vx = Math.abs(ball.vx); snd(440,0.05,'square',0.04); }
  if (ball.x > W - ball.r) { ball.x = W - ball.r; ball.vx = -Math.abs(ball.vx); snd(440,0.05,'square',0.04); }
  if (ball.y < ball.r) { ball.y = ball.r; ball.vy = Math.abs(ball.vy); snd(440,0.05,'square',0.04); }
  
  // Ball vs paddle
  if (ball.vy > 0) {
    const dx = ball.x - paddle.x;
    const dy = ball.y - paddle.y;
    if (Math.abs(dx) < paddle.w/2 + ball.r*0.6 && Math.abs(dy) < paddle.h/2 + ball.r) {
      ball.y = paddle.y - ball.r;
      const hit = dx / (paddle.w/2); // -1..1
      const ang = -Math.PI/2 + hit * (Math.PI/3);
      const sp = Math.hypot(ball.vx, ball.vy);
      ball.vx = Math.cos(ang) * sp;
      ball.vy = Math.sin(ang) * sp;
      paddle.hitFlash = 8;
      snd(660, 0.06, 'triangle', 0.05);
    }
  }
  
  // Ball vs bricks
  for (const b of bricks) {
    if (!b.alive) continue;
    if (ball.x > b.x && ball.x < b.x+b.w && ball.y > b.y && ball.y < b.y+b.h) {
      // Determine collision side
      const overlapX = Math.min(ball.x - b.x, b.x+b.w - ball.x);
      const overlapY = Math.min(ball.y - b.y, b.y+b.h - ball.y);
      if (overlapX < overlapY) {
        ball.vx = -ball.vx;
      } else {
        ball.vy = -ball.vy;
      }
      b.alive = false;
      score += 10;
      spawnHearts(b.x + b.w/2, b.y + b.h/2, b.hue);
      shake(6, 8);
      snd(330, 0.08, 'sawtooth', 0.06);
      break;
    }
  }
  
  // Ball falls below
  if (ball.y > H + 20) {
    lives--;
    if (lives <= 0) {
      state = 'lost';
      snd(200, 0.5, 'sawtooth', 0.1);
    } else {
      resetBall();
    }
  }
  
  // Check level clear
  if (bricks.every(b => !b.alive)) {
    level++;
    buildBricks();
    resetBall();
    demoMode = false;
    showFloat(W/2, H/2, `LEVEL ${level}`, '#ffd93d', 60);
    snd(659, 0.15, 'triangle', 0.08);
    setTimeout(()=>snd(784, 0.15, 'triangle', 0.08), 100);
    setTimeout(()=>snd(988, 0.2, 'triangle', 0.08), 200);
  }
  
  // Update hearts
  for (let i = hearts.length-1; i >= 0; i--) {
    const h = hearts[i];
    h.x += h.vx;
    h.y += h.vy;
    h.vy += 0.12; // gravity
    h.vx *= 0.99;
    h.life -= 1;
    if (h.life <= 0) hearts.splice(i, 1);
  }
  
  // Update float texts
  for (let i = floats.length-1; i >= 0; i--) {
    floats[i].t += dt;
    floats[i].y -= 0.8;
    if (floats[i].t > floats[i].dur) floats.splice(i, 1);
  }
  
  if (shaking > 0) {
    shakeT--;
    shakeX = (Math.random()-0.5) * shaking * (shakeT/8);
    shakeY = (Math.random()-0.5) * shaking * (shakeT/8);
    if (shakeT <= 0) { shaking = 0; shakeX = 0; shakeY = 0; }
  }
}

function spawnHearts(x, y, hueShift) {
  const count = 14 + Math.floor(Math.random()*4);
  for (let i = 0; i < count; i++) {
    const ang = Math.random() * Math.PI * 2;
    const sp = 1.5 + Math.random()*3.5;
    hearts.push({
      x: x + (Math.random()-0.5)*20,
      y: y + (Math.random()-0.5)*20,
      vx: Math.cos(ang)*sp,
      vy: Math.sin(ang)*sp - 1,
      life: 50 + Math.random()*30,
      size: 6 + Math.random()*7,
      rot: Math.random()*Math.PI*2,
      rotSp: (Math.random()-0.5)*0.3,
      hue: hueShift,
    });
  }
}

function showFloat(x, y, text, color, dur=70) {
  floats.push({x, y, text, color, t:0, dur});
}

function shake(amt, t) {
  shaking = Math.max(shaking, amt);
  shakeT = Math.max(shakeT, t);
}

function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }

// === Render ===
function render() {
  ctx.save();
  if (shakeX !== 0 || shakeY !== 0) {
    ctx.translate(shakeX, shakeY);
  }
  
  // Background
  const grad = ctx.createLinearGradient(0, 0, 0, H);
  grad.addColorStop(0, '#3d2817');
  grad.addColorStop(1, '#1a1208');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, W, H);
  
  // Subtle picnic dots
  ctx.fillStyle = 'rgba(255,200,100,0.04)';
  for (let i = 0; i < 30; i++) {
    const x = (i*137 + frame*0.3) % W;
    const y = (i*89) % H;
    ctx.beginPath();
    ctx.arc(x, y, 3, 0, Math.PI*2);
    ctx.fill();
  }
  
  // Floor line
  ctx.strokeStyle = 'rgba(255,180,80,0.15)';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(0, H-30);
  ctx.lineTo(W, H-30);
  ctx.stroke();
  
  // Bricks
  for (const b of bricks) {
    if (!b.alive) continue;
    drawBrick(b);
  }
  
  // Ball trail
  for (let i = 0; i < ball.trail.length; i++) {
    const t = ball.trail[i];
    const a = (i/ball.trail.length) * 0.4;
    ctx.fillStyle = `rgba(255,240,200,${a})`;
    ctx.beginPath();
    ctx.arc(t.x, t.y, ball.r * (i/ball.trail.length), 0, Math.PI*2);
    ctx.fill();
  }
  
  // Ball
  ctx.save();
  ctx.shadowColor = 'rgba(255,220,150,0.6)';
  ctx.shadowBlur = 15;
  const bg = ctx.createRadialGradient(ball.x-2, ball.y-2, 1, ball.x, ball.y, ball.r);
  bg.addColorStop(0, '#fffbe8');
  bg.addColorStop(1, '#e8c870');
  ctx.fillStyle = bg;
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
  ctx.fill();
  ctx.restore();
  
  // Hearts
  for (const h of hearts) {
    drawHeart(h);
  }
  
  // Paddle (hamburger)
  drawPaddle(paddle);
  
  // Floats
  for (const f of floats) {
    const a = 1 - f.t/f.dur;
    ctx.globalAlpha = a;
    ctx.font = 'bold 22px Segoe UI, sans-serif';
    ctx.textAlign = 'center';
    ctx.fillStyle = f.color;
    ctx.strokeStyle = 'rgba(0,0,0,0.5)';
    ctx.lineWidth = 4;
    ctx.strokeText(f.text, f.x, f.y);
    ctx.fillText(f.text, f.x, f.y);
    ctx.globalAlpha = 1;
  }
  
  // HUD
  drawHUD();
  
  // State overlays
  if (state === 'menu') drawMenu();
  else if (state === 'lost') drawLost();
  
  ctx.restore();
}

function drawBrick(b) {
  const {x, y, w, h, hue} = b;
  const g = ctx.createLinearGradient(x, y, x, y+h);
  g.addColorStop(0, `hsl(${hue}, 75%, 62%)`);
  g.addColorStop(1, `hsl(${(hue+20)%360}, 70%, 42%)`);
  ctx.fillStyle = g;
  roundRect(x, y, w, h, 6);
  ctx.fill();
  // Inner stripe pattern (looks like cheese/fries crosshatch)
  ctx.strokeStyle = 'rgba(0,0,0,0.15)';
  ctx.lineWidth = 1;
  for (let i = -h; i < w; i += 6) {
    ctx.beginPath();
    ctx.moveTo(x+i, y);
    ctx.lineTo(x+i+h, y+h);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(x, y+i);
    ctx.lineTo(x+w, y+i+h);
    ctx.stroke();
  }
  // Highlight
  ctx.fillStyle = 'rgba(255,255,255,0.2)';
  roundRect(x+2, y+2, w-4, 3, 2);
  ctx.fill();
}

function roundRect(x, y, w, h, r) {
  r = Math.min(r, w/2, h/2);
  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 drawHeart(h) {
  const s = h.size * (h.life / 60);
  if (s < 0.5) return;
  ctx.save();
  ctx.translate(h.x, h.y);
  ctx.rotate(h.rot);
  ctx.globalAlpha = Math.min(1, h.life/50);
  // Glow
  ctx.shadowColor = `hsl(${h.hue}, 80%, 50%)`;
  ctx.shadowBlur = 10;
  ctx.fillStyle = `hsl(${h.hue}, 75%, 55%)`;
  ctx.beginPath();
  const s2 = Math.max(1, s);
  ctx.moveTo(0, s2*0.3);
  ctx.bezierCurveTo(-s2*0.55, -s2*0.05, -s2*0.55, -s2*0.65, 0, -s2*0.32);
  ctx.bezierCurveTo(s2*0.55, -s2*0.65, s2*0.55, -s2*0.05, 0, s2*0.3);
  ctx.closePath();
  ctx.fill();
  // Shine
  ctx.shadowBlur = 0;
  ctx.fillStyle = 'rgba(255,255,255,0.5)';
  ctx.beginPath();
  ctx.ellipse(-s2*0.18, -s2*0.22, s2*0.12, s2*0.07, -0.4, 0, Math.PI*2);
  ctx.fill();
  ctx.restore();
}

function drawPaddle(p) {
  const x = p.x, y = p.y, w = p.w, h = p.h;
  const bob = Math.sin(p.bobPhase) * 1.5;
  
  ctx.save();
  
  // Shadow under paddle
  ctx.fillStyle = 'rgba(0,0,0,0.35)';
  roundRect(x - w/2 + 3, y + h + 3, w, 8, 6);
  ctx.fill();
  
  // === Bottom bun ===
  ctx.fillStyle = '#b5723a';
  roundRect(x - w/2, y + h*0.55, w, h*0.35, 8);
  ctx.fill();
  
  // Bottom bun darker
  ctx.fillStyle = '#8a4f24';
  roundRect(x - w/2, y + h*0.65, w, h*0.15, 6);
  ctx.fill();
  
  // === Patty ===
  ctx.fillStyle = '#5a2d0f';
  roundRect(x - w/2 + 2, y + h*0.42, w - 4, h*0.2, 5);
  ctx.fill();
  // Patty grill marks
  ctx.strokeStyle = 'rgba(0,0,0,0.3)';
  ctx.lineWidth = 1;
  for (let i = 0; i < 3; i++) {
    const mx = x - w/2 + 10 + i * (w-20)/3;
    ctx.beginPath();
    ctx.moveTo(mx, y + h*0.45);
    ctx.lineTo(mx + 6, y + h*0.48);
    ctx.stroke();
  }
  
  // === Lettuce ===
  ctx.fillStyle = '#6bb24a';
  drawLettuce(x - w/2, y + h*0.3, w, h*0.13);
  
  // === Top bun ===
  ctx.fillStyle = '#f0a850';
  ctx.beginPath();
  ctx.moveTo(x - w/2, y + h*0.5);
  ctx.quadraticCurveTo(x - w/2, y + h*0.1, x, y + h*0.08);
  ctx.quadraticCurveTo(x + w/2, y + h*0.1, x + w/2, y + h*0.5);
  ctx.closePath();
  ctx.fill();
  
  // Bun top highlight
  ctx.fillStyle = 'rgba(255,220,150,0.35)';
  ctx.beginPath();
  ctx.ellipse(x, y + h*0.18, w*0.42, h*0.15, 0, 0, Math.PI*2);
  ctx.fill();
  
  // Sesame seeds
  ctx.fillStyle = '#fff5e0';
  const seeds = [
    [x-w*0.3, y+h*0.12], [x-w*0.1, y+h*0.08], [x+w*0.12, y+h*0.09],
    [x+w*0.3, y+h*0.12], [x-w*0.2, y+h*0.18], [x+w*0.2, y+h*0.2]
  ];
  for (const [sx, sy] of seeds) {
    ctx.beginPath();
    ctx.ellipse(sx, sy, 3, 2, -0.2, 0, Math.PI*2);
    ctx.fill();
  }
  
  // Hit flash
  if (p.hitFlash > 0) {
    ctx.fillStyle = `rgba(255,255,200,${p.hitFlash/8 * 0.4})`;
    roundRect(x - w/2, y, w, h, 8);
    ctx.fill();
  }
  
  ctx.restore();
}

function drawLettuce(x, y, w, h) {
  ctx.beginPath();
  ctx.moveTo(x, y + h);
  const steps = 8;
  for (let i = 0; i <= steps; i++) {
    const px = x + (w * i / steps);
    const py = y + h + Math.sin(i * 1.5) * h * 0.3;
    ctx.lineTo(px, py);
  }
  ctx.lineTo(x + w, y);
  ctx.lineTo(x, y);
  ctx.closePath();
  ctx.fill();
  // Leaf detail
  ctx.strokeStyle = 'rgba(0,80,0,0.2)';
  ctx.lineWidth = 1;
  for (let i = 0; i <= steps; i++) {
    const px = x + (w * i / steps);
    const py = y + h + Math.sin(i*1.5)*h*0.3;
    ctx.beginPath();
    ctx.moveTo(px, y);
    ctx.lineTo(px, py + h*0.1);
    ctx.stroke();
  }
}

function drawHUD() {
  // Score
  ctx.font = 'bold 20px Segoe UI, sans-serif';
  ctx.textAlign = 'left';
  ctx.fillStyle = '#ffe0a0';
  ctx.strokeStyle = 'rgba(0,0,0,0.6)';
  ctx.lineWidth = 3;
  ctx.strokeText(`SCORE: ${score}`, 15, 28);
  ctx.fillText(`SCORE: ${score}`, 15, 28);
  
  // Lives as hearts
  ctx.textAlign = 'right';
  ctx.fillStyle = '#ff6b8a';
  ctx.strokeStyle = 'rgba(0,0,0,0.6)';
  let lifeStr = '';
  for (let i = 0; i < lives; i++) lifeStr += '♥ ';
  ctx.strokeText(lifeStr, W - 15, 28);
  ctx.fillText(lifeStr, W - 15, 28);
  
  // Level
  ctx.font = 'bold 16px Segoe UI, sans-serif';
  ctx.fillStyle = '#ffd93d';
  ctx.strokeText(`LEVEL ${level}`, W/2, 28);
  ctx.fillText(`LEVEL ${level}`, W/2, 28);
}

function drawMenu() {
  ctx.fillStyle = 'rgba(0,0,0,0.65)';
  ctx.fillRect(0, 0, W, H);
  
  // Title
  ctx.textAlign = 'center';
  ctx.font = 'bold 52px Segoe UI, sans-serif';
  const tg = ctx.createLinearGradient(0, H*0.3, 0, H*0.45);
  tg.addColorStop(0, '#ffd93d');
  tg.addColorStop(1, '#ff8c42');
  ctx.fillStyle = tg;
  ctx.strokeStyle = 'rgba(0,0,0,0.7)';
  ctx.lineWidth = 5;
  ctx.strokeText('HAMBURGER BREAKOUT', W/2, H*0.32);
  ctx.fillText('HAMBURGER BREAKOUT', W/2, H*0.32);
  
  // Subtitle
  ctx.font = '18px Segoe UI, sans-serif';
  ctx.fillStyle = '#ffe0a0';
  ctx.fillText('Break bricks and watch them explode into HEARTS!', W/2, H*0.43);
  
  // Start prompt
  if (!started) {
    const pulse = 0.7 + Math.sin(frame*0.08)*0.3;
    ctx.font = 'bold 26px Segoe UI, sans-serif';
    ctx.fillStyle = `rgba(255,255,255,${pulse})`;
    ctx.fillText('CLICK OR PRESS A KEY TO START', W/2, H*0.55);
  } else if (demoMode) {
    ctx.font = '16px Segoe UI, sans-serif';
    ctx.fillStyle = '#ffd93d';
    ctx.fillText('DEMO MODE — watch the AI play', W/2, H*0.55);
  }
  
  // Controls hint
  ctx.font = '14px Segoe UI, sans-serif';
  ctx.fillStyle = 'rgba(255,255,255,0.5)';
  ctx.fillText('Move mouse or ← → keys to steer the hamburger', W/2, H*0.65);
  ctx.fillText('Click anywhere or press Space to play yourself', W/2, H*0.69);
  
  // Small hamburger icon preview
  previewPaddle();
}

function previewPaddle() {
  const ix = W/2, iy = H*0.78, iw = 100, ih = 36;
  ctx.save();
  // simplified quick hamburger
  ctx.fillStyle = '#f0a850';
  ctx.beginPath();
  ctx.ellipse(ix, iy+ih*0.3, iw/2, ih*0.25, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = '#5a2d0f';
  ctx.fillRect(ix-iw/2+4, iy+ih*0.5, iw-8, ih*0.2);
  ctx.fillStyle = '#6bb24a';
  ctx.beginPath();
  ctx.moveTo(ix-iw/2, iy+ih*0.42);
  ctx.lineTo(ix+iw/2, iy+ih*0.42);
  ctx.lineTo(ix+iw/2, iy+ih*0.48);
  ctx.lineTo(ix-iw/2, iy+ih*0.48);
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}

function drawLost() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';
  ctx.font = 'bold 56px Segoe UI, sans-serif';
  ctx.fillStyle = '#ff6b6b';
  ctx.strokeStyle = 'rgba(0,0,0,0.7)';
  ctx.lineWidth = 5;
  ctx.strokeText('GAME OVER', W/2, H/2 - 30);
  ctx.fillText('GAME OVER', W/2, H/2 - 30);
  ctx.font = 'bold 24px Segoe UI, sans-serif';
  ctx.fillStyle = '#ffe0a0';
  ctx.fillText(`FINAL SCORE: ${score}`, W/2, H/2 + 20);
  
  const pulse = 0.7 + Math.sin(frame*0.1)*0.3;
  ctx.font = 'bold 22px Segoe UI, sans-serif';
  ctx.fillStyle = `rgba(255,255,255,${pulse})`;
  ctx.fillText('Click or press R to try again', W/2, H/2 + 60);
  
  window._restartHandler = () => {
    started = false;
    demoMode = true;
    reset();
  };
}

// Restart
window.addEventListener('click', e => {
  if (state === 'lost') {
    started = false;
    demoMode = true;
    reset();
  }
});
window.addEventListener('keydown', e => {
  if (state === 'lost' && (e.key === 'r' || e.key === 'R')) {
    started = false;
    demoMode = true;
    reset();
  }
});

// === Main loop ===
function loop() {
  update(1);
  render();
  requestAnimationFrame(loop);
}
reset();
loop();
```

Hmm wait, there's an issue with the menu flow. Let me think again:

1. Page loads → state = 'menu', started = false, demoMode = true (auto demo on start)
2. User clicks canvas → if !started, set started=true, state='play', demoMode=true (continue demo)
3. After demo completes a level... should we auto switch? Or stop demo? Let me make demo continue through levels but pause briefly to show "LEVEL X".

Actually simpler: demo continues through the first level automatically. When level cleared, show float text, continue demo to next level. User can click anytime to switch to manual play.

Also I need to handle the case where state is 'lost' and user clicks → restart demo.

Let me also reconsider: should demo play itself through several levels before stopping? Maybe limit demo to first 2-3 levels then stop and prompt "click to play manually"? That might be good for judging. Let me make demo run indefinitely (or until lost).

Actually, simplest: demo keeps playing through all levels. When user clicks, they switch to manual. That way demo shows full game action.

Let me also fix: in menu state, clicking should start the demo. Once started, the menu overlay disappears.

Wait but if demoMode is always true until user clicks... Let me think about UX:
- Default: demo mode ON, menu showing "Click or press a key to start", ball sits on paddle
- Click → play begins, still in demo (AI controlled)
- Click again (when in play) → switches to manual play
- When ball falls / level cleared during demo, continue demo
- When user clicks during demo → stop demo, enable manual control

That works. Let me adjust the code:

```javascript
let demoMode = true; // default on

// In mousedown handler:
if (!started && state === 'menu') {
  started = true;
  state = 'play';
  // keep demoMode as is (true) unless explicitly stopped
}
```

And when user presses key (space) during play:
```javascript
if (e.key === ' ' || e.key === 'Enter') {
  if (!started) {
    started = true; state = 'play';
  } else if (!demoStopped) {
    demoMode = false; // stop demo, switch to manual
  }
}
```

Hmm this is getting complicated. Let me simplify:

State variables:
- started: whether gameplay has begun
- demoMode: whether AI is controlling
- When started && !demoMode: player controls
- When started && demoMode: AI controls

Click behavior:
- Menu + click → started = true, demoMode stays true (default), state = 'play'
- Play + click → demoMode = false, player takes over
- Lost + click → restart with demoMode = true

This is clean enough. Let me implement.

One issue: keyboard events. The paddle uses keyboard arrows too. Let me add that:

```javascript
window.addEventListener('keydown', e => {
  if (!started && (e.key === ' ' || e.key === 'Enter')) {
    started = true; state = 'play';
  }
  if (started && !demoMode && (e.key === 'ArrowLeft')) { paddle.vx -= 5; }
  if (started && !demoMode && (e.key === 'ArrowRight')) { paddle.vx += 5; }
  if (state === 'lost' && (e.key === 'r' || e.key === 'R')) {
    started = false; demoMode = true; reset();
  }
});
```

And mouse move updates paddle position directly too:

Actually, let me use a "target" approach. Mouse moves directly set paddle.x. Keyboard adds velocity. Simpler.

```javascript
canvas.addEventListener('mousemove', e => {
  if (started && !demoMode) {
    const r = canvas.getBoundingClientRect();
    paddle.x = e.clientX - r.left;
  }
});
canvas.addEventListener('mousedown', e => {
  if (state === 'menu') {
    started = true; state = 'play';
  } else if (started && demoMode) {
    demoMode = false;
  } else if (state === 'lost') {
    started = false; demoMode = true; reset();
  }
});
```

Wait, but if in menu, the paddle and ball need to exist for demo. They do (reset was called). Good.

But when user clicks menu, paddle needs initial vx=0 so it doesn't jump. Good.

Let me finalize this approach. Also the paddle follows mouse during manual mode, which means I shouldn't also apply keyboard velocity. OK.

Actually I realize when starting from menu, mouse may not be tracked yet since user hasn't moved mouse. Let me default mouseX = paddle.x and update on move.

Let me simplify: paddle.x is directly controlled by mouse in manual mode, and by AI in demo mode. Keyboard just nudges.

Final plan for paddle control:
- Demo mode: AI moves paddle toward ball.x (smoothly)
- Manual mode: paddle.x follows mouse (with lerp) or keyboard velocity

Let me write it cleanly. Here's the final code:

Actually let me just write the whole file now, I've planned enough.

Wait, one concern - the paddle at W-65 and ball starting at H-95. With paddle width 130 and height 50, the bottom of paddle is at H-65. Ball radius 8, ball starts 18px above paddle top, so ball y = H-83. That leaves room below for falling.

Bricks start at y=60, 6 rows of 20px + 4 pad each = 6*(20+4) = 144px. So bricks end at y=60+144=204. Good, well above paddle.

Let me also make sure bricks fit. With W=1280 and 12 cols, brickW = min(60, (1280-50 - 11*5)/12) = min(60, (1230-55)/12) = min(60, 117.5/12) = min(60, 9.8) = 9.8. Hmm that's very narrow.

Wait that's wrong. Let me recalculate: W-50 = 1230. (cols-1)*(gap+pad) = 11*9 = 99. Available = 1230-99 = 1131. Per col = 1131/12 = 94. That's fine.

Wait I had C.gap=5 and C.pad=4 so gap+pad=9. 11*9=99. 1230-99=1131. 1131/12=94.25. So brickW = min(60, 94.25) = 60. 

OK good, wider than I thought because my earlier math was off. Let me verify with smaller screen W=375: (375-50-99)/12 = 226/12 = 18.8. BrickW = min(60, 18.8) = 18.8. OK that works, still 12 columns.

Alright, writing final code now. Let me be careful and produce clean working code.

One more thing: I should make sure the game works even at small widths. Let me cap things appropriately.

Also, for the "first paint" requirement - something must be visible within 3 seconds. The menu with "CLICK OR PRESS A KEY TO START" is visible immediately. Good.

Let me also consider: should I pause when window loses focus? Not strictly necessary but nice. Skip for simplicity.

Alright, writing the final file now. I'll aim for ~400 lines of clean code.

Actually let me make the paddle a bit bigger - 140px wide so the hamburger looks substantial. And the height 48.

For the hamburger rendering, let me refine:
- Bottom bun: rounded bottom, width w, height h*0.4
- Patty: width w-8, height h*0.25, slightly raised edges
- Lettuce: wavy strip between patty and bottom bun
- Top bun: dome shape with sesame seeds

Actually the order matters for visibility. From bottom up:
1. Bottom bun (trapezoid-ish)
2. Patty layer
3. Lettuce wavy strip
4. Top bun dome with sesame

Let me code it properly.

Also, I want the hamburger to look appetizing. Let me add some seasoning dots on patty (like salt/pepper specks) and visible grill marks.

OK here's my final attempt at writing this. Let me write it out completely and carefully.

I think I've planned enough. Time to write.