Models 44

Claude Opus 5.5

by Anthropicreasoning defaultClean
Open in new window

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

How it went

Total time
1 min 29 s
Cost
$0.230
Output tokens
11,377
Spent thinking
1,611 tokens
Speed
132.2 tok/s
Console errors
0
First token after
2.9 s
First visible text after
19.9 s
Prompt tokens
542
Code size
17.3 KB
Finish reason
stop
Served by
Claude Platform on AWS
Model id
anthropic/claude-opus-5.5
Reasoning sent
model default
Max output
128,000 tokens
Generated
Nov 12, 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 · 460 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Burger Breakout</title>
<style>
  html,body{margin:0;padding:0;height:100%;overflow:hidden;background:#1a0b24;}
  body{display:flex;align-items:center;justify-content:center;font-family:system-ui,Segoe UI,Arial,sans-serif;}
  canvas{display:block;touch-action:none;cursor:none;}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
(() => {
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const W = 800, H = 600;
let scale = 1, dpr = 1;

function resize() {
  dpr = Math.min(window.devicePixelRatio || 1, 2);
  scale = Math.min(window.innerWidth / W, window.innerHeight / H);
  canvas.style.width = (W * scale) + 'px';
  canvas.style.height = (H * scale) + 'px';
  canvas.width = Math.round(W * scale * dpr);
  canvas.height = Math.round(H * scale * dpr);
}
window.addEventListener('resize', resize);
resize();

// ---------- Heart sprites ----------
function heartPath(c, s) {
  c.beginPath();
  c.moveTo(0, -s * 0.22);
  c.bezierCurveTo(0, -s * 0.55, -s * 0.5, -s * 0.55, -s * 0.5, -s * 0.18);
  c.bezierCurveTo(-s * 0.5, s * 0.12, -s * 0.05, s * 0.32, 0, s * 0.52);
  c.bezierCurveTo(s * 0.05, s * 0.32, s * 0.5, s * 0.12, s * 0.5, -s * 0.18);
  c.bezierCurveTo(s * 0.5, -s * 0.55, 0, -s * 0.55, 0, -s * 0.22);
  c.closePath();
}
const spriteCache = {};
function heartSprite(color) {
  if (spriteCache[color]) return spriteCache[color];
  const S = 40, cv = document.createElement('canvas');
  cv.width = cv.height = S;
  const c = cv.getContext('2d');
  c.translate(S / 2, S / 2 + 1);
  c.shadowColor = color; c.shadowBlur = 6;
  heartPath(c, 28);
  c.fillStyle = color; c.fill();
  c.shadowBlur = 0;
  c.beginPath(); c.ellipse(-6, -6, 3.5, 2.2, -0.6, 0, Math.PI * 2);
  c.fillStyle = 'rgba(255,255,255,0.65)'; c.fill();
  spriteCache[color] = cv;
  return cv;
}
const rowColors = ['#ff3b6b', '#ff7a3d', '#ffc93c', '#5ee07a', '#3cc6ff', '#b06bff'];
const extraHearts = ['#ff5fa2', '#ff8fc4', '#ffffff', '#ff2d55'];
[...rowColors, ...extraHearts].forEach(heartSprite);

// ---------- Background stars ----------
const stars = Array.from({length: 70}, () => ({x: Math.random() * W, y: Math.random() * H, r: Math.random() * 1.4 + 0.3, t: Math.random() * 6}));

// ---------- State ----------
let mode = 'demo'; // demo | play | over
let bricks = [], particles = [], floats = [];
let score = 0, lives = 3, level = 1, shake = 0;
let stuck = false, stuckTimer = 0;
const paddle = {x: W / 2, y: H - 46, w: 124, h: 36, target: W / 2};
const ball = {x: W / 2, y: H / 2, vx: 0, vy: 0, r: 8, speed: 420};
let trail = [];
const keys = {};

function buildBricks() {
  bricks = [];
  const cols = 10, rows = 6, margin = 36, gap = 6;
  const bw = (W - margin * 2 - gap * (cols - 1)) / cols, bh = 22;
  for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
    // little pattern variation per level
    if (level % 3 === 2 && (r + c) % 5 === 0) continue;
    if (level % 3 === 0 && Math.abs(c - 4.5) < r * 0.5 - 1) continue;
    bricks.push({x: margin + c * (bw + gap), y: 70 + r * (bh + gap), w: bw, h: bh, color: rowColors[r], alive: true, pts: (rows - r) * 10});
  }
}

function serve(stick) {
  ball.speed = 400 + (level - 1) * 35;
  if (stick) {
    stuck = true; stuckTimer = 0;
    ball.x = paddle.x; ball.y = paddle.y - ball.r - 2;
    ball.vx = 0; ball.vy = 0;
  } else {
    stuck = false;
    ball.x = paddle.x; ball.y = paddle.y - 40;
    launch();
  }
  trail = [];
}
function launch() {
  stuck = false;
  const a = -Math.PI / 2 + (Math.random() - 0.5) * 0.9;
  ball.vx = Math.cos(a) * ball.speed; ball.vy = Math.sin(a) * ball.speed;
}

function startDemo() {
  mode = 'demo'; score = 0; lives = 3; level = 1;
  buildBricks(); serve(false);
}
function startPlay() {
  mode = 'play'; score = 0; lives = 3; level = 1;
  particles.length = 0; floats.length = 0;
  buildBricks(); serve(true);
}

// ---------- Particles ----------
function explode(x, y, color, n) {
  for (let i = 0; i < n; i++) {
    const a = Math.random() * Math.PI * 2;
    const sp = 60 + Math.random() * 340;
    const col = Math.random() < 0.6 ? color : extraHearts[(Math.random() * extraHearts.length) | 0];
    const life = 0.9 + Math.random() * 1.3;
    particles.push({
      x: x + (Math.random() - 0.5) * 30, y: y + (Math.random() - 0.5) * 10,
      vx: Math.cos(a) * sp, vy: Math.sin(a) * sp - 120,
      rot: (Math.random() - 0.5) * 0.8, vr: (Math.random() - 0.5) * 6,
      size: 0.25 + Math.random() * 0.45, life, max: life,
      img: spriteCache[col], wob: Math.random() * 6
    });
  }
  if (particles.length > 3500) particles.splice(0, particles.length - 3500);
}

// ---------- Input ----------
function toLogical(clientX) {
  const rect = canvas.getBoundingClientRect();
  return (clientX - rect.left) / scale;
}
let lastPointer = 0;
canvas.addEventListener('mousemove', e => { if (mode === 'play') { paddle.target = toLogical(e.clientX); lastPointer = 1; } });
canvas.addEventListener('touchmove', e => { e.preventDefault(); if (mode === 'play') paddle.target = toLogical(e.touches[0].clientX); }, {passive: false});
function primary(e) {
  if (mode !== 'play') { startPlay(); if (e && e.clientX !== undefined) paddle.target = toLogical(e.clientX); return; }
  if (stuck) launch();
}
canvas.addEventListener('mousedown', primary);
canvas.addEventListener('touchstart', e => { e.preventDefault(); if (mode === 'play') paddle.target = toLogical(e.touches[0].clientX); primary(); }, {passive: false});
window.addEventListener('keydown', e => {
  keys[e.key] = true;
  if (['ArrowLeft', 'ArrowRight', ' ', 'ArrowUp'].includes(e.key)) e.preventDefault();
  if (mode !== 'play') { startPlay(); return; }
  if ((e.key === ' ' || e.key === 'ArrowUp') && stuck) launch();
});
window.addEventListener('keyup', e => { keys[e.key] = false; });

// ---------- AI ----------
let aiOffset = 0, aiTimer = 0;
function predictX() {
  if (ball.vy <= 0) return W / 2 + (ball.x - W / 2) * 0.5;
  const t = (paddle.y - ball.r - ball.y) / ball.vy;
  let x = ball.x + ball.vx * t;
  const lo = ball.r, hi = W - ball.r, span = hi - lo;
  let m = (x - lo) % (2 * span); if (m < 0) m += 2 * span;
  return lo + (m > span ? 2 * span - m : m);
}

// ---------- Update ----------
function update(dt) {
  // paddle control
  if (mode === 'demo' || mode === 'over') {
    aiTimer -= dt;
    if (aiTimer <= 0) { aiTimer = 0.8 + Math.random(); aiOffset = (Math.random() - 0.5) * paddle.w * 0.7; }
    const tx = predictX() + aiOffset;
    const d = tx - paddle.x, maxStep = 900 * dt;
    paddle.x += Math.max(-maxStep, Math.min(maxStep, d));
  } else {
    if (keys['ArrowLeft'] || keys['a'] || keys['A']) paddle.target = paddle.x - 700 * dt * 1.6;
    if (keys['ArrowRight'] || keys['d'] || keys['D']) paddle.target = paddle.x + 700 * dt * 1.6;
    paddle.x += (paddle.target - paddle.x) * Math.min(1, dt * 22);
  }
  paddle.x = Math.max(paddle.w / 2, Math.min(W - paddle.w / 2, paddle.x));
  paddle.target = Math.max(paddle.w / 2, Math.min(W - paddle.w / 2, paddle.target));

  if (stuck) {
    ball.x = paddle.x; ball.y = paddle.y - ball.r - 2;
    stuckTimer += dt;
    if (stuckTimer > 3) launch();
  } else {
    const steps = 4, sdt = dt / steps;
    for (let s = 0; s < steps; s++) stepBall(sdt);
  }
  trail.push({x: ball.x, y: ball.y});
  if (trail.length > 12) trail.shift();

  // particles
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.life -= dt;
    if (p.life <= 0) { particles.splice(i, 1); continue; }
    p.vx *= (1 - 1.6 * dt); p.vy *= (1 - 1.2 * dt);
    p.vy += 160 * dt;
    p.wob += dt * 5;
    p.x += (p.vx + Math.sin(p.wob) * 30) * dt; p.y += p.vy * dt;
    p.rot += p.vr * dt;
  }
  for (let i = floats.length - 1; i >= 0; i--) {
    const f = floats[i]; f.life -= dt; f.y -= 40 * dt;
    if (f.life <= 0) floats.splice(i, 1);
  }
  shake = Math.max(0, shake - dt * 30);
}

function stepBall(dt) {
  ball.x += ball.vx * dt; ball.y += ball.vy * dt;
  if (ball.x < ball.r) { ball.x = ball.r; ball.vx = Math.abs(ball.vx); }
  if (ball.x > W - ball.r) { ball.x = W - ball.r; ball.vx = -Math.abs(ball.vx); }
  if (ball.y < ball.r + 40) { ball.y = ball.r + 40; ball.vy = Math.abs(ball.vy); }

  // paddle
  const top = paddle.y - paddle.h / 2;
  if (ball.vy > 0 && ball.y + ball.r >= top && ball.y < paddle.y + 6 &&
      ball.x > paddle.x - paddle.w / 2 - ball.r && ball.x < paddle.x + paddle.w / 2 + ball.r) {
    const rel = Math.max(-1, Math.min(1, (ball.x - paddle.x) / (paddle.w / 2)));
    const a = -Math.PI / 2 + rel * 1.05;
    ball.vx = Math.cos(a) * ball.speed; ball.vy = Math.sin(a) * ball.speed;
    ball.y = top - ball.r;
    explode(ball.x, top, '#ff8fc4', 8);
    squish = 1;
  }

  // bottom
  if (ball.y > H + 20) {
    if (mode === 'play') {
      lives--;
      shake = 10;
      if (lives <= 0) { mode = 'over'; serve(false); }
      else serve(true);
    } else serve(false);
    return;
  }

  // bricks
  for (const b of bricks) {
    if (!b.alive) continue;
    const cx = Math.max(b.x, Math.min(ball.x, b.x + b.w));
    const cy = Math.max(b.y, Math.min(ball.y, b.y + b.h));
    const dx = ball.x - cx, dy = ball.y - cy;
    if (dx * dx + dy * dy <= ball.r * ball.r) {
      b.alive = false;
      const ox = Math.min(ball.x + ball.r - b.x, b.x + b.w - (ball.x - ball.r));
      const oy = Math.min(ball.y + ball.r - b.y, b.y + b.h - (ball.y - ball.r));
      if (ox < oy) ball.vx = ball.x < b.x + b.w / 2 ? -Math.abs(ball.vx) : Math.abs(ball.vx);
      else ball.vy = ball.y < b.y + b.h / 2 ? -Math.abs(ball.vy) : Math.abs(ball.vy);
      explode(b.x + b.w / 2, b.y + b.h / 2, b.color, 55);
      shake = Math.min(8, shake + 3);
      if (mode === 'play') { score += b.pts; floats.push({x: b.x + b.w / 2, y: b.y, text: '+' + b.pts, life: 0.9, color: b.color}); }
      if (!bricks.some(q => q.alive)) {
        level++;
        for (let k = 0; k < 6; k++) explode(Math.random() * W, 100 + Math.random() * 150, rowColors[k], 40);
        buildBricks(); serve(mode === 'play');
      }
      break;
    }
  }
}

// ---------- Drawing ----------
let squish = 0;
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 drawBurger(cx, cy, w, h) {
  const sq = squish;
  ctx.save();
  ctx.translate(cx, cy + h / 2);
  ctx.scale(1 + sq * 0.08, 1 - sq * 0.15);
  ctx.translate(0, -h / 2);
  const L = -w / 2;
  // shadow
  ctx.fillStyle = 'rgba(0,0,0,0.3)';
  ctx.beginPath(); ctx.ellipse(0, h / 2 + 6, w / 2, 5, 0, 0, Math.PI * 2); ctx.fill();
  // bottom bun
  let g = ctx.createLinearGradient(0, 6, 0, h / 2);
  g.addColorStop(0, '#e9a957'); g.addColorStop(1, '#b8722c');
  ctx.fillStyle = g; roundRect(L + 4, 7, w - 8, h / 2 - 6, 7); ctx.fill();
  // patty
  g = ctx.createLinearGradient(0, -1, 0, 9);
  g.addColorStop(0, '#7a3b1c'); g.addColorStop(1, '#4a200c');
  ctx.fillStyle = g; roundRect(L + 1, -1, w - 2, 10, 5); ctx.fill();
  ctx.fillStyle = 'rgba(0,0,0,0.25)';
  for (let i = 0; i < 8; i++) ctx.fillRect(L + 10 + i * (w - 20) / 8, 2, 4, 2);
  // cheese
  ctx.fillStyle = '#ffd23f';
  ctx.beginPath(); ctx.moveTo(L + 2, -3); ctx.lineTo(L + w - 2, -3); ctx.lineTo(L + w - 2, 0);
  const drips = 6;
  for (let i = drips; i >= 0; i--) {
    const x = L + 2 + i * (w - 4) / drips;
    ctx.lineTo(x, 0);
    if (i > 0) ctx.lineTo(x - (w - 4) / drips / 2, 6 + (i % 2) * 3);
  }
  ctx.closePath(); ctx.fill();
  // tomato
  ctx.fillStyle = '#e8343f'; roundRect(L + 6, -7, w - 12, 5, 2.5); ctx.fill();
  // lettuce
  ctx.fillStyle = '#5fcf3a';
  ctx.beginPath(); ctx.moveTo(L, -7);
  for (let x = 0; x <= w; x += 8) ctx.quadraticCurveTo(L + x + 4, -3 + ((x / 8) % 2) * 3, L + x + 8, -7);
  ctx.lineTo(L + w, -10); ctx.lineTo(L, -10); ctx.closePath(); ctx.fill();
  // top bun
  g = ctx.createLinearGradient(0, -h / 2 - 6, 0, -8);
  g.addColorStop(0, '#ffcf7a'); g.addColorStop(0.5, '#eea24a'); g.addColorStop(1, '#c97a2f');
  ctx.fillStyle = g;
  ctx.beginPath();
  ctx.moveTo(L + 2, -8);
  ctx.bezierCurveTo(L + 2, -h / 2 - 14, L + w - 2, -h / 2 - 14, L + w - 2, -8);
  ctx.closePath(); ctx.fill();
  // highlight
  ctx.fillStyle = 'rgba(255,255,255,0.25)';
  ctx.beginPath(); ctx.ellipse(-w * 0.18, -h / 2 + 1, w * 0.18, 3, -0.15, 0, Math.PI * 2); ctx.fill();
  // sesame
  ctx.fillStyle = '#fff6dc';
  const seeds = [[-0.32, -0.55], [-0.15, -0.8], [0.05, -0.62], [0.22, -0.82], [0.35, -0.5], [-0.02, -0.92], [0.12, -0.42], [-0.25, -0.35]];
  for (const [sx, sy] of seeds) {
    ctx.beginPath(); ctx.ellipse(sx * w, sy * h / 2 - 6, 2.6, 1.4, sx, 0, Math.PI * 2); ctx.fill();
  }
  ctx.restore();
}

function drawBall() {
  for (let i = 0; i < trail.length; i++) {
    const t = trail[i], a = i / trail.length;
    ctx.fillStyle = `rgba(255,120,180,${a * 0.35})`;
    ctx.beginPath(); ctx.arc(t.x, t.y, ball.r * a, 0, Math.PI * 2); ctx.fill();
  }
  ctx.save();
  ctx.shadowColor = '#ff6fb0'; ctx.shadowBlur = 18;
  const g = ctx.createRadialGradient(ball.x - 3, ball.y - 3, 1, ball.x, ball.y, ball.r);
  g.addColorStop(0, '#ffffff'); g.addColorStop(1, '#ff7fbf');
  ctx.fillStyle = g;
  ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2); ctx.fill();
  ctx.restore();
}

function drawBricks() {
  for (const b of bricks) {
    if (!b.alive) continue;
    const g = ctx.createLinearGradient(0, b.y, 0, b.y + b.h);
    g.addColorStop(0, b.color); g.addColorStop(1, shade(b.color, -0.3));
    ctx.fillStyle = g; roundRect(b.x, b.y, b.w, b.h, 6); ctx.fill();
    ctx.fillStyle = 'rgba(255,255,255,0.3)'; roundRect(b.x + 4, b.y + 3, b.w - 8, 5, 2.5); ctx.fill();
    // tiny heart emblem
    ctx.save(); ctx.translate(b.x + b.w / 2, b.y + b.h / 2 + 2);
    heartPath(ctx, 10); ctx.fillStyle = 'rgba(255,255,255,0.45)'; ctx.fill();
    ctx.restore();
  }
}
function shade(hex, f) {
  const n = parseInt(hex.slice(1), 16);
  let r = n >> 16, g = (n >> 8) & 255, b = n & 255;
  r = Math.round(r * (1 + f)); g = Math.round(g * (1 + f)); b = Math.round(b * (1 + f));
  return `rgb(${r},${g},${b})`;
}

function drawParticles() {
  for (const p of particles) {
    const t = p.life / p.max;
    ctx.globalAlpha = Math.min(1, t * 2);
    const s = p.size * (0.6 + 0.4 * Math.min(1, (p.max - p.life) * 6));
    const c = Math.cos(p.rot) * s, sn = Math.sin(p.rot) * s;
    ctx.setTransform(c * dpr * scale, sn * dpr * scale, -sn * dpr * scale, c * dpr * scale, p.x * dpr * scale + shakeX * dpr * scale, p.y * dpr * scale + shakeY * dpr * scale);
    ctx.drawImage(p.img, -20, -20);
  }
  ctx.globalAlpha = 1;
  setBase();
}

let shakeX = 0, shakeY = 0;
function setBase() {
  ctx.setTransform(dpr * scale, 0, 0, dpr * scale, shakeX * dpr * scale, shakeY * dpr * scale);
}

function drawHeartIcon(x, y, s, color) {
  ctx.save(); ctx.translate(x, y); heartPath(ctx, s); ctx.fillStyle = color; ctx.fill(); ctx.restore();
}

let time = 0;
function draw() {
  shakeX = (Math.random() - 0.5) * shake; shakeY = (Math.random() - 0.5) * shake;
  ctx.setTransform(dpr * scale, 0, 0, dpr * scale, 0, 0);
  const bg = ctx.createLinearGradient(0, 0, 0, H);
  bg.addColorStop(0, '#2a0f3d'); bg.addColorStop(1, '#4a1840');
  ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H);
  for (const s of stars) {
    ctx.fillStyle = `rgba(255,220,240,${0.3 + 0.3 * Math.sin(time * 2 + s.t)})`;
    ctx.fillRect(s.x, s.y, s.r, s.r);
  }
  setBase();
  drawBricks();
  drawBurger(paddle.x, paddle.y, paddle.w, paddle.h);
  drawBall();
  drawParticles();

  for (const f of floats) {
    ctx.globalAlpha = Math.min(1, f.life * 2);
    ctx.fillStyle = '#fff'; ctx.font = 'bold 16px system-ui,Arial';
    ctx.textAlign = 'center'; ctx.fillText(f.text, f.x, f.y);
  }
  ctx.globalAlpha = 1;

  // HUD
  ctx.setTransform(dpr * scale, 0, 0, dpr * scale, 0, 0);
  ctx.fillStyle = 'rgba(0,0,0,0.25)'; ctx.fillRect(0, 0, W, 40);
  ctx.font = 'bold 18px system-ui,Arial'; ctx.textBaseline = 'middle';
  ctx.textAlign = 'left'; ctx.fillStyle = '#ffd6ea';
  ctx.fillText('SCORE ' + score, 16, 21);
  ctx.textAlign = 'center'; ctx.fillStyle = '#ffcf7a';
  ctx.fillText('🍔 BURGER BREAKOUT · LEVEL ' + level, W / 2, 21);
  for (let i = 0; i < 3; i++) drawHeartIcon(W - 24 - i * 26, 20, 20, i < lives ? '#ff3b6b' : 'rgba(255,255,255,0.15)');

  if (mode !== 'play') {
    const pulse = 0.75 + 0.25 * Math.sin(time * 4);
    ctx.fillStyle = 'rgba(20,5,30,0.55)';
    roundRect(W / 2 - 230, H / 2 - 20, 460, 120, 18); ctx.fill();
    ctx.textAlign = 'center';
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 30px system-ui,Arial';
    ctx.fillText(mode === 'over' ? 'GAME OVER' : 'BURGER BREAKOUT', W / 2, H / 2 + 14);
    ctx.globalAlpha = pulse;
    ctx.fillStyle = '#ff8fc4';
    ctx.font = 'bold 20px system-ui,Arial';
    ctx.fillText('Click or press a key to start', W / 2, H / 2 + 50);
    ctx.globalAlpha = 1;
    ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.font = '13px system-ui,Arial';
    ctx.fillText('Mouse / touch / ← → to move · Space to launch', W / 2, H / 2 + 80);
  } else if (stuck) {
    ctx.textAlign = 'center'; ctx.fillStyle = 'rgba(255,255,255,' + (0.6 + 0.4 * Math.sin(time * 5)) + ')';
    ctx.font = 'bold 18px system-ui,Arial';
    ctx.fillText('Click or press Space to launch', W / 2, H - 110);
  }
}

let last = performance.now();
function loop(now) {
  let dt = Math.min(0.033, (now - last) / 1000);
  last = now; time += dt;
  squish = Math.max(0, squish - dt * 6);
  update(dt);
  draw();
  requestAnimationFrame(loop);
}
startDemo();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
Reasoning trace (1,611 tokens)
I'm planning a complete breakout game with a hamburger paddle, heart particle explosions, a demo AI mode, and a start message, keeping it concise but polished. The canvas will fill the full window and stay responsive, with colored rows of bricks. The paddle will be drawn as a hamburger, and I'm weighing whether the ball should be a cherry tomato or a simple ball. For the hearts, I'll either draw them with paths or use cached sprites to keep performance up.

I'm working out the logical coordinate system next, weighing a fixed 800x600 space scaled to fit the window against sizing the game area from the viewport width, since a portrait phone would make the scaled version quite small.

Next I'm settling the physics: the ball moves at a constant speed with sub-steps so it can't tunnel through bricks, and the paddle's bounce angle depends on where the ball hits it. For the hearts, each broken brick spawns around forty with random velocities, a little gravity, rotation, and fading, plus a few when the ball hits the paddle, capped near three thousand. I'm also sketching the heart sprite path that gets cached per color.

I'm laying out the hamburger paddle's layers next: bottom bun, patty, cheese with drips, lettuce, tomato, and a top bun with sesame seeds, about 120 wide and 34 tall, with its top edge serving as the collision line. For the ball, I'm weighing a round pink glowing ball against a heart, leaning toward the round one. The overlay will show a pulsing "Click or press a key to start" over the "BURGER BREAKOUT" title, with score, hearts for lives, and level on the HUD. Controls will be mouse, touch, and arrow or A/D keys, and I'm about to work out how the ball gets launched.

I'm setting the game's time rules: the ball stays stuck to the paddle until a click, space, or up arrow launches it, with an automatic launch after two seconds. I'm also sketching the game-over state, which just shows a "click to play again" message and restarts on click. Next I'm working out the brick grid and the demo AI, which predicts where the ball will land by reflecting it off the walls.

Next I'm getting the particle system ready: each one tracks position, velocity, rotation, size, and life, and I'll use a fixed time step so the physics stays steady. The ball speeds up a bit each level, and when a brick breaks I'll spawn a burst of hearts in that brick's color plus some pink ones. The background will be a dark purple gradient with faint stars.