Models 44

Claude Haiku 5.5

by AnthropicReasoningClean
Open in new window

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

How it went

Total time
7 min 15 s
Cost
$0.054
Output tokens
107,581
Spent thinking
91,861 tokens
Speed
248.4 tok/s
Console errors
0
First token after
1.8 s
First visible text after
6 min 34 s
Prompt tokens
542
Code size
29.6 KB
Finish reason
stop
Served by
Claude Platform on AWS
Model id
anthropic/claude-haiku-5.5
Reasoning sent
{"effort":"xhigh"}
Max output
128,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 · 1048 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hamburger Breakout</title>
<style>
  * { box-sizing: border-box; }
  html, body { margin: 0; padding: 0; }
  body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 12px;
    overflow-x: hidden;
    background: radial-gradient(120% 90% at 50% 0%, #3f1533 0%, #180810 55%, #0a0306 100%);
    color: #ffe6ef;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  }
  header { text-align: center; }
  h1 { margin: 0; font-size: clamp(20px, 3.2vw, 30px); font-weight: 800; letter-spacing: .2px; }
  h1 .pink { color: #ff5d8f; }
  header p { margin: 4px 0 0; font-size: 13px; color: #d8a9bb; }
  #stage {
    position: relative;
    flex: none;
    width: 320px;
    height: 480px;
    max-width: 100%;
    border-radius: 18px;
    overflow: hidden;
    background: #12050c;
    box-shadow: 0 0 0 2px rgba(255,93,143,.35), 0 24px 70px rgba(0,0,0,.6);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
  }
  #c {
    position: absolute;
    left: 0; top: 0;
    width: 100%; height: 100%;
    display: block;
    touch-action: none;
  }
  #overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 16px;
    text-align: center;
    cursor: pointer;
    background: radial-gradient(circle at 50% 50%, rgba(10,3,8,0) 0%, rgba(10,3,8,.4) 100%);
    transition: opacity .35s ease;
  }
  #overlay.hidden { opacity: 0; pointer-events: none; }
  #msg {
    font-size: clamp(17px, 3.8vw, 26px);
    font-weight: 800;
    color: #fff;
    padding: 12px 22px;
    border-radius: 16px;
    background: rgba(26,6,17,.86);
    border: 2px solid #ff5d8f;
    box-shadow: 0 0 28px rgba(255,93,143,.5);
    animation: pulse 1.6s ease-in-out infinite;
  }
  #sub {
    font-size: 14px;
    color: #ffd6e3;
    padding: 6px 14px;
    border-radius: 999px;
    background: rgba(26,6,17,.78);
  }
  @keyframes pulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.05); }
  }
  footer {
    font-size: 12px;
    color: #b98aa0;
    text-align: center;
    max-width: 100%;
  }
  kbd {
    font-family: inherit;
    background: rgba(255,255,255,.08);
    border-radius: 4px;
    padding: 1px 5px;
  }
</style>
</head>
<body>
<header>
  <h1>🍔 <span class="pink">Hamburger</span> Breakout</h1>
  <p>Every brick you smash bursts into hearts</p>
</header>

<div id="stage">
  <canvas id="c"></canvas>
  <div id="overlay">
    <div id="msg">Click or press a key to start</div>
    <div id="sub">Demo mode: the AI chef is cooking</div>
  </div>
</div>

<footer>
  Mouse / touch to move · <kbd>←</kbd> <kbd>→</kbd> to nudge · <kbd>Space</kbd> to serve · <kbd>P</kbd> to pause
</footer>

<script>
(() => {
  'use strict';

  // ---------- arena + constants ----------
  const W = 640, H = 720;
  const COLS = 10, BW = 54, BH = 22, GX = 6, GY = 6, BTOP = 84;
  const BLEFT = (W - (COLS * BW + (COLS - 1) * GX)) / 2;
  const PW = 124, PH = 58, PADDLE_Y = H - 72;
  const MAX_HEARTS = 1600;
  const PALETTE = ['#ff4f6e', '#ffa33d', '#ffd447', '#6ed36a', '#ff86b8', '#f0485a', '#ffb36b'];
  const HEART_COLORS = ['#ff2e63', '#ff6b9a', '#ffb8cf', '#ffffff', '#d6104f', '#ff9fb8'];
  const WHITE = [255, 255, 255], BLACK = [0, 0, 0];
  const SEEDS = [[30, 11], [44, 8], [58, 7], [72, 7], [86, 9], [98, 13], [38, 17], [52, 15], [66, 15], [80, 17], [24, 17], [104, 18]];
  const PATTERNS = [
    () => true,
    (r, c) => (r + c) % 2 === 0,
    (r, c, R) => Math.abs(c - 4.5) <= 4.5 - (r / R) * 4,
    (r, c) => c % 3 !== 1,
    (r, c, R) => r === 0 || r === R - 1 || c === 0 || c === COLS - 1 || (r % 2 === 0 && c % 2 === 0)
  ];

  // ---------- DOM ----------
  const canvas = document.getElementById('c');
  const ctx = canvas.getContext('2d');
  const stage = document.getElementById('stage');
  const overlay = document.getElementById('overlay');
  const msgEl = document.getElementById('msg');
  const subEl = document.getElementById('sub');
  const headerEl = document.querySelector('header');
  const footerEl = document.querySelector('footer');

  let sx = 1, sy = 1;
  const clamp = (v, a, b) => (v < a ? a : v > b ? b : v);
  const rand = (a, b) => a + Math.random() * (b - a);

  // ---------- heart sprites ----------
  const HEART_PTS = [];
  for (let i = 0; i < 72; i++) {
    const t = (i / 72) * Math.PI * 2;
    HEART_PTS.push([
      16 * Math.pow(Math.sin(t), 3),
      -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t))
    ]);
  }

  function makeHeartSprite(color) {
    const S = 40;
    const c = document.createElement('canvas');
    c.width = S;
    c.height = S;
    const g = c.getContext('2d');
    let minX = 1e9, maxX = -1e9, minY = 1e9, maxY = -1e9;
    for (const p of HEART_PTS) {
      minX = Math.min(minX, p[0]); maxX = Math.max(maxX, p[0]);
      minY = Math.min(minY, p[1]); maxY = Math.max(maxY, p[1]);
    }
    const k = (S * 0.9) / Math.max(maxX - minX, maxY - minY);
    const ox = S / 2 - ((minX + maxX) / 2) * k;
    const oy = S / 2 - ((minY + maxY) / 2) * k;
    g.beginPath();
    HEART_PTS.forEach((p, i) => {
      const X = ox + p[0] * k, Y = oy + p[1] * k;
      if (i) g.lineTo(X, Y); else g.moveTo(X, Y);
    });
    g.closePath();
    g.fillStyle = color;
    g.fill();
    g.fillStyle = 'rgba(255,255,255,0.55)';
    g.beginPath();
    g.ellipse(S * 0.3, S * 0.34, S * 0.085, S * 0.05, -0.6, 0, Math.PI * 2);
    g.fill();
    return c;
  }
  const SPRITES = HEART_COLORS.map(col => makeHeartSprite(col));

  // ---------- colour + path helpers ----------
  function hexRgb(h) {
    const n = parseInt(h.slice(1), 16);
    return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
  }
  function mixHex(hex, to, t) {
    const a = hexRgb(hex);
    return 'rgb(' +
      Math.round(a[0] + (to[0] - a[0]) * t) + ',' +
      Math.round(a[1] + (to[1] - a[1]) * t) + ',' +
      Math.round(a[2] + (to[2] - a[2]) * t) + ')';
  }
  function rr(x, y, w, h, r) {
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.arcTo(x + w, y, x + w, y + h, r);
    ctx.arcTo(x + w, y + h, x, y + h, r);
    ctx.arcTo(x, y + h, x, y, r);
    ctx.arcTo(x, y, x + w, y, r);
    ctx.closePath();
  }
  function speedFor(lv) { return Math.min(560, 330 + (lv - 1) * 24); }

  // ---------- game state ----------
  let state = 'demo';          // 'demo' | 'play' | 'over'
  let paused = false;
  let score = 0, lives = 3, level = 1;
  let bricks = [];
  let hearts = [], rings = [], popups = [];
  const paddle = { cx: W / 2, y: PADDLE_Y, squish: 0 };
  let paddleTarget = W / 2;
  let keyLeft = false, keyRight = false;
  let intermission = 0, overTimer = 0, serveTimer = 0, trailTimer = 0;
  let banner = null;
  let shake = 0, flashRed = 0;
  let aiOffset = 0;
  let lastHit = null;
  let lastT = 0;
  const ball = { x: W / 2, y: 0, vx: 0, vy: 0, r: 7, speed: 330, stuck: true, active: true };

  // ---------- level building ----------
  function buildLevel(lv) {
    level = lv;
    const R = Math.min(5 + Math.floor((lv - 1) / 2), 8);
    const pat = PATTERNS[(lv - 1) % PATTERNS.length];
    bricks = [];
    for (let r = 0; r < R; r++) {
      for (let c = 0; c < COLS; c++) {
        if (!pat(r, c, R)) continue;
        let hp = 1;
        if (r === 0 && lv >= 2) hp = 2;
        if (r === 1 && lv >= 4) hp = 2;
        if (lv >= 3 && (r * 7 + c * 3 + lv) % 11 === 0) hp = 2;
        const color = PALETTE[r % PALETTE.length];
        bricks.push({
          x: BLEFT + c * (BW + GX),
          y: BTOP + r * (BH + GY),
          w: BW, h: BH,
          row: r, hp, maxHp: hp, color,
          light: mixHex(color, WHITE, 0.35),
          dark: mixHex(color, BLACK, 0.3),
          pts: (R - r) * 10 * hp,
          flash: 0,
          alive: true
        });
      }
    }
    ball.speed = speedFor(lv);
  }

  function placeBallOnPaddle() {
    ball.x = paddle.cx;
    ball.y = paddle.y + 6 - ball.r - 1;
  }

  function resetBall() {
    ball.active = true;
    ball.stuck = true;
    ball.vx = 0;
    ball.vy = 0;
    ball.speed = speedFor(level);
    placeBallOnPaddle();
    serveTimer = 0.9;
  }

  function serve() {
    if (!ball.active || !ball.stuck || intermission > 0) return;
    ball.stuck = false;
    const a = rand(-0.4, 0.4);
    ball.vx = Math.sin(a) * ball.speed;
    ball.vy = -Math.cos(a) * ball.speed;
  }

  // ---------- particles ----------
  function makeHeart(x, y, vx, vy, size, life, spr) {
    hearts.push({
      x, y, vx, vy,
      rot: Math.random() * 6.28,
      vr: rand(-8, 8),
      size, life, max: life, spr
    });
  }

  function burstAt(x, y, count, smin, smax) {
    for (let i = 0; i < count; i++) {
      const a = Math.random() * Math.PI * 2;
      const sp = rand(smin, smax);
      const big = Math.random() < 0.12;
      makeHeart(
        x, y,
        Math.cos(a) * sp,
        Math.sin(a) * sp - 80,
        big ? rand(11, 15) : rand(4, 9),
        rand(1.2, 2.4),
        SPRITES[(Math.random() * SPRITES.length) | 0]
      );
    }
  }

  function addRing(x, y, color, life, size) {
    rings.push({ x, y, color, life, max: life, size });
  }

  function updateHearts(dt) {
    const drag = Math.exp(-dt * 1.3);
    const g = 520 * dt;
    let w = 0;
    for (let i = 0; i < hearts.length; i++) {
      const h = hearts[i];
      h.life -= dt;
      if (h.life <= 0 || h.y > H + 40) continue;
      h.vy += g;
      h.vx *= drag;
      h.vy *= drag;
      h.x += h.vx * dt;
      h.y += h.vy * dt;
      h.rot += h.vr * dt;
      hearts[w++] = h;
    }
    hearts.length = w;
    if (hearts.length > MAX_HEARTS) hearts.splice(0, hearts.length - MAX_HEARTS);
  }

  function updateRings(dt) {
    let w = 0;
    for (const r of rings) {
      r.life -= dt;
      if (r.life > 0) rings[w++] = r;
    }
    rings.length = w;
  }

  function updatePopups(dt) {
    let w = 0;
    for (const p of popups) {
      p.t += dt;
      if (p.t < p.life) popups[w++] = p;
    }
    popups.length = w;
  }

  // ---------- gameplay events ----------
  function paddleHit() {
    paddle.squish = 1;
    burstAt(ball.x, paddle.y + 6, 10, 50, 170);
    if (state === 'demo') aiOffset = rand(-22, 22);
  }

  function hitBrick(b) {
    b.hp -= 1;
    b.flash = 0.12;
    const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
    if (b.hp > 0) {
      burstAt(cx, cy, 16, 90, 280);
      addRing(cx, cy, b.color, 0.35, 20);
      shake = Math.max(shake, 2.5);
      return;
    }
    b.alive = false;
    burstAt(cx, cy, 78 + ((Math.random() * 16) | 0), 110, 500);
    addRing(cx, cy, b.color, 0.55, 26);
    addRing(cx, cy, '#ffffff', 0.4, 14);
    score += b.pts;
    popups.push({ text: '+' + b.pts, x: cx, y: cy, t: 0, life: 0.9, color: '#fff3a0' });
    shake = Math.max(shake, 4);
    if (bricks.every(x => !x.alive)) levelClear();
  }

  function levelClear() {
    intermission = 1.8;
    ball.stuck = true;
    ball.vx = 0;
    ball.vy = 0;
    placeBallOnPaddle();
    const bonus = 200 * level;
    score += bonus;
    popups.push({
      text: 'LEVEL CLEAR! +' + bonus,
      x: W / 2, y: H * 0.42, t: 0, life: 1.8, big: true, color: '#ffe066'
    });
    burstAt(W / 2, H * 0.42, 140, 140, 560);
    shake = Math.max(shake, 6);
  }

  function loseLife() {
    lives -= 1;
    shake = Math.max(shake, 10);
    flashRed = 0.4;
    burstAt(clamp(ball.x, 20, W - 20), H - 8, 26, 60, 240);
    if (lives <= 0) gameOver();
    else resetBall();
  }

  function gameOver() {
    state = 'over';
    overTimer = 0;
    ball.active = false;
    ball.stuck = true;
    paddleTarget = W / 2;
    updateOverlay();
  }

  function newGame(mode) {
    state = mode;
    score = 0;
    lives = 3;
    paused = false;
    intermission = 0;
    overTimer = 0;
    paddle.cx = W / 2;
    paddleTarget = W / 2;
    popups = [];
    rings = [];
    buildLevel(1);
    resetBall();
    banner = mode === 'play' ? { text: 'LEVEL 1', t: 0, dur: 1.6 } : null;
    updateOverlay();
  }

  function togglePause() {
    if (state !== 'play') return;
    paused = !paused;
    updateOverlay();
  }

  // ---------- ball physics ----------
  function stepBall(dt) {
    const r = ball.r;
    const steps = 3;
    const h = dt / steps;
    lastHit = null;
    for (let s = 0; s < steps; s++) {
      const px = ball.x, py = ball.y;
      ball.x += ball.vx * h;
      ball.y += ball.vy * h;

      // walls
      if (ball.x - r < 0) { ball.x = r; ball.vx = Math.abs(ball.vx); }
      else if (ball.x + r > W) { ball.x = W - r; ball.vx = -Math.abs(ball.vx); }
      if (ball.y - r < 0) { ball.y = r; ball.vy = Math.abs(ball.vy); }

      // paddle (hamburger top bun)
      if (ball.vy > 0) {
        const top = paddle.y + 6;
        const left = paddle.cx - PW / 2, right = paddle.cx + PW / 2;
        if (py + r <= top + 0.5 && ball.y + r >= top &&
            ball.x >= left - r * 0.5 && ball.x <= right + r * 0.5) {
          const rel = clamp((ball.x - paddle.cx) / (PW / 2), -1, 1);
          const ang = rel * 1.0;
          ball.vx = ball.speed * Math.sin(ang);
          ball.vy = -ball.speed * Math.cos(ang);
          ball.y = top - r;
          paddleHit();
          continue;
        }
      }

      // bricks
      let hit = null, hdx = 0, hdy = 0;
      for (const b of bricks) {
        if (!b.alive || b === lastHit) continue;
        const qx = clamp(ball.x, b.x, b.x + b.w);
        const qy = clamp(ball.y, b.y, b.y + b.h);
        const dx = ball.x - qx, dy = ball.y - qy;
        if (dx * dx + dy * dy <= r * r) {
          hit = b; hdx = dx; hdy = dy;
          break;
        }
      }
      if (hit) {
        const prevInX = px + r > hit.x && px - r < hit.x + hit.w;
        const prevInY = py + r > hit.y && py - r < hit.y + hit.h;
        let flipX;
        if (prevInX && !prevInY) flipX = false;
        else if (prevInY && !prevInX) flipX = true;
        else flipX = Math.abs(hdx) >= Math.abs(hdy);
        if (flipX) { ball.vx = -ball.vx; ball.x = px; }
        else { ball.vy = -ball.vy; ball.y = py; }
        lastHit = hit;
        hitBrick(hit);
        if (ball.stuck || !ball.active) return;
      }

      // lost
      if (ball.y - r > H + 6) { loseLife(); return; }
    }
  }

  // ---------- control ----------
  function predictX() {
    const r = ball.r;
    const contactY = paddle.y + 6 - r;
    const t = Math.max(0, (contactY - ball.y) / ball.vy);
    const L = W - 2 * r;
    let pos = (ball.x - r) + ball.vx * t;
    pos = ((pos % (2 * L)) + 2 * L) % (2 * L);
    if (pos > L) pos = 2 * L - pos;
    return r + pos;
  }

  function aiControl() {
    if (!ball.active || ball.stuck) paddleTarget = paddle.cx;
    else if (ball.vy > 0) paddleTarget = predictX() + aiOffset;
    else paddleTarget = W / 2 + (ball.x - W / 2) * 0.6;
  }

  function playerControl(dt) {
    if (keyLeft !== keyRight) {
      paddle.cx = clamp(paddle.cx + (keyRight ? 1 : -1) * 760 * dt, PW / 2 + 4, W - PW / 2 - 4);
      paddleTarget = paddle.cx;
    }
  }

  function movePaddle(dt, maxSpeed) {
    const lo = PW / 2 + 4, hi = W - PW / 2 - 4;
    paddleTarget = clamp(paddleTarget, lo, hi);
    const d = paddleTarget - paddle.cx;
    const step = clamp(d * Math.min(1, dt * 18), -maxSpeed * dt, maxSpeed * dt);
    paddle.cx = clamp(paddle.cx + step, lo, hi);
  }

  // ---------- update ----------
  function update(dt) {
    if (paused) return;

    updateHearts(dt);
    updateRings(dt);
    updatePopups(dt);
    shake *= Math.exp(-dt * 9);
    if (shake < 0.05) shake = 0;
    flashRed = Math.max(0, flashRed - dt * 1.6);
    for (const b of bricks) if (b.flash > 0) b.flash -= dt;
    if (banner) {
      banner.t += dt;
      if (banner.t >= banner.dur) banner = null;
    }

    if (state === 'over') {
      overTimer += dt;
      movePaddle(dt, 300);
      if (overTimer > 4.5) newGame('demo');
      return;
    }

    if (state === 'demo') aiControl();
    else playerControl(dt);
    movePaddle(dt, state === 'demo' ? 900 : 1400);
    paddle.squish = Math.max(0, paddle.squish - dt * 6);

    if (intermission > 0) {
      intermission -= dt;
      if (intermission <= 0) {
        intermission = 0;
        buildLevel(level + 1);
        resetBall();
        banner = { text: 'LEVEL ' + level, t: 0, dur: 1.6 };
      }
    }

    if (!ball.active) return;

    if (ball.stuck) {
      placeBallOnPaddle();
      if (state === 'demo' && intermission === 0) {
        serveTimer -= dt;
        if (serveTimer <= 0) serve();
      }
      return;
    }

    // trail of tiny hearts behind the ball
    trailTimer -= dt;
    if (trailTimer <= 0) {
      trailTimer = 0.035;
      makeHeart(ball.x, ball.y, rand(-18, 18), rand(-18, 18) + 10, rand(3, 6), rand(0.35, 0.6),
        SPRITES[1 + ((Math.random() * 3) | 0)]);
    }

    stepBall(dt);
  }

  // ---------- rendering ----------
  function drawBackground() {
    const g = ctx.createLinearGradient(0, 0, 0, H);
    g.addColorStop(0, '#2c0e20');
    g.addColorStop(1, '#100409');
    ctx.fillStyle = g;
    ctx.fillRect(-20, -20, W + 40, H + 40);

    const rg = ctx.createRadialGradient(W / 2, H * 0.3, 30, W / 2, H * 0.3, W * 0.75);
    rg.addColorStop(0, 'rgba(255,90,140,0.22)');
    rg.addColorStop(1, 'rgba(255,90,140,0)');
    ctx.fillStyle = rg;
    ctx.fillRect(-20, -20, W + 40, H + 40);

    ctx.fillStyle = 'rgba(255,200,220,0.06)';
    for (let y = 100; y < H; y += 30) {
      for (let x = 10; x < W; x += 30) ctx.fillRect(x, y, 2, 2);
    }
  }

  function drawBricks() {
    for (const b of bricks) {
      if (!b.alive) continue;
      const x = b.x, y = b.y, w = b.w, h = b.h;
      ctx.save();
      if (b.maxHp > 1 && b.hp > 1) {
        ctx.fillStyle = b.dark;
        rr(x + 1, y + 4, w - 2, h, 5);
        ctx.fill();
      }
      const g = ctx.createLinearGradient(0, y, 0, y + h);
      g.addColorStop(0, b.light);
      g.addColorStop(0.5, b.color);
      g.addColorStop(1, b.dark);
      ctx.fillStyle = g;
      rr(x, y, w, h, 5);
      ctx.fill();

      ctx.fillStyle = 'rgba(255,255,255,0.4)';
      rr(x + 4, y + 3, w - 8, 3, 1.5);
      ctx.fill();

      ctx.strokeStyle = 'rgba(40,0,15,0.35)';
      ctx.lineWidth = 1;
      rr(x + 0.5, y + 0.5, w - 1, h - 1, 4.5);
      ctx.stroke();

      if (b.maxHp > 1 && b.hp === 1) {
        ctx.strokeStyle = 'rgba(30,5,15,0.6)';
        ctx.lineWidth = 1.3;
        ctx.beginPath();
        ctx.moveTo(x + w * 0.3, y + 2);
        ctx.lineTo(x + w * 0.42, y + h * 0.5);
        ctx.lineTo(x + w * 0.36, y + h - 2);
        ctx.moveTo(x + w * 0.62, y + 2);
        ctx.lineTo(x + w * 0.7, y + h * 0.6);
        ctx.stroke();
      }

      if (b.flash > 0) {
        ctx.globalAlpha = Math.min(1, b.flash * 6);
        ctx.fillStyle = '#ffffff';
        rr(x, y, w, h, 5);
        ctx.fill();
      }
      ctx.restore();
    }
  }

  function drawPaddle() {
    const sq = paddle.squish;
    ctx.save();

    // shadow
    ctx.fillStyle = 'rgba(0,0,0,0.32)';
    ctx.beginPath();
    ctx.ellipse(paddle.cx, paddle.y + PH + 4, PW / 2 - 6, 5, 0, 0, Math.PI * 2);
    ctx.fill();

    ctx.translate(paddle.cx, paddle.y + PH);
    ctx.scale(1 + sq * 0.05, 1 - sq * 0.1);
    ctx.translate(-PW / 2, -PH);

    // bottom bun
    const bunG = ctx.createLinearGradient(0, 44, 0, PH);
    bunG.addColorStop(0, '#f4b25a');
    bunG.addColorStop(1, '#b96b22');
    ctx.fillStyle = bunG;
    ctx.beginPath();
    ctx.moveTo(4, 44);
    ctx.lineTo(PW - 4, 44);
    ctx.quadraticCurveTo(PW - 1, 45, PW - 2, 51);
    ctx.quadraticCurveTo(PW - 2, PH, PW - 12, PH);
    ctx.lineTo(12, PH);
    ctx.quadraticCurveTo(2, PH, 2, 51);
    ctx.quadraticCurveTo(1, 45, 4, 44);
    ctx.closePath();
    ctx.fill();

    // patty
    const pG = ctx.createLinearGradient(0, 36, 0, 48);
    pG.addColorStop(0, '#9c5632');
    pG.addColorStop(1, '#4a2010');
    ctx.fillStyle = pG;
    rr(2, 36, PW - 4, 12, 6);
    ctx.fill();
    ctx.strokeStyle = 'rgba(30,10,0,0.45)';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    for (let i = 0; i < 4; i++) {
      const x = 16 + i * 28;
      ctx.moveTo(x, 40);
      ctx.lineTo(x + 7, 44);
    }
    ctx.stroke();

    // cheese with drips
    ctx.fillStyle = '#ffd23f';
    ctx.beginPath();
    ctx.moveTo(0, 29);
    ctx.lineTo(PW, 29);
    ctx.lineTo(PW, 33);
    const n = 8, st = PW / n;
    for (let i = 0; i < n; i++) {
      const xa = PW - i * st;
      ctx.quadraticCurveTo(xa - st / 2, (i % 2) ? 36 : 40, xa - st, 33);
    }
    ctx.closePath();
    ctx.fill();

    // tomato
    const tG = ctx.createLinearGradient(0, 26, 0, 33);
    tG.addColorStop(0, '#ff6a55');
    tG.addColorStop(1, '#c42a20');
    ctx.fillStyle = tG;
    rr(5, 26, PW - 10, 7, 3.5);
    ctx.fill();
    ctx.fillStyle = 'rgba(255,255,255,0.35)';
    rr(10, 27, PW - 20, 2, 1);
    ctx.fill();

    // lettuce (scalloped)
    ctx.fillStyle = '#6ccf4c';
    ctx.beginPath();
    ctx.moveTo(-4, 21);
    ctx.lineTo(PW + 4, 21);
    const LN = 10, ls = (PW + 8) / LN;
    for (let i = 0; i < LN; i++) {
      const xa = PW + 4 - i * ls;
      ctx.quadraticCurveTo(xa - ls / 2, 33, xa - ls, 27);
    }
    ctx.closePath();
    ctx.fill();

    // top bun (domed)
    const bG = ctx.createLinearGradient(0, 0, 0, 26);
    bG.addColorStop(0, '#ffdc8f');
    bG.addColorStop(0.5, '#f3a84c');
    bG.addColorStop(1, '#c97a2c');
    ctx.fillStyle = bG;
    ctx.beginPath();
    ctx.moveTo(2, 24);
    ctx.bezierCurveTo(2, -8, PW - 2, -8, PW - 2, 24);
    ctx.quadraticCurveTo(PW / 2, 29, 2, 24);
    ctx.closePath();
    ctx.fill();

    // sesame seeds
    ctx.fillStyle = '#fff6e2';
    for (const p of SEEDS) {
      ctx.beginPath();
      ctx.ellipse(p[0], p[1], 2.3, 1.3, 0.5, 0, Math.PI * 2);
      ctx.fill();
    }

    // bun gloss
    ctx.strokeStyle = 'rgba(255,255,255,0.4)';
    ctx.lineWidth = 3;
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(16, 17);
    ctx.quadraticCurveTo(34, 4, 56, 2);
    ctx.stroke();

    ctx.restore();
  }

  function drawBall() {
    if (!ball.active) return;
    ctx.save();
    ctx.shadowColor = 'rgba(255,70,130,0.9)';
    ctx.shadowBlur = 14;
    const g = ctx.createRadialGradient(ball.x - 2.5, ball.y - 2.5, 1, ball.x, ball.y, ball.r);
    g.addColorStop(0, '#ffffff');
    g.addColorStop(0.45, '#ffd1e0');
    g.addColorStop(1, '#ff4b7d');
    ctx.fillStyle = g;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  function drawRings() {
    for (const r of rings) {
      const p = 1 - r.life / r.max;
      ctx.globalAlpha = (1 - p) * 0.9;
      ctx.strokeStyle = r.color;
      ctx.lineWidth = 3 * (1 - p) + 0.6;
      ctx.beginPath();
      ctx.arc(r.x, r.y, r.size * (0.3 + p * 1.2), 0, Math.PI * 2);
      ctx.stroke();
    }
    ctx.globalAlpha = 1;
  }

  function drawHearts() {
    for (let i = 0; i < hearts.length; i++) {
      const h = hearts[i];
      const age = h.max - h.life;
      const s = h.size * Math.min(1, 0.35 + age * 7);
      ctx.globalAlpha = Math.min(1, h.life / 0.5);
      ctx.save();
      ctx.translate(h.x, h.y);
      ctx.rotate(h.rot);
      ctx.drawImage(h.spr, -s / 2, -s / 2, s, s);
      ctx.restore();
    }
    ctx.globalAlpha = 1;
  }

  function drawPopups() {
    for (const p of popups) {
      const k = p.t / p.life;
      ctx.save();
      ctx.globalAlpha = Math.max(0, 1 - k * k);
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.font = p.big ? '900 34px system-ui, sans-serif' : '800 17px system-ui, sans-serif';
      ctx.lineWidth = 4;
      ctx.lineJoin = 'round';
      ctx.strokeStyle = 'rgba(40,0,20,0.65)';
      ctx.fillStyle = p.color || '#ffffff';
      ctx.strokeText(p.text, p.x, p.y - k * 30);
      ctx.fillText(p.text, p.x, p.y - k * 30);
      ctx.restore();
    }
  }

  function drawMiniBurger(cx, cy, on) {
    ctx.save();
    ctx.globalAlpha = on ? 1 : 0.2;
    ctx.fillStyle = '#f3a84c';
    ctx.beginPath();
    ctx.ellipse(cx, cy - 1, 11, 8, 0, Math.PI, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#6ccf4c';
    rr(cx - 12, cy - 1, 24, 3, 1.5);
    ctx.fill();
    ctx.fillStyle = '#7a3c1c';
    rr(cx - 11, cy + 2, 22, 4, 2);
    ctx.fill();
    ctx.fillStyle = '#e09a46';
    rr(cx - 11, cy + 6, 22, 4, 2);
    ctx.fill();
    ctx.restore();
  }

  function drawHUD() {
    const g = ctx.createLinearGradient(0, 0, 0, 56);
    g.addColorStop(0, 'rgba(18,5,12,0.9)');
    g.addColorStop(1, 'rgba(18,5,12,0)');
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, W, 56);

    ctx.textBaseline = 'middle';
    ctx.textAlign = 'left';
    ctx.fillStyle = '#ffa8c4';
    ctx.font = '700 13px system-ui, sans-serif';
    ctx.fillText('SCORE', 20, 15);
    ctx.fillStyle = '#ffffff';
    ctx.font = '800 24px system-ui, sans-serif';
    ctx.fillText(score.toLocaleString(), 20, 37);

    ctx.textAlign = 'center';
    ctx.fillStyle = '#ffa8c4';
    ctx.font = '700 13px system-ui, sans-serif';
    ctx.fillText(state === 'demo' ? 'AUTO-PLAY' : 'LEVEL', W / 2, 15);
    ctx.fillStyle = '#ffffff';
    ctx.font = '800 24px system-ui, sans-serif';
    ctx.fillText(state === 'demo' ? 'AI CHEF' : String(level), W / 2, 37);

    ctx.textAlign = 'right';
    ctx.fillStyle = '#ffa8c4';
    ctx.font = '700 13px system-ui, sans-serif';
    ctx.fillText('LIVES', W - 20, 15);
    const alive = state === 'demo' ? 3 : Math.max(0, lives);
    for (let i = 0; i < 3; i++) drawMiniBurger(W - 26 - i * 30, 36, i < alive);
  }

  function drawHint() {
    if (state !== 'play' || paused || !ball.active || !ball.stuck || intermission > 0) return;
    const a = 0.6 + 0.4 * Math.sin(performance.now() / 300);
    ctx.save();
    ctx.globalAlpha = a;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = '700 17px system-ui, sans-serif';
    ctx.fillStyle = '#ffd6e3';
    ctx.fillText('Click, tap or press Space to serve', W / 2, 470);
    ctx.restore();
  }

  function drawBanner() {
    if (!banner) return;
    const t = banner.t, d = banner.dur;
    const a = Math.min(1, t * 4, (d - t) * 3);
    if (a <= 0) return;
    const s = 1 + Math.max(0, 1 - t * 4) * 0.35;
    ctx.save();
    ctx.globalAlpha = a;
    ctx.translate(W / 2, H * 0.5);
    ctx.scale(s, s);
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = '900 64px system-ui, sans-serif';
    ctx.lineJoin = 'round';
    ctx.lineWidth = 10;
    ctx.strokeStyle = 'rgba(70,0,30,0.75)';
    ctx.strokeText(banner.text, 0, 0);
    ctx.fillStyle = '#ffffff';
    ctx.fillText(banner.text, 0, 0);
    ctx.restore();
  }

  function render() {
    ctx.setTransform(sx, 0, 0, sy, 0, 0);
    ctx.save();
    if (shake > 0) ctx.translate(rand(-shake, shake) * 0.6, rand(-shake, shake) * 0.6);
    drawBackground();
    drawBricks();
    drawPaddle();
    drawBall();
    drawRings();
    drawHearts();
    drawPopups();
    drawHUD();
    drawHint();
    drawBanner();
    if (flashRed > 0) {
      ctx.fillStyle = 'rgba(255,40,90,' + (flashRed * 0.35).toFixed(3) + ')';
      ctx.fillRect(0, 0, W, H);
    }
    ctx.restore();
  }

  // ---------- overlay ----------
  function updateOverlay() {
    let show = true, msg = '', sub = '';
    if (state === 'play') {
      if (paused) { msg = 'Paused'; sub = 'Click or press P to resume'; }
      else show = false;
    } else if (state === 'over') {
      msg = 'Game over';
      sub = 'Score ' + score.toLocaleString() + ' · Click or press a key to play again';
    } else {
      msg = 'Click or press a key to start';
      sub = 'Demo mode: the AI chef is cooking';
    }
    overlay.classList.toggle('hidden', !show);
    msgEl.textContent = msg;
    subEl.textContent = sub;
  }

  // ---------- input ----------
  window.addEventListener('keydown', (e) => {
    const k = e.key;
    if (k === 'ArrowLeft' || k === 'ArrowRight' || k === 'ArrowUp' || k === 'ArrowDown' || k === ' ') {
      e.preventDefault();
    }
    if (state !== 'play') {
      if (!e.repeat) newGame('play');
      return;
    }
    if (k === 'ArrowLeft' || k === 'a' || k === 'A') keyLeft = true;
    else if (k === 'ArrowRight' || k === 'd' || k === 'D') keyRight = true;
    else if (k === ' ' || k === 'ArrowUp' || k === 'w' || k === 'W') {
      if (paused) togglePause();
      else serve();
    } else if (k === 'p' || k === 'P' || k === 'Escape') {
      togglePause();
    }
  });

  window.addEventListener('keyup', (e) => {
    const k = e.key;
    if (k === 'ArrowLeft' || k === 'a' || k === 'A') keyLeft = false;
    else if (k === 'ArrowRight' || k === 'd' || k === 'D') keyRight = false;
  });

  window.addEventListener('blur', () => { keyLeft = false; keyRight = false; });

  function toLogicalX(clientX) {
    const r = canvas.getBoundingClientRect();
    return clamp((clientX - r.left) / r.width * W, 0, W);
  }

  stage.addEventListener('pointerdown', (e) => {
    if (state !== 'play') { newGame('play'); return; }
    if (paused) { togglePause(); return; }
    paddleTarget = toLogicalX(e.clientX);
    serve();
  });

  stage.addEventListener('pointermove', (e) => {
    if (state === 'play' && !paused) paddleTarget = toLogicalX(e.clientX);
  });

  // ---------- sizing ----------
  function fit() {
    const hh = headerEl.offsetHeight, fh = footerEl.offsetHeight;
    const availW = Math.max(200, window.innerWidth - 24);
    const availH = Math.max(200, window.innerHeight - hh - fh - 60);
    let h = Math.min(availH, availW * H / W);
    let w = h * W / H;
    if (w > availW) { w = availW; h = w * H / W; }
    w = Math.floor(w);
    h = Math.floor(h);
    stage.style.width = w + 'px';
    stage.style.height = h + 'px';
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = Math.max(1, Math.round(w * dpr));
    canvas.height = Math.max(1, Math.round(h * dpr));
    sx = canvas.width / W;
    sy = canvas.height / H;
  }

  window.addEventListener('resize', fit);

  // ---------- loop ----------
  function frame(t) {
    const dt = lastT ? Math.min(0.033, (t - lastT) / 1000) : 0.016;
    lastT = t;
    update(dt);
    render();
    requestAnimationFrame(frame);
  }

  fit();
  newGame('demo');
  requestAnimationFrame(frame);
})();
</script>
</body>
</html>
Reasoning trace (91,861 tokens)
I'm planning a breakout game where the paddle is a hamburger and broken bricks burst into hearts, as a single self-contained HTML file. I'm sketching it as a canvas-based game with a self-playing demo behind a "Click or press a key to start" prompt.

Now I'm laying out the canvas so it scales to fit the window while keeping its aspect ratio, with devicePixelRatio scaling for sharp rendering. The hamburger paddle will be drawn once to an offscreen canvas, with a bun, lettuce, tomato, patty, and bottom bun.

Still writing the heart sprites and game logic...

Now I'm settling the state machine: a demo mode where the AI paddle plays behind a translucent "Click or press a key to start" overlay, and a click or key press moves the game into play.

Now I'm working out the game flow: the start overlay gets hidden once a click or key press begins a new game at level 1 with score 0 and three lives, and the player steers the paddle with the mouse, touch, or arrow keys. I'm weighing whether a key should launch the ball or whether a click does, and I'm leaning toward keeping pause out for now.

Losing the last life should send the game back to demo mode with a "Game over" overlay that asks for a click or key press to start, and the demo should quietly reset the board and restart itself whenever its own lives run out.

I'm settling how the ball launches: when a game starts from the overlay, I'm weighing whether that same click or key should also release the ball, since launching right away could feel abrupt.

I'm settling the start logic: the start action will just begin the game with the ball stuck to the paddle, and a click or Space will launch it, so the same keypress that starts the game doesn't also fire the ball.

Now I'm settling the serve: the ball will stay on the paddle and launch after about 0.8 seconds, or sooner if the player clicks or presses Space. For control, the mouse or touch position sets the paddle target, and the arrow keys or A/D move it, while the AI drives it in demo mode.

Now I'm working out the AI's aim: it predicts where the ball will land, then moves the paddle toward that spot with a little error so the demo looks human. I'm also settling the canvas size, weighing a wider landscape arena against a taller portrait one so it still fits on a small phone screen.

Now I'm settling the canvas layout: a fit-to-window size that keeps the arena's aspect ratio, with the backing store scaled to the device pixel ratio.

Still working out the canvas sizing: I'm fitting the arena to the window while keeping its aspect ratio, and setting the backing store and transform from the device pixel ratio so it stays sharp.

Now I'm deciding the bricks should be colorful rounded tiles with a light sheen, and I'm still weighing whether to theme them as cheese slices or pickles, or keep them as simple pastel blocks.

I'm deciding how the bricks take damage: the upper rows will need two hits and show a crack after the first, and any brick that breaks bursts into a shower of hearts. A brick that survives a hit might throw off a small puff of hearts too.

I'm working out the particle details: each heart gets a random angle and speed, a lifetime of about 1.2 to 2.2 seconds, and a size and color from a pink-to-white palette, with gravity and drag applied. I'm planning to pre-render the heart sprites to keep drawing fast, and I'm capping the particle count at around 2,500 so the frame rate holds.

Now I'm working out the collision logic: circle-against-box checks for the bricks, with the side of impact decided by overlap, and walls and the bottom edge handled separately. The paddle's hit position sets the bounce angle, ranging from -60° to 60° from vertical.

Now I'm settling the paddle's size and shape: a hitbox about 124 by 58, with the hamburger layers drawn procedurally each frame, from the domed sesame bun down to the flattened bottom bun.

Still writing the hamburger's top bun: I'm drawing it as a dome with quadratic curves and a slightly wavy underside.

Still writing the paddle's layers: lettuce, tomato, cheese, patty, and the flat bottom bun, all tuned to fit the roughly 58-pixel height. Next I'm setting the ball's hitbox against the top edge of the paddle.

Now I'm placing the ball and paddle on the same grid, with the paddle near the bottom and the ball sized to match. I'm weighing a small trail of faded hearts behind the ball for a bit of charm, and I'm working out the brick layout for each level, starting at six rows.

Now I'm sizing the brick grid: ten columns with a margin, the top offset clearing the score bar, and room left above the paddle for up to eight rows.

I'm settling what happens when the last life goes: the game ends, and the overlay shows the score with a prompt to play again. I'm also thinking about the demo, which might keep going on its own after a short pause.

I'm working out the demo's AI and the reset flow: once the lives run out, the board resets and the overlay returns to "Click or press a key to start," and I'm going to keep the AI good enough that the demo shows bricks breaking. Next I'm predicting where the ball will land by simulating its path from its current position and velocity.

Now I'm working out how to predict where the ball will land: I'll step its path forward to the paddle's height and fold the horizontal position back off the walls, so the AI can aim there.

Now I'm settling the AI's aim: it predicts the ball's landing spot, and in demo mode it adds a random offset so the paddle looks human. When the ball is heading up, I'm having the paddle simply track the ball's x position instead.

Now I'm settling the demo's speed and aim: the paddle chases its target at up to 520 px/s, with a small random error so the ball is mostly caught. I'm also varying the bounce angle with where the ball strikes the paddle, and I'm having the ball start at 330 px/s on level one and speed up by 25 px/s each level, capped at 560.

Now I'm settling the post-paddle bounce: the hit point sets the angle from vertical, up to 60 degrees, and I'm keeping a minimum vertical speed so the ball doesn't drift sideways forever. For bricks, I'm checking the closest point on each rectangle against the ball's radius, then using the smaller overlap on x or y to decide which side it hit.

Now I'm working out how to decide which side of a brick the ball hit: I'll check where the ball was on the previous frame, and if it was clear of the brick horizontally, I flip the horizontal speed, otherwise the vertical. That should hold up even when the ball is moving fast.

Now I'm checking how a brick break works: each hit drops its hit points, and a destroyed brick throws out a big burst of hearts and adds to the score, while a surviving one just cracks and sheds a few small hearts.

I'm adding small touches on top of the core loop: a few hearts puffing off the ball on paddle hits, a brief screen shake on big explosions, and a floating "+10" score popup. Hearts won't collide with anything; they just fall under gravity until they leave the screen. For the backdrop, I'm leaning toward a warm dark plum-to-maroon gradient with a soft pink glow, maybe a faint floor line, and a HUD bar along the top.

I'm settling the last details of the HUD: the score and level text drawn on the canvas, lives shown as small mini-burger icons, all in a system UI font with no external fonts. I'm also deciding the page around it, a small title above the canvas and a footer hint about controls, and I'm weighing whether to add a pause toggle on P or Escape.

I'm wiring up the start trigger: the whole game wrapper gets a pointer listener, so a click anywhere on the canvas area starts the game from the demo, while mouse movement still steers the paddle once it's live.

Now I'm wiring up the controls: a keydown listener that starts the game from the demo, with arrow keys steering the paddle, space to launch the ball, and P to pause. Mouse and touch movement will set the paddle's target position while play is live, and I'm making sure the canvas ignores default touch scrolling.

Now I'm settling what happens after a game over: the score shows on the overlay, and the player can click or press a key to try again. If nothing is done, the game drops back to the demo after a few seconds. For the overlay text, I'm working out the wording for each state, starting with the demo's title and prompt.

I'm deciding how the overlay text changes for each state, and I'm settling the serve timing: in play mode the player serves by clicking or pressing Space, with a hint shown while the ball is stuck to the paddle, while the demo auto-serves after a short pause.

Still wiring the serve timing: the AI will auto-serve after 0.8 seconds, while a human player has to click or press Space. The start click or keypress won't launch the ball, so the first input only starts the game.

I'm setting the page layout: the body fills the viewport with a dark background and centers a small title above the stage. The canvas sizing uses the space left after the header and footer, shrinking it to keep the aspect ratio, and I'm making sure nothing overflows horizontally on narrow screens. On a 375px-wide phone that works out to a canvas roughly 386px tall.

Now I'm adding a quick sanity check on the overlay and stage sizing: the stage gets fixed pixel dimensions from the computed canvas size, and the overlay sits over it with a pulsing prompt. I'm also bumping the hearts per brick up to around 70 for a fuller burst, while keeping an eye on frame cost.

I'm settling the particle counts: about 70 hearts per brick, capped at 2000 total, so the burst stays full without hurting frame rate. For the colors, I'm picking a varied set of pinks, reds, and white, and I'm weighing whether to tint hearts toward each brick's own color.

Still writing the heart drawing code...

Still writing the heart outline...

Still working out the heart outline: I'm using the parametric heart curve, with the cusp at the top and the tip pointing down, and scaling it to the brick's heart size.

Still writing particle code...

I'm picking the brick colors as a burger theme: tomato red, cheese yellow, lettuce green, ham pink, and bun orange, with each brick drawn as a rounded rectangle that has a lighter top highlight and a darker bottom edge. I'm also setting up a small trail of tiny hearts that spawns about every 40 milliseconds behind the ball, each one fading out quickly.

Now I'm settling how bricks look: they'll take the burger palette with a flash on hit, and two-hit bricks get a double border that shows cracks once damaged. I'm also sketching out a level pattern function that decides which bricks are present and how many hits each one takes.

Now I'm defining the level layouts as a list of pattern functions, cycling through them by level number, with the first level filling every cell.

Still defining the level patterns: the next one is a checkerboard, and I'm working on an inverted-triangle layout for the third.

Still working out the level patterns: I'm setting the row count so it grows with each level, and deciding which rows need two hits to break.

Now I'm settling how a level clears: once the last brick is gone, a banner shows the next level number while the ball stays stuck to the paddle for about 1.8 seconds, with hearts still flying and the paddle still movable. After that the next level is built and the ball resets. I'm also weighing what happens to the ball when a life is lost, since I'm not sure yet whether to spawn anything for that.

I'm deciding what happens when a life is lost: the ball resets and waits on the paddle with a serve timer. In the demo it auto-serves after about 0.9 seconds, while in play mode the player has to click or press Space, and a hint on the canvas says so while the ball is stuck.

Now I'm working out how the paddle follows input: arrow keys nudge a target position over time, while the mouse sets it directly, and the paddle eases toward that target so movement stays smooth.

Still working out the paddle's movement: I'm having it ease toward its target at a capped speed, so the AI and the mouse both follow smoothly.

Now I'm finishing the paddle clamping and the demo's random serve angle, then planning a pause state with an overlay. For the effects, I'm adding expanding ring flashes and white sparkle dots on each brick break, and I'm sketching the HUD with the score on the left and the level in the center.

Still writing the HUD and banner styling: I'm drawing mini hamburger icons for the lives on the right, and setting the fonts and a shadow for the level banner. I'm also weighing whether to cache the background gradient instead of redrawing it each frame.

Now I'm settling the background: a dark gradient from deep plum at the top to near-black below, with a soft pink glow at the top center. For the ball, I'm drawing a radial gradient with a white highlight offset up and to the left, fading from bright red to deep maroon, and adding a shadow glow around it.

Still writing the game's CSS and page layout...

Still writing the page styles...

Still writing the overlay and message styles.

Still writing the overlay and message styles...

Now I'm settling the page layout: the stage gets sized from the space left after the header and footer, with a minimum height for very short windows. The canvas is 640 by 720 logically, so on a typical laptop it comes out around 613 pixels wide. Next I'm starting the JavaScript, grabbing the canvas, its context, the stage, and the overlay elements.

Still writing the resize logic...

Still working out the canvas sizing: I'm setting the stage to the available width and matching its height to the 640 by 720 aspect ratio. For the backing store, I'm scaling by the device pixel ratio so it renders sharply.

Still writing the game state setup...

Still writing the collision code: I'm checking whether the ball is descending onto the paddle's top edge this frame, using the previous position to confirm it came from above.

Still writing the brick setup: I'm working out the grid of ten columns and the spacing between them, so the row sits centered in the canvas.

Still writing the brick colors and the per-brick gradient, with a highlight line along the top edge.

Still working out the level builder: I'm capping the row count, picking a pattern by level number, and making sure every pattern leaves at least some bricks, since the triangle layout would come out empty on its lower rows.

Still writing the level patterns: I'm making the triangle layout narrow at the bottom and wide at the top, and a columns layout that skips every third column, both of which will leave bricks in every row.

Still writing the serve logic: once the ball is released, I'm picking a launch angle and setting its speed for the level.

Still writing the ball positioning: when it's stuck, it sits on the paddle's top edge, and I'm setting the collision line a little below the dome.

Still writing the AI paddle logic: I'm deciding where it should aim when the ball is stuck, when it's heading down, and when it's heading up toward the top wall.

Still writing the paddle prediction logic...

Still working out the AI: I'm predicting where the ball will land and nudging the paddle toward that spot with a random offset for varied angles. I'm also considering capping the paddle's speed and having the AI refresh its target only every 0.08 seconds.

Now I'm setting up the game's state variables for the score, lives, and level, along with the bricks, hearts, rings, and popups.

Still writing the game state setup and the new-game reset...

Still writing the game state setup and the reset logic...

Still writing the game-over and demo-restart flow: once the game ends, the overlay shows the score and prompts a restart, and after a few seconds it switches back to the self-playing demo.

Now I'm wiring up the pointer handler on the stage: a click starts a new game if one isn't running, and otherwise it handles the pause toggle.

Still writing the keyboard controls...

Still writing the keyboard controls...

Still writing the pointer handling: I'm tracking the mouse's horizontal position on the canvas and mapping it to the paddle's target, and touch input will work the same way.

Now I'm setting the paddle's target: a steady drift from the arrow keys when they're held, otherwise wherever the pointer last was. Clamping keeps it on the stage, and the AI will only steer while the demo runs.

Still writing the paddle movement: the step is capped at a faster speed in demo than in play, and I'm treating the keyboard as a direct nudge to the paddle's position.

Still writing the paddle clamp and the ball's physics substeps, with wall bounces on the left, right, and top edges.

Still writing the paddle bounce: I'm checking whether the ball is coming down onto the paddle's top edge, and if it lands within the paddle's width, its angle depends on where it hits.

Still writing the paddle bounce and the brick collision checks inside the physics substep.

Still working out the brick collision: for each live brick, I'm finding the closest point on its rectangle to the ball's center and checking whether that falls within the radius.

Still working out the brick collision: when the ball overlaps a brick, I'm deciding whether to flip its horizontal or vertical direction, based on which side it came from and how far its center sits inside the brick.

Still working out the brick collision: if the ball's previous x was already outside the brick's horizontal span, I'll reset x to where it was, and likewise for y. That should keep the ball from sinking into the brick on a side hit.

Still writing the brick damage logic: on a hit, I'm decrementing the brick's hit points and flashing it, and when it breaks I'm removing it, updating the score, and checking whether the level is cleared.

Still writing the burst effect: I'm spawning hearts from a point with random angles and speeds, biased upward, each with its own rotation.

Still writing the burst effect: I'm tuning the hearts' gravity and drag, with a per-brick count and speed range, and capping the total so a full row clear doesn't flood the scene.

Still writing the particle draw loop: each heart fades out over its last 0.35 seconds and gets drawn rotated at its current position.

Still working out the heart sprite: I'm sizing it so it fills a small offscreen canvas and checking where its top lobes and point land, so it stays centered when drawn rotated.

Still writing the heart sprite: I'm sampling points along the heart outline to get its bounding box, then scaling it to fit a fixed square so it stays centered when drawn rotated.

Still writing the heart sprite: I'm sampling its outline to find the bounding box, then scaling it to fit a fixed square so it stays centered when drawn rotated.

Still writing the heart sprite: I'm placing its shine highlight on the upper-left lobe, sized relative to the heart's scale.

Still writing the heart sprite: I'm fixing the shine's position and size relative to the heart's scale, then picking a palette of six pinks and white for the hearts, with some smaller ones mixed in.

Still writing the paddle drawing: I'm translating to the paddle's position and starting the burger's layers.

Still writing the paddle's bottom bun, shaping its rounded edges.

Still writing the paddle's body: the patty, a rounded dark-brown layer with grill marks, and the cheese layer above it.

Still writing the cheese layer: a wavy-bottomed yellow shape across the top of the patty, with drips.

Still writing the cheese layer's zigzag drips, and the tomato slice above it: a rounded red strip with a light highlight along its top.

Still writing the lettuce layer: a green scalloped strip across the top of the tomato, with each bump drawn as a curve.

Still writing the paddle's top bun: I'm shaping its dome so it sits over the lettuce and extends a little past the lower layers.

Still writing the top bun: I'm shaping its dome with a curved bottom edge, filled with a golden gradient, and adding sesame seeds at fixed spots across it.

Still writing the paddle's collision: I'm settling its top edge a few pixels below the dome's peak, and setting the ball to sit just above that edge when it's stuck.

Still writing the brick drawing: I'm filling each brick with a vertical gradient in its color, adding a white shine strip near the top and a dark outline, then showing cracks or an inner border depending on its remaining hit points, with a white flash overlay when it's hit.

Still writing the brick color helpers, precomputing lighter and darker shades per brick, and sorting out the special hp2 bricks.

Still writing the brick drawing: for two-hit bricks I'm adding a darker slab beneath for a stacked look, and once one hit point is left I'm drawing two jagged cracks. Next up is the background gradient.

Still writing the background: I'm filling the whole canvas with a dark vertical gradient, then laying a soft pink radial glow over the middle.

Still writing the game's overlay: I'm wiring up the start, paused, and game-over messages so the right text shows for each state.

I'm wiring up the overlay so that a click on it, in the game-over state, starts a new game, and in the paused state it toggles pause. I'm also checking that hiding the overlay with pointer-events off still lets clicks reach the stage. Serving the ball on pointerdown is the part I still need to settle.

Still writing the game's level flow: I'm setting up a level-clear state that shows a popup and a short pause, then advances to the next level and shows a banner.

Still writing the level flow: while the intermission runs the ball stays stuck on the paddle, and auto-serve in demo mode waits until it's over. Then the next level is built, the ball speed ramps up per level with a cap, and a level banner appears.

Still writing the AI paddle logic: when the ball is stuck it tracks the paddle center, and while it's moving upward it follows the ball's x position.

I'm checking the AI's aim: it predicts where the ball will land and adds a random offset, so it can hit a bit off-center, and the ball should still usually be caught. Then I'm noticing the prediction doesn't account for the paddle's own offset, which I still need to reconcile.

Still writing the HUD: I'm drawing a dark translucent band across the top of the canvas, then setting up the label font and alignment for the score text.

Still writing the HUD: I'm filling in the score text, then the centered level label, and the lives shown as mini burgers on the right, plus a small "DEMO" badge in demo mode.

Still writing the HUD: for the center label I'm showing "DEMO MODE" in pink during auto-play and "LEVEL N" otherwise, then drawing a small burger icon for each remaining life.

Still writing the mini burger icon: I'm drawing the top bun as a half-ellipse dome with an orange fill.

Still writing the paddle's bottom bun as a half-ellipse shape, the mini burger's layers: lettuce, patty, and bottom bun.

Still writing the particle update: I'm setting a drag factor from the frame time, then each heart gets gravity, the drag on its horizontal and vertical speed, its position and rotation advanced, and its life counted down.

Still writing the particle update: the hearts get filtered out once their life runs down or they fall past the bottom, compacted in place to keep it cheap.

Now I'm wrapping up the particle work: spawning a few faint trail hearts behind the ball with small random speeds and short lives, and planning to raise the burst to around 80 hearts with a wider size range, plus a few larger ones.

Still writing the page styles: I'm setting up the body as a centered flex column with a dark radial background and light pink text.

Still writing the page styles: I'm finishing the stage's frame with a rounded clip, a soft pink glow, and the layout around the header.

Still writing the overlay's CSS: the message text is sizing to fit the window, and the overlay fades out with pointer events turned off once it's hidden.

Still writing the page styles: I'm styling the header and the overlay message, with a pulsing pink border and a soft glow.

Still writing the overlay markup: I'm adding the centered message and the sub-prompt inside it, with a click handler wired to the overlay.

Still writing the page markup: I'm adding the overlay's message and sub-prompt, then the footer with the controls.

Still writing the game constants: I'm setting the board size, the brick grid, paddle dimensions, a heart cap, and the palettes for the bricks and hearts.

Still writing the heart sprite: I'm tracing the heart outline from a parametric curve so I can pre-render it in each palette color.

Still writing the heart sprite: I'm finishing the outline loop and computing the scale and offset so the shape fits the sprite, then starting a radial gradient for its fill.

Still writing the heart's shine: I'm adding a small white highlight ellipse on the upper-left lobe, after filling the sprite with its solid color.

Still writing the heart sprite: I'm finishing the lobe placement so the shine sits on the upper-left lobe, then planning a helper that mixes two hex colors into an rgb string for the brick shading.

Still writing the game loop's state variables: I'm setting the score, lives, and level, plus the brick, heart, ring, and popup lists and the ball.

Still writing the game state: I'm setting the paddle's position and target, the key flags, and timers for intermission, serve, and the AI's aim offset, plus the ball's default speed.

Still writing the level patterns: I'm defining the brick layouts as functions of row and column, from a full grid through a checkerboard, a tapering triangle, a striped version, and a bordered pattern with holes.

Still writing the level builder: I'm setting the brick grid's row count from the level number, picking a pattern for the layout, and giving some bricks two hit points on the higher levels.

Still writing the level builder: I'm picking the brick pattern for this level and deciding which bricks take two hits, leaning on a deterministic scatter on the higher levels.

Still writing the brick layout: I'm placing each brick at its grid position with its color, shading, and hit points, and the number of rows grows with the level.

Still writing the reset function: the ball gets zeroed velocity and its speed for this level, then sits on top of the paddle at its center.

Still writing the serve function: when the ball is stuck, it gets a random launch angle and velocity from the paddle, and I'm weighing whether to offset the angle by the paddle's position.

Still writing the game state: I'm setting up the new-game reset, starting from level 1 with three lives and the paddle centered, and keeping the existing hearts so they fade out on their own.

Still writing the game-over and life-loss logic: I'm decrementing lives, triggering the shake and red flash, and either ending the game or resetting the ball.

Still writing the game-over logic: the ball gets hidden instead of parked off-screen, so it doesn't reappear once the paddle moves.

Still writing the game-over and level-clear logic: I'm adding an active flag on the ball so it stops drawing and updating once the game ends, and planning a level-clear step that shows a banner and a celebratory burst.

Now I'm writing the level-clear step, with a celebratory burst across the board and a banner. Still writing the intermission logic: the level builds and the ball resets once the timer runs out.

Still writing the update loop: I'm working through the timers and the paddle movement while the game is over.

Still writing the update loop: after the game-over timer, I'm adding the paddle movement and player or AI control, then handling the intermission countdown and the ball's stuck state, with the demo auto-serving once its timer runs out.

Still writing the player controls: I'm moving the paddle left or right with the arrow keys and syncing its target to the new position.

Still writing the paddle movement: I'm clamping the paddle's position and easing it toward its target, with a speed cap.

Still writing the AI control: I'm setting up its target logic before clamping and stepping the paddle toward it.

Still writing the AI control: I'm picking its target from the ball's state, following the ball's predicted landing spot when it's heading down and drifting toward the center otherwise.

Still writing the collision code: I'm defining the brick hit handler and stepping the ball in three sub-steps per frame, starting with the side walls.

Still writing the collision code: I'm bouncing the ball off the side walls and ceiling, then checking whether it has landed on the paddle, and if so, setting its new direction from where it hit.

Still writing the collision code: I'm checking for a paddle hit with a tolerance so the ball doesn't slip through, and I'm worried about the edge where it clips the paddle's corner.

Still writing the collision code: I'm checking each brick's nearest point to the ball to find an overlap.

Still writing the collision code: I'm bouncing the ball off each brick's nearest edge, choosing whether to flip its horizontal or vertical velocity based on which side it came from.

Still working through the bounce logic: when the ball is inside a brick, I'm flipping whichever velocity component makes sense and reverting its position, though I'm wary that it might stay overlapping in edge cases.

Still working out the brick collision cooldown: after a hit the loop breaks, but the ball could still overlap the same brick on the next substep, so I'm tracking the last brick hit within the step and skipping it.

Now I'm working out how the ball should behave during the intermission between levels, since the collision code can end a level mid-step.

Still writing the level-clear handling: I'm marking the ball as stuck so it returns to the paddle with zero velocity, and I need to make sure the step loop stops rather than continuing with the ball frozen mid-field.

Still writing the brick hit handler: I'm decrementing the brick's hit points and flashing it, and if it survives, I'm adding a small heart pop and a ring, then bailing out before the scoring.

Still writing the brick hit handler: a surviving brick gets a heart pop and ring, and once a brick is destroyed I'm adding a bigger heart burst, a score popup, and a screen shake, then checking whether the board is clear.

Still writing the brick burst helper: I'm choosing the random angle and speed for each particle, scaled by the size multiplier.

Still writing the burst helper: I'm adding the hearts with random angles and speeds, with an upward bias, rotation, and varied sizes.

Still writing the hearts update: I'm stepping each heart's lifetime down and compacting the list so expired ones drop out.

Still tuning the particle motion: the hearts get a little gravity and a drag so the bursts slow down and fall, and I'm planning to clip the list so expired hearts drop out.

Still writing the draw code: I'm sizing each heart with a quick pop-in that scales up over its first moments and fades out as its lifetime runs low.

Still writing the ring and popup code: I'm drawing each ring as an expanding, fading stroke, and queuing score popups that rise and fade out.

Still writing the rendering code: I'm drawing the text popups with a fade and rise, adding a bigger centered version for the level-clear message, and sketching the banner's fade-in and fade-out with a small scale pop.

Still writing the rendering code: I'm finishing the HUD, the banner, and the red flash vignette, then drawing the ball with a radial gradient and a pink glow.

Still writing the ball's look: I'm giving it a radial gradient and a glow, weighing whether a tomato-style red fits with the hearts.

Still writing the ball's trail: I'm spawning small, short-lived pink and white hearts behind it at intervals, with light random motion.

Still writing the paddle hit: I'm adding a small burst of hearts where the ball meets it, nudging the AI's aim in demo mode, and giving the paddle a brief squish that decays after impact.

Still writing the paddle squish and speed code: the paddle's squish decays over time, and the ball's speed is capped by level.

Still writing the HUD: I'm drawing a dark gradient band across the top of the canvas and setting up the small pink "SCORE" label.

Still writing the HUD: I'm placing the score and level labels on the top band, with the mode text switching between auto-play and the level number.

Still writing the HUD: I'm adding the lives label on the right, then drawing three mini hamburger icons beside it, dimmed when a life is lost.

Still writing the mini burger icon: I'm drawing its stacked layers, with the bun, lettuce, and patty, and dimming the whole icon when that life is gone.

Still writing the hamburger icon: I'm drawing the top bun as an upper half-ellipse and placing the lettuce and patty layers just below it.

Still writing the HUD and the playfield overlays: I'm drawing the lives icons, the banner text, and the red flash vignette that fades out over time.

Still writing the paddle drawing: I'm laying out the seed points for the scattered hearts, then the paddle squish scales it slightly on impact.

Still writing the paddle seed points: I'm placing them inside the dome's interior, keeping them further from the edges where the dome curves down.

Still working out the paddle's seed points: I'm checking the dome's top curve so the scattered hearts stay inside it, and I'm placing a set of points lower near the edges where the dome curves down.

Still writing the paddle seed points: I'm checking the dome's top curve to confirm the seed at x≈24 sits inside, then starting the dome path itself.

Still writing the dome path: I'm tracing its outline with a bezier curve across the top, then closing it with a shallow curve along the bottom.

Still writing the paddle's dome path: I'm settling on a bezier top with its peak near the center, and checking that the bottom curve sits about 24 pixels down so the shape stays compact.

Still writing the lettuce: I'm building its wavy outline with a row of quadratic curves across the paddle's width.

Still writing the lettuce: I'm finishing its scalloped lower edge so it peeks out past the bun on both sides.

Still writing the lettuce's scalloped edge: I'm setting up the path so it starts from the right side of the paddle and works its way back across the width.

Still writing the lettuce's scalloped edge: I'm finishing the wavy bottom curve so it hangs just below the bun on both sides.

Still writing the paddle's cheese layer: I'm adding a wavy, dripping edge below the tomato.

Still writing the cheese layer: I'm drawing its wavy underside as a row of curves that dip down and back up across the patty's width.

Still writing the cheese drips and the patty: I'm drawing the wavy cheese underside, then the patty with a few diagonal grill marks, and starting the bottom bun.

Still writing the bottom bun: I'm tracing its rounded outline with a few quadratic curves, then closing the shape along the top edge.

Still writing the bottom bun, patty, and cheese layers: I'm setting the bun, then the patty over it, then the cheese drips over the patty.

Still writing the paddle's layers: I'm sizing the lettuce and tomato bands, then ordering the bottom bun, patty, cheese, tomato, lettuce, and top bun from bottom to top.

Still writing the resize logic: I'm adding a subtle drop shadow with no filter, then sizing the paddle at 124 by 58 so it still reads as a hamburger, and next I'll handle the fit function that scales the stage to the available space.

Still writing the resize code: I'm storing the stage's CSS size, then setting the canvas backing store to match the device pixel ratio so it renders sharply.

Still writing the game loop: I'm setting up the frame timing with a capped delta, and mapping pointer positions back into logical canvas coordinates.

Still writing the game loop: I'm clamping the frame delta so a long first frame doesn't blow up the physics, then running update and render each tick.

I'm double-checking the demo AI's prediction, since the ball's bounces off bricks change its direction, so I'll re-predict every frame. When the ball is heading up, the paddle drifts toward a damped target, and once it starts coming back down the prediction takes over. The paddle's max speed of 900 px/s should be enough to cover the field before the ball arrives.

Still writing the game loop: I'm checking when a ball counts as lost, once it drops below the bottom edge of the playfield, and making sure the score display formats with commas.

Still writing the overlay logic: I'm figuring out which message to show based on whether the game is paused, running, or over.

Still writing the overlay text: I'm setting the paused and game-over messages, with the score shown in the game-over subtitle, and making sure the overlay hides during active play.

I'm wiring up the pointer handling so a click while paused just resumes the game and returns early. P toggles pause during play, and in the demo or game-over states it starts a fresh game instead.

Still writing the render function: I'm setting the transform, applying a small screen shake when it's active, and drawing the background, bricks, paddle, ball, effects, HUD, and overlay banners in order.

Still writing the render function: I'm drawing the hearts now, fading each one out as its life runs down and popping it in with a quick scale.

Still writing the hearts render: I'm drawing each one rotated and scaled by its remaining life, and I'm considering a 32px sprite instead of 48px so the downscaling looks cleaner.

Still writing the brick draw function: I'm setting the brick's rounded shape, then a vertical gradient from light to dark across its height.

Still writing the brick draw function: I'm adding a gloss highlight and an outline, and when a double-stacked brick is cracked I'm starting to draw its crack lines.

Still writing the brick draw function: I'm finishing the cracked lines and the flash overlay, then closing out the brick loop.

Still writing the brick draw function: I'm checking whether the brick is still double-stacked, so the dark underlay only shows while it has full health and disappears once it's cracked.

Still writing the rounded-rectangle helper: I'm tracing the corners with arcs and closing the path so each brick and gloss shape can be filled.

Still writing the banner: I'm setting its fade and scale timing, then positioning the text and styling the font and outline.

Still writing the popups: I'm working out each popup's life fraction so its fade and rise can be drawn.

Still writing the popups: I'm finishing the text styling and stroke, then offsetting each popup upward as its fade plays out. I'm also updating popup timers and dropping any that have expired, with score popups in gold.

Still writing the hint: I'm drawing a pulsing "Click or press Space to serve" line near the bottom of the stage, but only while the ball is stuck to the paddle during active play.

Still writing the trail: I'm adding hearts behind the ball on a timer while it's moving, each with a small random offset, velocity, and fade.

Still writing the game loop: I'm adding the heart-creation helper and using it for both the burst and the trail.

Now I'm checking the pause and game-over edge cases: the ball goes inactive after a game over, and losing a life resets it to stuck on the paddle.

Still writing the game loop: I'm moving the pause check to the very top of update so everything freezes while paused, and moving the game-over handling to run after the particle updates.

Still writing the game loop: I'm finishing the update order so the AI or player control, paddle movement, intermission countdown, and ball stepping all run in sequence.

Still writing the paddle hit logic: I'm setting the paddle-hit handler up to take the relative position where the ball lands.

Still writing the paddle hit handler: I'm squishing the paddle, spawning a burst of hearts at the ball's position, and resetting the demo AI's aim offset.

I'm sorting out the level progression now: levels from 2 onward start the top row with two-hit bricks. Scoring works per brick, weighted by row and hit points, so top-row bricks are worth more. I'm also adding a level-clear bonus that scales with the level, shown as a popup.

Still writing the layout math: I'm working out the canvas size for a 1280×800 window, roughly 605 by 680 pixels after subtracting the header, footer, and padding, so the whole page fits.

Working out the canvas height now: subtracting the header, footer, and padding from the window height, with the gaps and body padding summed so the whole page fits without a scrollbar.

Still tightening the page layout: I'm setting the body to fill the viewport with no margin, using min-height so the header, canvas, and footer fit without a scrollbar, and centering the stage horizontally. I'm also making sure the footer and header heights are measured on resize, since they can wrap on narrow screens.

Working out the paused state now: pausing should freeze everything except the hearts, but I'm rethinking that. I'll make the pause check return at the very top of update, and handle the game-over timer after the particle updates so the screen still animates when the game ends.

Still writing the page styles: I'm setting the body to a centered flex column with a dark radial background and light text, using min-height so nothing scrolls.

Still writing the page styles: I'm laying out the stage with fixed dimensions, rounded corners, and a dark background so the canvas sits cleanly inside the page.

Still writing the page styles: I'm finishing the overlay's transition so it fades out smoothly when the game starts.

Still writing the page styles: I'm adding the overlay's message and subtitle styling, with a pulsing border and a rounded background.

Still writing the page styles: I'm finishing the pulse animation for the overlay message and setting the footer's small text and key-hint styling.

Still writing the game constants: I'm setting up the brick grid, paddle size, heart cap, and the color palettes for bricks and hearts.

Still writing the canvas setup: I'm grabbing the context, stage, overlay, and HUD elements, and defining the scale variables and small clamp and random helpers. Now I'm starting the heart shape points for the sprite.

Still writing the heart sprite: I'm building the heart outline from its parametric points and finding the bounds so I can size the sprite canvas.

Still writing the heart sprite: I'm finishing the bounds loop and sizing the sprite canvas so the heart fits with some margin, then drawing its outline.

Still writing the heart sprite: I'm setting the shine's fill style and drawing a small highlight ellipse near the left lobe, then returning the sprite and building one per heart color.

Still tuning the heart shine: I'm nudging its ellipse position to about 30% across and 34% down the sprite, which should land it near the left lobe.

Still writing the helpers: I'm adding a function that turns the packed hex value into its red, green, and blue channels, then a color-mixing helper that blends a palette color toward white or black by a given amount. I'm also setting up the rounded-rectangle drawing function and a speed curve that rises with each level, capped at a maximum. Next I'm declaring the game state, starting in demo mode.

Still writing the game state: I'm declaring the score, lives, and level counters, plus the bricks, hearts, rings, and popups lists. I'm also setting up the paddle, its target position, the key flags, and timers for intermission, game-over, and serving, then initializing the ball so it starts stuck to the paddle.

Still writing the level builder: I'm generating each level's brick layout from a pattern, with the top rows taking two hits from level 2 onward.

Still writing the level builder: I'm finishing each brick's position, hit points, and color shades, and I'm setting its score value from its row and hit points.

Now I'm wrapping up the level builder: I'm setting each brick's hit points and shades, and then pacing the ball's speed to the current level.

Still writing the reset logic: I'm clearing the ball's velocity and marking it stuck to the paddle, setting its speed from the level, and starting a short serve timer.

Still writing the reset logic: I'm making sure the ball stays on the paddle until the serve, then checking the collision condition so it bounces off the paddle's top edge.

Still writing the level-clear handler: I'm zeroing the ball's velocity, snapping it back onto the paddle, and adding the level bonus to the score with a gold popup. I'm also triggering a burst of hearts at the center of the field and a small screen shake.

Still writing the ball physics: I'm adding a lastHit tracker, then stepping the ball in three sub-steps per frame and clamping it against the left wall.

Still writing the ball physics: I'm bouncing the ball off the side walls and the top, then checking whether it has landed on the paddle, and if so, angling it off the paddle based on where it hit.

Still writing the ball physics: I'm checking whether the ball is about to hit the paddle's top edge and, if so, angling it off based on the hit position, with a little extra tolerance near the sides.

I'm checking each brick for overlap with the ball's circle, skipping any brick the ball just hit so a two-hit brick doesn't register twice in one frame.

Still writing the brick collision: I'm checking whether the ball's previous position overlapped the brick horizontally or vertically, so I can decide which velocity component to flip.

Still writing the collision code: I'm recording which brick was hit and the ball's offset from its nearest point, so I can tell whether to flip the horizontal or vertical velocity.

Still writing the brick collision: I'm flipping the ball's horizontal or vertical velocity based on which side it came from, reverting its position to the previous step, and marking the brick as just hit so it isn't counted again this frame.

Still writing the game loop's ball physics: I'm finishing the brick hit handling and checking whether the ball has fallen below the field, which triggers a life loss.

Still writing the game state: I'm initializing a new game with the score, lives, and paddle position reset, and clearing the popups and rings.

Still writing the game loop: I'm wrapping up the newGame setup, then defining the togglePause handler and starting the keydown listener for input.

Still writing the keydown handler: I'm checking whether the pressed key is a movement key and, if so, preventing its default scroll, then starting a new game if the state isn't 'play'.

Still writing the keyup handler: I'm clearing the left and right movement flags when those keys are released.

Still writing the keyup and pointer handlers: I'm clearing the movement flags on release and on window blur, then wiring up pointer input so the paddle follows the mouse and a click either starts the game, resumes from pause, or serves the ball.

Still writing the pointer handling: I'm adding the mouse-move listener and the toX helper that maps the cursor position onto the field's width.

Still writing the update loop: I'm pausing it when the game is paused, then ticking the hearts, rings, and popups, decaying the screen shake and red flash, and counting down the brick flashes and the banner timer.

Still writing the update loop: I'm handling the game-over timer and restarting into demo mode, then driving the paddle with the AI or the player's input, and easing the squish back down. I'm also counting down the intermission and building the next level when it ends, and I'm about to check whether the ball is active before moving it.

Still writing the update loop: I'm handling the ball's stuck state so it stays on the paddle during intermission and the demo serve timer, then stepping the trail and the ball itself.

Still writing the trail: I'm pushing heart sprites from the ball's position as it moves.

Still writing the heart spawner: I'm making a makeHeart helper that drops the oldest heart when the list is full, then records each heart's position, velocity, spin, and lifetime.

Still writing the burst helper: I'm adding a loop that spawns a set number of hearts at a point with random angles and speeds, then trimming the list if it grows too long.

Still writing the burst helper: I'm finishing the heart object's fields with a random spin, size, and lifetime, and adding a slight upward bias to the spray.

Still writing the heart update: I'm applying gravity and drag to each heart, moving and spinning it, and dropping the ones that have expired or fallen off the field.

Still writing the rings: I'm adding an addRing helper that stores each ring's position, color, lifetime, and size, then starting the ring update loop.

Still writing the ring helpers: I'm drawing the ring fade-out and popup update logic next.

Still writing the popup and AI code: I'm finishing the popup update and the color choice for brick point labels, then starting the AI paddle targeting that predicts where the ball will land.

Still writing the AI targeting: I'm computing where the ball will cross the paddle's height by stepping its position forward in time, clamped to the field's edges.

Still writing the paddle movement: I'm clamping the target, easing the paddle toward it at a capped speed, and starting the player's keyboard control.

Still writing the background: I'm setting up a vertical gradient fill for the canvas backdrop.

Still writing the background: I'm adding a faint radial glow over the gradient, then laying down a dot grid across the field.

Still writing the background details: I'm adding a shadow ellipse under the paddle and starting its squish-scaled drawing.

Still writing the paddle's shadow and bun shape: I'm finishing the shadow ellipse, then translating and squashing the drawing origin so the paddle compresses on impact.

Still writing the bun shape: I'm finishing the bottom bun's gradient fill and tracing its rounded outline.

Still writing the patty and its grill marks: I'm filling a rounded rectangle with a dark brown gradient and stroking a set of diagonal lines across it.

Still writing the cheese: I'm drawing its top edge as a series of wavy curves across the patty.

Still writing the cheese: I'm finishing its scalloped bottom edge with alternating drips across the patty.

Still writing the lettuce: I'm adding the cheese highlight, then the tomato slice and its shine, and starting the lettuce's scalloped edge.

Still writing the lettuce: I'm drawing its scalloped bottom edge, working out the curve points so the leaf lines up from one side of the paddle to the other.

Still writing the lettuce: I'm checking whether its top corners stick out past the bun's rounded edges, and I'm starting the lettuce's top edge a little lower so the corners don't look sharp.

Still writing the top bun: I'm finishing its curved outline with a bezier across the upper arc.

Still writing the top bun: I'm working out the bottom edge's midpoint so the curve lines up with the sides of the bun.

Still writing the top bun: I'm adding sesame seeds across its surface, starting the loop that places each one.

Still writing the seed placement: I'm picking a set of coordinates for the sesame seeds so they sit inside the dome, checking that the ones near the edges stay within its curve.

Still writing the highlight: I'm adding a white curved stroke along the bun's upper dome to suggest a shine.

Still writing the ball: I'm drawing it as a radial gradient with a pink glow around it.

Still writing the ball and HUD: I'm finishing the ball's glow, then drawing the score display and mini hamburger icons, followed by the hint and banner text.

Still writing the render loop: I'm applying the shake offset, drawing the background, bricks, paddle, ball, rings, hearts, popups, and HUD in order, then the hint, banner, and red flash before restoring the context.

Still writing the HUD: I'm drawing the score label and its large number in the top bar.

Still writing the HUD: I'm drawing the level or auto-play label in the center, then the lives label on the right.

Still writing the HUD: I'm making sure the score text is left-aligned before drawing it, then centering the level label and right-aligning the lives icons.

Still writing the HUD: I'm setting the text alignment back to left before the score, since the previous frame left it on right, and then drawing the lives icons at the right edge.

Still writing the mini burger: I'm drawing its top dome and starting the green lettuce layer beneath it.

Still writing the mini burger: I'm drawing the lettuce layer, the patty, and the bottom bun as stacked rounded rectangles below the dome.

Still writing the game's render loop and HUD: I'm finishing the lives display and the mini burger icons, then checking that the text alignment resets each frame so the score doesn't draw right-aligned.

Now I'm checking the AI's prediction: the ball starts stuck to the paddle, and after the serve, the paddle targets the ball's predicted landing point.

I'm checking whether the ball can stall: its speed stays constant and it always leaves the paddle with a meaningful vertical component, so flipping directions off bricks or walls can't flatten it into a near-horizontal crawl.

Still writing the HUD: I'm checking the score's alignment and the lives icons' spacing, then the banner sits lower on the screen between the bricks and paddle.

Next I'm checking the overlay: in demo mode its gradient just dims the field a bit, and it doesn't block the canvas, so the game stays visible. Clicks land on the overlay and bubble up to the stage to start a new game, and once play begins the overlay is hidden and ignores pointer events. I'm also making sure the canvas is fitted once before the first frame so it has a size on initial paint.

I'm checking the mobile layout: the stage fits within the 375px width with the body padding, so nothing overflows. The canvas is scaled down to about 0.55x on small screens, which makes the HUD text quite small, so I'm considering larger label and value fonts to keep the score readable.

Still writing the HUD font sizes: I'm bumping the labels, values, banner, popups, and hint up a bit so they stay readable once the canvas scales down on phones.

Now I'm checking how the AI behaves in demo mode after the serve, with the ball starting at the paddle's center and a random angle.

I'm checking the AI's aim after the serve: the paddle tracks a point partway between the center and the ball's x-position, so each frame it re-predicts where the ball will land. The offset of up to ±22px means the hit point varies and the ball leaves at a range of angles, which keeps the demo from looking repetitive.

Still writing the brick hit handler: I'm reducing the brick's hit points, and when it survives I spawn a small burst and ring with a light screen shake. When it breaks, I add a bigger burst, two rings, and add the brick's points to the score.

Still writing the brick hit handler: I'm finishing the score popup and the screen shake, then checking whether the last brick has been cleared.

Still writing the game loop: I'm finishing the fit and the initial game setup, then wiring up the resize listener and starting the animation frame loop.

Still writing the controls: I'm wiring the hint text to say to click or press Space to serve, with a "Tap to serve" variant for mobile.

Still writing the banner: I'm setting up its fade-in and scale-down over the first moments, then the large centered text font.

Still writing the banner: I'm finishing its large centered text, drawing a dark outline and then the white fill, and restoring the context afterward.

Still checking the footer and the overlay text. I'm also making sure the dimming overlay doesn't hide the HUD on the canvas beneath it.

Next I'm settling the overlay's look: a radial gradient that starts fully transparent in the middle and only darkens toward the edges, so the demo stays visible behind the centered message.

Next I'm checking how the paddle behaves in the game-over state: it should drift back toward center, so I'm confirming the target is set there and that the lerp settles without overshooting.

Now I'm checking the paddle and ball positions: the ball sits at the paddle's top edge while stuck, and on serve it moves upward. I'm also confirming the collision check only triggers when the ball was above the paddle on the previous frame, so it doesn't bounce from below.

Still writing the game loop: I'm wrapping the code in a strict-mode IIFE, and checking that the intermission path rebuilds the level and resets the ball speed before the next serve.

Now I'm writing the final file: a single HTML document with the canvas, the overlay, and the script wrapped in strict mode.

Still writing the HTML markup: the header, the stage with its canvas and overlay, and the footer with the control hints.

Still writing the page layout: I'm checking the overall height at 1280×800, working out that the stage fills the remaining space and comes out about 592px wide.

I'm checking the mobile fit numbers: the header and footer heights take up about 100px together, which leaves roughly 505px of height on a 375×667 phone, and I'm still working out how that compares with the available width.

Still writing the arena constants: I'm setting the logical size to 640×720, with a 10-column brick grid, paddle dimensions, and a cap on the number of hearts.

Still writing the arena constants: I'm adding the color palettes for bricks and hearts, plus the seed positions and patterns that shape the brick layout.

Still writing the game's helpers: I'm setting up the canvas scaling and clamp and random utilities, then the heart sprites and color helpers, plus a rounded-rectangle path function and the game state.

Still writing the fit function: I'm working out the stage's width and height from the available space and the 640×720 aspect ratio, then setting the canvas backing size with devicePixelRatio scaling, and recording the scale factors.

Still writing the final assembly: I'm checking the hint alpha timing and the sprite fade for the hearts.

Still writing the final file: I'm wrapping up the arena constants and the game's helper functions, then writing out the fit logic in full.

Still writing the arena constants: I'm setting the brick grid, paddle size, and heart cap, then adding the color palettes and seed positions for the brick layout.

Still writing the arena palettes and the brick layout patterns.

Still writing the heart sprites: I'm building the heart outline from a parametric curve, then starting a small offscreen canvas to draw each colored heart sprite.

Still writing the sprite: I'm finishing the heart outline's bounding box and scale, then filling the path on the small offscreen canvas.

Still writing the heart sprite: I'm adding a small white highlight ellipse near the upper left of the shape, then returning the finished sprite canvas.

Still writing the heart burst: I'm defining a helper that pushes each heart with a random spin and lifetime, and starting a loop that spawns a set number of them at a point with a size range.

Still writing the heart burst: I'm spawning each heart with a random direction and speed, a small chance of a larger one, and a random sprite color.

Still writing the heart draw loop: for each heart I'm working out a pop-in size from its age, fading it out as its lifetime runs down, then drawing its sprite rotated around its center.

Still checking the heart draw loop: I'm making sure the per-heart alpha and the trailing reset don't leak into the next pass. I'm also watching the heart count, since a long first second could make drawing too heavy.

Now I'm checking the overlay's start-up text and whether a key press in demo mode should begin the game straight away, which it should. I'm also confirming that pausing toggles correctly and that Space does nothing when the ball isn't stuck.

Still checking the rest of the game loop: I'm confirming that paused frames still render, and that the heart draw happens before the HUD so the score stays on top.

Still writing the game state setup: I'm declaring the current mode, score, lives, and level, along with the brick list, the particle lists, and the paddle and its target position.

Still writing the stepBall function: I'm splitting each frame into smaller sub-steps so the ball can't tunnel through bricks, and resetting the last-hit tracker at the start of each pass.

Still writing the collision code: I'm stepping the ball forward in smaller sub-steps, checking the walls, and testing for a paddle hit, and now I'm starting to scan the bricks for a collision.

Still writing the brick collision: I'm checking the nearest point on each brick against the ball's radius, and when one is hit I'm working out whether to flip the horizontal or vertical velocity from where the ball was on the previous sub-step.

Still writing the brick collision: I'm checking whether the ball is actually crossing the brick's top or bottom edge or its side, using where it was on the previous sub-step, so I know which velocity to flip.

Still writing the paddle hit: I'm squishing the paddle on contact and spawning a burst of hearts at the paddle's top edge, plus adjusting the AI's aim offset in demo mode.

Still writing the overlay: I'm setting its message and subtitle for the paused, game over, and start states, and toggling it hidden while play is running.

Still checking the paddle drawing: I'm confirming the transform puts the local origin at the paddle's top-left corner, so its shadow lands just below it.

Still writing the paddle drawing: I'm drawing the paddle's shadow, then applying the squish scale around its bottom center, and starting the bottom bun's gradient.

Still writing the bun shape: I'm filling the bottom bun's rounded outline with its gradient, then starting the patty's gradient.

Still writing the patty's grooves: I'm looping over four spots across the patty to draw the little marks.

Still writing the cheese: I'm drawing the drippy bottom edge with a row of quadratic curves across the top of the patty.

Still writing the tomato: I'm giving it a red gradient and drawing its rounded strip across the patty.

Still writing the lettuce: I'm drawing its wavy scalloped edge across the top of the tomato.

Still writing the lettuce: I'm finishing its scalloped edge and closing the path back to its starting point.

Still writing the top bun: I'm setting its gradient and starting the rounded outline with a bezier curve across the top.

Still writing the top bun: I'm drawing its seeds as small ellipses and starting a translucent white stroke for its highlight.

Still writing the top bun's highlight: I'm setting the line cap to round, then drawing a translucent curved stroke across the upper part of the bun before restoring the canvas state.

Still writing the rect helper: I'm drawing rounded rectangles with arcTo, which should hold up at small sizes too.

Now I'm checking whether the AI demo could lose lives early, which I think it shouldn't, since the wall-bounce prediction and the clamped paddle target look correct. I'm also making sure the predicted-x calculation still works when the ball is far above the paddle with a small downward speed.

Still checking the remaining edge cases: I'm confirming the ball can't get stuck between the paddle and the bottom, and that paddle hits outside its edges clamp correctly. Then I'm making sure the footer controls are written as kbd elements and that the console stays clear of errors.

Now I'm writing out the full final version of the code, checking each part for typos as I go.