Models 44

Grok 4.7

by SpaceXAIreasoning defaultClean
Open in new window

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

How it went

Total time
16 min 26 s
Cost
$0.677
Output tokens
112,540
Spent thinking
96,964 tokens
Speed
114.4 tok/s
Console errors
0
First token after
1.7 s
First visible text after
14 min 43 s
Prompt tokens
1,597
Code size
39.4 KB
Finish reason
stop
Served by
xAI
Model id
x-ai/grok-4.7
Reasoning sent
model default
Max output
450,000 tokens
Generated
Nov 12, 2026

What it was asked

The prompt
“i want a breakout game where the paddle is a hamburger and every brick you break explodes into tons of little hearts”
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 1269 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Heartbreak — Burger Breakout</title>
<style>
  * { box-sizing: border-box; }
  html, body {
    margin: 0;
    height: 100%;
    overflow: hidden;
    background: #1a0b10;
    color: #fff6ea;
    font-family: "Trebuchet MS", "Segoe UI", sans-serif;
    -webkit-text-size-adjust: 100%;
    user-select: none;
    -webkit-user-select: none;
  }
  #app {
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 14px 12px;
    background: repeating-linear-gradient(90deg, #d62839 0 22px, #fff6ee 22px 44px);
    border-bottom: 5px solid #3a1510;
    flex: none;
  }
  .brand, .stats {
    display: flex;
    align-items: center;
    gap: 8px;
    background: linear-gradient(180deg, #3d1a20, #241014);
    border: 2px solid #ffc857;
    border-radius: 14px;
    padding: 6px 10px;
    box-shadow: 0 3px 0 #8a3a22;
    max-width: 100%;
  }
  .brand { min-width: 0; }
  h1 {
    margin: 0;
    font-family: Impact, "Arial Black", "Trebuchet MS", sans-serif;
    font-weight: 400;
    letter-spacing: 1.5px;
    font-size: clamp(16px, 2.5vw, 30px);
    color: #ffe08a;
    text-shadow: 2px 2px 0 #c4234a;
    line-height: 1;
    white-space: nowrap;
  }
  .tagline {
    margin: 2px 0 0;
    font-size: 11px;
    letter-spacing: 0.6px;
    color: #ffd0dc;
    text-transform: uppercase;
  }
  .stats { gap: 12px; flex: none; }
  .stat { text-align: right; }
  .stat span {
    display: block;
    font-size: 9px;
    letter-spacing: 1.4px;
    color: #ffb4c8;
  }
  .stat b {
    display: block;
    font-family: Impact, "Arial Black", sans-serif;
    font-weight: 400;
    font-size: clamp(16px, 2vw, 24px);
    color: #fff;
    letter-spacing: 0.5px;
    line-height: 1;
  }
  .bestline {
    display: block;
    margin-top: 2px;
    font-size: 9px;
    color: #ffd56a;
    letter-spacing: 0.8px;
  }
  #lives { display: flex; gap: 2px; justify-content: flex-end; margin-top: 2px; }
  #lives svg { width: 24px; height: 18px; display: block; }
  #stage {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    background: #2a1016;
  }
  canvas { display: block; width: 100%; height: 100%; touch-action: none; }
  #overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(12, 4, 8, 0.18);
    z-index: 2;
    cursor: pointer;
    transition: opacity 0.35s ease;
  }
  #overlay.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
  .card {
    width: min(460px, calc(100% - 8px));
    background: rgba(32, 10, 16, 0.92);
    border: 3px solid #ffc857;
    border-radius: 22px;
    padding: 16px 18px 18px;
    text-align: center;
    box-shadow: 0 14px 40px rgba(0,0,0,0.45), 0 0 0 5px rgba(255, 77, 109, 0.22);
    animation: bob 2.4s ease-in-out infinite;
  }
  @keyframes bob {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-6px); }
  }
  .hero-burger { display: flex; justify-content: center; }
  .logo { width: 48px; height: 36px; flex: none; filter: drop-shadow(0 2px 0 rgba(0,0,0,.35)); }
  .hero-burger svg { width: 86px; height: 64px; filter: drop-shadow(0 6px 0 rgba(0,0,0,.28)); }
  .kicker {
    color: #ff8fa3;
    font-size: 12px;
    font-weight: 800;
    letter-spacing: 1.8px;
    margin-top: 4px;
  }
  #overlayTitle {
    margin: 8px 0 6px;
    font-size: clamp(18px, 3.8vw, 28px);
    font-weight: 800;
    line-height: 1.2;
    color: #fffaf3;
    text-shadow: 0 2px 0 #8a1530;
  }
  #overlaySub {
    margin: 0;
    color: #ffe1c2;
    font-size: clamp(12px, 2.4vw, 15px);
    line-height: 1.35;
  }
  @media (max-width: 520px) {
    header { padding: 8px; gap: 6px; }
    .brand, .stats { padding: 5px 8px; border-radius: 12px; gap: 6px; }
    .tagline { display: none; }
    .stat span { font-size: 8px; }
    #lives svg { width: 20px; height: 15px; }
    #overlay { padding: 10px; }
    .card { padding: 12px; border-radius: 16px; }
    .hero-burger svg { width: 68px; height: 51px; }
  }
  @media (max-height: 560px) {
    .hero-burger svg { width: 56px; height: 42px; }
    #overlayTitle { margin: 4px 0; }
    .card { padding: 10px 12px; }
  }
</style>
</head>
<body>
<div id="app">
  <header>
    <div class="brand">
      <svg class="logo" viewBox="0 0 64 48" aria-hidden="true">
        <ellipse cx="32" cy="43" rx="22" ry="4.5" fill="#e8b86a"/>
        <rect x="10" y="32" width="44" height="8" rx="3" fill="#e8b86a"/>
        <rect x="9" y="27" width="46" height="7" rx="2.5" fill="#6b3a1f"/>
        <rect x="8" y="24" width="48" height="5" rx="1.5" fill="#ffc53d"/>
        <ellipse cx="32" cy="25" rx="26" ry="4" fill="#7ed04a"/>
        <rect x="11" y="19" width="42" height="6" rx="2" fill="#ef3d3d"/>
        <path d="M10 22C12 7 22 4 32 4C42 4 52 7 54 22Z" fill="#f3c56a"/>
        <ellipse cx="24" cy="13" rx="2" ry="1.2" fill="#fff6dd"/>
        <ellipse cx="33" cy="10.5" rx="2" ry="1.2" fill="#fff6dd"/>
        <ellipse cx="42" cy="13.5" rx="2" ry="1.2" fill="#fff6dd"/>
      </svg>
      <div>
        <h1>HEARTBREAK</h1>
        <p class="tagline">smash bricks · rain hearts</p>
      </div>
    </div>
    <div class="stats">
      <div class="stat">
        <span>SCORE</span>
        <b id="score">0</b>
        <small class="bestline" id="bestline">BEST 0</small>
      </div>
      <div class="stat">
        <span>LIVES</span>
        <div id="lives"></div>
      </div>
    </div>
  </header>
  <div id="stage" role="application" aria-label="Heartbreak burger breakout. Click or press a key to start.">
    <canvas id="c"></canvas>
    <div id="overlay">
      <div class="card">
        <div class="hero-burger" aria-hidden="true">
          <svg viewBox="0 0 64 48">
            <ellipse cx="32" cy="43" rx="22" ry="4.5" fill="#e8b86a"/>
            <rect x="10" y="32" width="44" height="8" rx="3" fill="#e8b86a"/>
            <rect x="9" y="27" width="46" height="7" rx="2.5" fill="#6b3a1f"/>
            <rect x="8" y="24" width="48" height="5" rx="1.5" fill="#ffc53d"/>
            <ellipse cx="32" cy="25" rx="26" ry="4" fill="#7ed04a"/>
            <rect x="11" y="19" width="42" height="6" rx="2" fill="#ef3d3d"/>
            <path d="M10 22C12 7 22 4 32 4C42 4 52 7 54 22Z" fill="#f3c56a"/>
            <ellipse cx="24" cy="13" rx="2" ry="1.2" fill="#fff6dd"/>
            <ellipse cx="33" cy="10.5" rx="2" ry="1.2" fill="#fff6dd"/>
            <ellipse cx="42" cy="13.5" rx="2" ry="1.2" fill="#fff6dd"/>
          </svg>
        </div>
        <div class="kicker" id="kicker">♥ AUTO-PLAY DEMO ♥</div>
        <div id="overlayTitle">Click or press a key to start</div>
        <div id="overlaySub">The burger plays itself. Then steer with mouse, touch, or arrow keys.</div>
      </div>
    </div>
  </div>
</div>
<script>
(() => {
  const canvas = document.getElementById('c');
  const ctx = canvas.getContext('2d', { alpha: false });
  const stage = document.getElementById('stage');
  const overlay = document.getElementById('overlay');
  const overlayTitle = document.getElementById('overlayTitle');
  const overlaySub = document.getElementById('overlaySub');
  const kickerEl = document.getElementById('kicker');
  const scoreEl = document.getElementById('score');
  const bestEl = document.getElementById('bestline');
  const livesEl = document.getElementById('lives');

  const BURGER_SVG = '<svg viewBox="0 0 64 48" width="24" height="18" aria-hidden="true"><ellipse cx="32" cy="43" rx="22" ry="4.5" fill="#e8b86a"/><rect x="10" y="32" width="44" height="8" rx="3" fill="#e8b86a"/><rect x="9" y="27" width="46" height="7" rx="2.5" fill="#6b3a1f"/><rect x="8" y="24" width="48" height="5" rx="1.5" fill="#ffc53d"/><ellipse cx="32" cy="25" rx="26" ry="4" fill="#7ed04a"/><rect x="11" y="19" width="42" height="6" rx="2" fill="#ef3d3d"/><path d="M10 22C12 7 22 4 32 4C42 4 52 7 54 22Z" fill="#f3c56a"/><ellipse cx="24" cy="13" rx="2" ry="1.2" fill="#fff6dd"/><ellipse cx="33" cy="10.5" rx="2" ry="1.2" fill="#fff6dd"/><ellipse cx="42" cy="13.5" rx="2" ry="1.2" fill="#fff6dd"/></svg>';

  const ROW_COLORS = [
    { fill: '#ff4d6d', hi: '#ffc2d1', shade: '#c9184a' },
    { fill: '#ffb703', hi: '#ffe08a', shade: '#d97706' },
    { fill: '#70e000', hi: '#d8f3a2', shade: '#2d6a4f' },
    { fill: '#ff6b35', hi: '#ffc2a8', shade: '#c2410c' },
    { fill: '#c77dff', hi: '#e8d4ff', shade: '#7b2cbf' },
    { fill: '#4cc9f0', hi: '#caf0f8', shade: '#0077b6' }
  ];
  const HEART_COLORS = ['#ff4d6d', '#ff7a96', '#ff2d55', '#ffffff', '#ffd6e0', '#ffb703', '#ff5c8a', '#ffe66d', '#ff0a54', '#ffb3c6'];
  const MAX_P = 360;

  const ball = { x: 200, y: 200, vx: 0, vy: 0, r: 8, spin: 0 };
  const paddle = { x: 0, y: 0, w: 150, h: 64, vx: 0, squash: 1 };
  let bricks = [];
  let particles = [];
  let rings = [];
  let popups = [];
  let steam = [];
  let ambient = [];
  let trail = [];
  const keys = {};
  const banner = { text: '', t: 0 };

  let W = 0, H = 0, dpr = 1;
  let speedScale = 1;
  let baseSpeedMul = 1;
  let mode = 'demo';
  let score = 0, best = 0, lives = 3, level = 1;
  let combo = 0, comboTimer = 0;
  let clearing = false, clearTimer = 0;
  let serveWait = 0;
  let shake = 0, pain = 0;
  let gridCols = 0, gridRows = 0;
  let paddleRel = 0.5;
  let placed = false;
  let pointerCtl = false;
  let pointerX = null;
  let launchDir = 1;
  let trailAcc = 0;
  let ready = false;
  let last = 0;

  function ballSpeed() {
    return 500 * speedScale * baseSpeedMul;
  }

  function updateHUD() {
    if (score > best) best = score;
    scoreEl.textContent = String(score);
    bestEl.textContent = 'BEST ' + best;
    livesEl.innerHTML = BURGER_SVG.repeat(Math.max(0, lives));
  }

  function showOverlay(title, sub, kicker) {
    overlayTitle.textContent = title;
    overlaySub.textContent = sub;
    kickerEl.textContent = kicker;
    overlay.classList.remove('hidden');
  }

  function showBanner(text) {
    banner.text = text;
    banner.t = 1.25;
  }

  function resize() {
    const rect = stage.getBoundingClientRect();
    dpr = Math.min(window.devicePixelRatio || 1, 2);
    const bw = Math.max(1, Math.floor(rect.width * dpr));
    const bh = Math.max(1, Math.floor(rect.height * dpr));
    if (canvas.width !== bw || canvas.height !== bh) {
      canvas.width = bw;
      canvas.height = bh;
    }
    W = rect.width;
    H = rect.height;
    if (W < 40 || H < 40) return;
    const old = speedScale || 1;
    speedScale = Math.max(0.55, Math.min(1.12, W / 1100));
    const k = speedScale / old;
    if (ball.vx || ball.vy) {
      ball.vx *= k;
      ball.vy *= k;
    }
    layoutBricks(false);
    fitPaddle();
    ball.r = Math.max(6.5, Math.min(10, W * 0.0095));
  }

  function layoutBricks(force) {
    if (W < 40 || H < 40) return;
    const cols = W < 420 ? 5 : W < 700 ? 7 : W < 1020 ? 9 : 11;
    const rows = H < 480 ? 4 : H < 650 ? 5 : 6;
    const gap = W < 480 ? 4 : 5;
    const margin = 12;
    const top = 34;
    const maxArea = Math.min(H * 0.36, rows * 30);
    const bh = Math.max(14, (maxArea - gap * (rows - 1)) / rows);
    const bw = (W - margin * 2 - gap * (cols - 1)) / cols;
    if (bw < 8) return;
    if (force || cols !== gridCols || rows !== gridRows) {
      gridCols = cols;
      gridRows = rows;
      bricks = [];
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          bricks.push({
            r: r,
            c: c,
            state: 'live',
            breakT: 0,
            color: ROW_COLORS[r % ROW_COLORS.length]
          });
        }
      }
    }
    for (let i = 0; i < bricks.length; i++) {
      const b = bricks[i];
      b.w = bw;
      b.h = bh;
      b.x = margin + b.c * (bw + gap);
      b.y = top + b.r * (bh + gap);
    }
  }

  function fitPaddle() {
    paddle.w = Math.max(128, Math.min(210, W * 0.2));
    paddle.h = Math.max(60, Math.min(78, paddle.w * 0.38));
    paddle.y = H - paddle.h - 14;
    paddle.x = placed ? paddleRel * W - paddle.w / 2 : (W - paddle.w) / 2;
    clampPaddle();
  }

  function clampPaddle() {
    const min = 8;
    const max = Math.max(min, W - paddle.w - 8);
    paddle.x = Math.max(min, Math.min(max, paddle.x));
    if (W > 0) paddleRel = (paddle.x + paddle.w / 2) / W;
  }

  function brickBottom() {
    let m = 48;
    for (let i = 0; i < bricks.length; i++) {
      const b = bricks[i];
      if (b.state !== 'gone') m = Math.max(m, b.y + b.h);
    }
    return m;
  }

  function launchBall(intoBricks) {
    const sp = ballSpeed();
    ball.r = Math.max(6.5, Math.min(10, W * 0.0095));
    ball.spin = 0;
    trail.length = 0;
    if (intoBricks) {
      launchDir *= -1;
      ball.x = W * (0.38 + Math.random() * 0.24);
      ball.y = brickBottom() + ball.r + 20;
      if (ball.y > paddle.y - 50) ball.y = Math.max(brickBottom() + ball.r + 8, paddle.y - 70);
      const ang = -1.02;
      ball.vx = Math.abs(Math.cos(ang) * sp) * launchDir;
      ball.vy = Math.sin(ang) * sp;
    } else {
      ball.x = paddle.x + paddle.w / 2;
      ball.y = paddle.y - ball.r - 4;
      const ang = -Math.PI / 2 + (Math.random() * 0.7 - 0.35);
      ball.vx = Math.cos(ang) * sp;
      ball.vy = Math.sin(ang) * sp;
    }
  }

  function seedAmbient() {
    ambient.length = 0;
    for (let i = 0; i < 16; i++) {
      ambient.push({
        x: Math.random() * W,
        y: Math.random() * H * 0.8,
        s: 5 + Math.random() * 8,
        vy: -12 - Math.random() * 18,
        vx: (Math.random() - 0.5) * 16,
        rot: Math.random() * Math.PI,
        vr: (Math.random() - 0.5) * 0.7,
        color: HEART_COLORS[i % HEART_COLORS.length],
        a: 0.16 + Math.random() * 0.22
      });
    }
  }

  function spawnHearts(x, y, tint, count) {
    const n = count || 44;
    for (let i = 0; i < n; i++) {
      const ang = Math.random() * Math.PI * 2;
      const sp = 90 + Math.random() * 380;
      const roll = Math.random();
      const s = roll < 0.62 ? 4.5 + Math.random() * 3.5 : 8 + Math.random() * 6;
      let color = HEART_COLORS[(Math.random() * HEART_COLORS.length) | 0];
      if (Math.random() < 0.18) color = '#ffffff';
      else if (Math.random() < 0.12 && tint) color = tint;
      particles.push({
        x: x + (Math.random() - 0.5) * 22,
        y: y + (Math.random() - 0.5) * 12,
        vx: Math.cos(ang) * sp + ball.vx * 0.08,
        vy: Math.sin(ang) * sp - 70,
        g: 420 + Math.random() * 220,
        s: s,
        rot: Math.random() * Math.PI * 2,
        vr: (Math.random() - 0.5) * 7,
        life: 1,
        decay: 0.36 + Math.random() * 0.28,
        color: color
      });
    }
    if (particles.length > MAX_P) particles.splice(0, particles.length - MAX_P);
  }

  function spawnRing(x, y, color) {
    rings.push({ x: x, y: y, r: 6, vr: 210, a: 0.75, color: '#fff' });
    rings.push({ x: x, y: y, r: 4, vr: 140, a: 0.55, color: color || '#ffd6e0' });
    if (rings.length > 40) rings.splice(0, rings.length - 40);
  }

  function destroyBrick(b) {
    if (!b || b.state !== 'live') return;
    b.state = 'break';
    b.breakT = 0;
    combo += 1;
    comboTimer = 1.7;
    const gained = 100 * combo;
    score += gained;
    updateHUD();
    spawnHearts(b.x + b.w / 2, b.y + b.h / 2, b.color.fill, 44);
    spawnRing(b.x + b.w / 2, b.y + b.h / 2, b.color.fill);
    popups.push({ x: b.x + b.w / 2, y: b.y + b.h / 2, text: '+' + gained, life: 1 });
    if (popups.length > 20) popups.shift();
    shake = Math.min(5, shake + 2.2);
    let any = false;
    for (let i = 0; i < bricks.length; i++) {
      if (bricks[i].state === 'live') { any = true; break; }
    }
    if (!any && !clearing) {
      clearing = true;
      clearTimer = 0.85;
    }
  }

  function collideWalls() {
    const m = 8;
    if (ball.x < ball.r + m) {
      ball.x = ball.r + m;
      ball.vx = Math.abs(ball.vx);
    } else if (ball.x > W - ball.r - m) {
      ball.x = W - ball.r - m;
      ball.vx = -Math.abs(ball.vx);
    }
    if (ball.y < ball.r + m) {
      ball.y = ball.r + m;
      ball.vy = Math.abs(ball.vy);
    }
  }

  function collidePaddle() {
    if (ball.vy <= 0) return;
    const top = paddle.y;
    const bot = paddle.y + Math.min(20, paddle.h * 0.36);
    if (ball.y + ball.r < top || ball.y - ball.r > bot) return;
    if (ball.x < paddle.x - ball.r * 0.2 || ball.x > paddle.x + paddle.w + ball.r * 0.2) return;
    const hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
    const c = Math.max(-1, Math.min(1, hit));
    const sp = Math.max(ballSpeed() * 0.92, Math.min(ballSpeed() * 1.22, Math.hypot(ball.vx, ball.vy) * 1.02));
    ball.vx = c * sp * 0.86;
    const remain = sp * sp - ball.vx * ball.vx;
    ball.vy = -Math.sqrt(remain > 1 ? remain : sp * sp * 0.28);
    ball.y = top - ball.r - 0.4;
    paddle.squash = 0.8;
  }

  function collideBricks() {
    for (let i = 0; i < bricks.length; i++) {
      const b = bricks[i];
      if (b.state !== 'live') continue;
      if (ball.x + ball.r < b.x || ball.x - ball.r > b.x + b.w ||
          ball.y + ball.r < b.y || ball.y - ball.r > b.y + b.h) continue;
      const overlapLeft = (ball.x + ball.r) - b.x;
      const overlapRight = (b.x + b.w) - (ball.x - ball.r);
      const overlapTop = (ball.y + ball.r) - b.y;
      const overlapBottom = (b.y + b.h) - (ball.y - ball.r);
      const minX = Math.min(overlapLeft, overlapRight);
      const minY = Math.min(overlapTop, overlapBottom);
      destroyBrick(b);
      if (minX < minY) {
        if (overlapLeft < overlapRight) {
          ball.vx = -Math.abs(ball.vx);
          ball.x -= minX;
        } else {
          ball.vx = Math.abs(ball.vx);
          ball.x += minX;
        }
      } else if (overlapTop < overlapBottom) {
        ball.vy = -Math.abs(ball.vy);
        ball.y -= minY;
      } else {
        ball.vy = Math.abs(ball.vy);
        ball.y += minY;
      }
      ball.vx += (Math.random() - 0.5) * 36;
      return;
    }
  }

  function predictX() {
    if (ball.vy < 50) return ball.x;
    let x = ball.x;
    let y = ball.y;
    let vx = ball.vx;
    let vy = ball.vy;
    const left = ball.r + 8;
    const right = W - ball.r - 8;
    const goal = paddle.y - ball.r;
    for (let i = 0; i < 10; i++) {
      if (vy < 20) return x;
      const t = (goal - y) / vy;
      if (t < 0) return ball.x;
      const xAt = x + vx * t;
      if (xAt >= left && xAt <= right) return xAt;
      if (Math.abs(vx) < 8) return x;
      const wall = vx > 0 ? right : left;
      const tw = (wall - x) / vx;
      if (!(tw > 0) || tw > t) return Math.max(left, Math.min(right, xAt));
      y += vy * tw;
      x = wall;
      vx = -vx;
    }
    return x;
  }

  function movePaddle(dt) {
    const prev = paddle.x;
    if (mode === 'demo') {
      const zone = brickBottom() + 24;
      const targetX = (ball.y < zone || ball.vy < 0) ? ball.x : predictX();
      const dest = targetX - paddle.w / 2;
      const maxStep = 980 * speedScale * dt;
      const diff = dest - paddle.x;
      paddle.x += Math.max(-maxStep, Math.min(maxStep, diff));
    } else {
      const speed = 820 * speedScale;
      if (keys.ArrowLeft || keys.KeyA) {
        pointerCtl = false;
        paddle.x -= speed * dt;
      }
      if (keys.ArrowRight || keys.KeyD) {
        pointerCtl = false;
        paddle.x += speed * dt;
      }
      if (pointerCtl && pointerX != null) {
        const target = pointerX - paddle.w / 2;
        paddle.x += (target - paddle.x) * Math.min(1, dt * 18);
      }
    }
    clampPaddle();
    paddle.vx = (paddle.x - prev) / Math.max(dt, 0.001);
  }

  function stabilizeBall() {
    let sp = Math.hypot(ball.vx, ball.vy);
    const min = ballSpeed() * 0.9;
    const max = ballSpeed() * 1.28;
    if (!Number.isFinite(sp) || sp < 1) {
      launchBall(mode === 'demo');
      return;
    }
    if (sp < min) {
      ball.vx *= min / sp;
      ball.vy *= min / sp;
      sp = min;
    } else if (sp > max) {
      ball.vx *= max / sp;
      ball.vy *= max / sp;
      sp = max;
    }
    const maxVx = sp * 0.88;
    if (Math.abs(ball.vx) > maxVx) {
      const signY = ball.vy < 0 ? -1 : 1;
      ball.vx = Math.sign(ball.vx || 1) * maxVx;
      ball.vy = signY * Math.sqrt(Math.max(1, sp * sp - ball.vx * ball.vx));
    }
  }

  function loseLife() {
    lives -= 1;
    pain = 0.42;
    updateHUD();
    if (lives <= 0) {
      mode = 'over';
      showOverlay('Out of buns!', 'Click or press a key to play again', '♥ GAME OVER ♥');
      return;
    }
    serveWait = 0.85;
  }

  function nextLevel() {
    clearing = false;
    clearTimer = 0;
    level += 1;
    baseSpeedMul = Math.min(1.32, 1 + (level - 1) * 0.06);
    score += 500;
    layoutBricks(true);
    spawnHearts(W * 0.5, Math.min(H * 0.42, brickBottom()), '#ffd6e0', 72);
    showBanner('ORDER UP!');
    launchBall(mode === 'demo');
    updateHUD();
  }

  function updateParticles(dt) {
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.vy += p.g * dt;
      p.x += p.vx * dt;
      p.y += p.vy * dt;
      p.rot += p.vr * dt;
      p.life -= p.decay * dt;
      if (p.life <= 0 || p.y > H + 50 || p.x < -60 || p.x > W + 60 || !Number.isFinite(p.x)) {
        particles.splice(i, 1);
      }
    }
    if (particles.length > MAX_P) particles.splice(0, particles.length - MAX_P);
  }

  function update(dt) {
    if (dt > 0.05) dt = 0.05;
    if (!(dt > 0)) return;

    updateParticles(dt);
    for (let i = rings.length - 1; i >= 0; i--) {
      rings[i].r += rings[i].vr * dt;
      rings[i].a -= dt * 1.45;
      if (rings[i].a <= 0) rings.splice(i, 1);
    }
    for (let i = popups.length - 1; i >= 0; i--) {
      popups[i].y -= 38 * dt;
      popups[i].life -= dt * 0.85;
      if (popups[i].life <= 0) popups.splice(i, 1);
    }
    for (let i = 0; i < ambient.length; i++) {
      const a = ambient[i];
      a.y += a.vy * dt;
      a.x += a.vx * dt;
      a.rot += a.vr * dt;
      if (a.y < -30) { a.y = H + 16; a.x = Math.random() * W; }
      if (a.x < -30) a.x = W + 8;
      if (a.x > W + 30) a.x = -8;
    }
    if (steam.length < 16 && Math.random() < dt * 10) {
      steam.push({
        x: paddle.x + paddle.w * (0.28 + Math.random() * 0.44),
        y: paddle.y + 6,
        vy: -26 - Math.random() * 20,
        vx: (Math.random() - 0.5) * 14,
        a: 0.28 + Math.random() * 0.2,
        r: 3 + Math.random() * 4,
        life: 1
      });
    }
    for (let i = steam.length - 1; i >= 0; i--) {
      const s = steam[i];
      s.y += s.vy * dt;
      s.x += s.vx * dt;
      s.r += dt * 7;
      s.life -= dt * 0.65;
      if (s.life <= 0) steam.splice(i, 1);
    }
    for (let i = 0; i < bricks.length; i++) {
      const b = bricks[i];
      if (b.state === 'break') {
        b.breakT += dt / 0.42;
        if (b.breakT >= 1) b.state = 'gone';
      }
    }

    paddle.squash += (1 - paddle.squash) * Math.min(1, dt * 10);
    shake = Math.max(0, shake - dt * 16);
    pain = Math.max(0, pain - dt * 0.8);
    banner.t = Math.max(0, banner.t - dt);
    if (comboTimer > 0) {
      comboTimer -= dt;
      if (comboTimer <= 0) combo = 0;
    }
    if (mode === 'over') return;

    if (serveWait > 0) {
      serveWait -= dt;
      movePaddle(dt);
      ball.x = paddle.x + paddle.w / 2;
      ball.y = paddle.y - ball.r - 3;
      ball.vx = 0;
      ball.vy = 0;
      if (serveWait <= 0) launchBall(false);
      return;
    }

    movePaddle(dt);
    const sub = 4;
    const h = dt / sub;
    for (let s = 0; s < sub; s++) {
      ball.x += ball.vx * h;
      ball.y += ball.vy * h;
      ball.spin += h * 4;
      collideWalls();
      collidePaddle();
      collideBricks();
    }
    if (!Number.isFinite(ball.x) || !Number.isFinite(ball.y)) {
      launchBall(mode === 'demo');
      return;
    }
    stabilizeBall();
    trail.push({ x: ball.x, y: ball.y });
    if (trail.length > 12) trail.shift();
    trailAcc += dt;
    if (trailAcc > 0.045) {
      trailAcc = 0;
      particles.push({
        x: ball.x,
        y: ball.y,
        vx: (Math.random() - 0.5) * 28,
        vy: (Math.random() - 0.5) * 28,
        g: 30,
        s: 3.6 + Math.random() * 2.2,
        rot: Math.random() * 6,
        vr: (Math.random() - 0.5) * 4,
        life: 0.55,
        decay: 1.5,
        color: '#ffb3c7'
      });
    }
    if (ball.y - ball.r > H) {
      if (mode === 'demo') launchBall(true);
      else loseLife();
    }
    if (mode !== 'over' && clearing) {
      clearTimer -= dt;
      if (clearTimer <= 0) nextLevel();
    }
  }

  function roundRect(x, y, w, h, r) {
    if (w < 1 || h < 1) return;
    const rr = Math.max(0, Math.min(r || 0, w / 2, h / 2));
    ctx.beginPath();
    ctx.moveTo(x + rr, y);
    ctx.arcTo(x + w, y, x + w, y + h, rr);
    ctx.arcTo(x + w, y + h, x, y + h, rr);
    ctx.arcTo(x, y + h, x, y, rr);
    ctx.arcTo(x, y, x + w, y, rr);
    ctx.closePath();
  }

  function drawHeart(x, y, s, color, rot, alpha) {
    if (!(s > 1) || alpha < 0.03) return;
    ctx.save();
    ctx.translate(x, y);
    if (rot) ctx.rotate(rot);
    ctx.globalAlpha = ctx.globalAlpha * Math.max(0, Math.min(1, alpha));
    ctx.fillStyle = color;
    const k = s / 16;
    ctx.beginPath();
    ctx.moveTo(0, 12 * k);
    ctx.bezierCurveTo(0, 12 * k, -12 * k, 4 * k, -12 * k, -4 * k);
    ctx.bezierCurveTo(-12 * k, -8 * k, -9 * k, -11 * k, -5 * k, -11 * k);
    ctx.bezierCurveTo(-2.5 * k, -11 * k, -1 * k, -9.5 * k, 0, -8 * k);
    ctx.bezierCurveTo(1 * k, -9.5 * k, 2.5 * k, -11 * k, 5 * k, -11 * k);
    ctx.bezierCurveTo(9 * k, -11 * k, 12 * k, -8 * k, 12 * k, -4 * k);
    ctx.bezierCurveTo(12 * k, 4 * k, 0, 12 * k, 0, 12 * k);
    ctx.closePath();
    ctx.fill();
    if (s > 5.5) {
      ctx.fillStyle = 'rgba(255,255,255,0.42)';
      const hx = Math.max(0.6, 2.2 * k);
      const hy = Math.max(0.5, 1.3 * k);
      ctx.beginPath();
      ctx.ellipse(-4.2 * k, -6.5 * k, hx, hy, -0.6, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.restore();
  }

  function drawBackground() {
    const bg = ctx.createRadialGradient(W * 0.5, H * 0.18, 30, W * 0.5, H * 0.45, Math.max(W, H) * 0.75);
    bg.addColorStop(0, '#5a2430');
    bg.addColorStop(0.45, '#3a1520');
    bg.addColorStop(1, '#14080c');
    ctx.fillStyle = bg;
    ctx.fillRect(-30, -30, W + 60, H + 60);

    const spots = [
      [0.12, 0.2, 0.18, 'rgba(255,77,109,0.16)'],
      [0.84, 0.14, 0.16, 'rgba(255,200,87,0.10)'],
      [0.68, 0.48, 0.14, 'rgba(255,140,170,0.09)'],
      [0.22, 0.55, 0.12, 'rgba(255,255,255,0.04)']
    ];
    for (let i = 0; i < spots.length; i++) {
      const s = spots[i];
      ctx.fillStyle = s[3];
      ctx.beginPath();
      ctx.arc(s[0] * W, s[1] * H, s[2] * Math.min(W, H), 0, Math.PI * 2);
      ctx.fill();
    }

    const glow = ctx.createRadialGradient(W * 0.5, 70, 10, W * 0.5, 80, Math.max(W, H) * 0.42);
    glow.addColorStop(0, 'rgba(255,90,120,0.14)');
    glow.addColorStop(1, 'rgba(255,90,120,0)');
    ctx.fillStyle = glow;
    ctx.fillRect(0, 0, W, H * 0.55);

    const y = Math.max(paddle.y + paddle.h * 0.58, H - 86);
    const wood = ctx.createLinearGradient(0, y, 0, H);
    wood.addColorStop(0, '#b06b42');
    wood.addColorStop(0.2, '#7a4328');
    wood.addColorStop(1, '#3c2014');
    ctx.fillStyle = wood;
    ctx.fillRect(-20, y, W + 40, H - y + 30);
    ctx.strokeStyle = 'rgba(255,220,180,0.14)';
    ctx.lineWidth = 2;
    for (let i = 0; i < 5; i++) {
      const yy = y + 12 + i * 12;
      ctx.beginPath();
      ctx.moveTo(0, yy);
      ctx.bezierCurveTo(W * 0.3, yy - 4, W * 0.62, yy + 6, W, yy - 1);
      ctx.stroke();
    }
    ctx.fillStyle = 'rgba(255,206,130,0.4)';
    ctx.fillRect(0, y, W, 3);

    ctx.strokeStyle = 'rgba(255,200,120,0.32)';
    ctx.lineWidth = 4;
    ctx.strokeRect(6, 6, W - 12, H - 12);
  }

  function drawBrick(b) {
    if (b.state === 'gone') return;
    const alpha = b.state === 'break' ? Math.max(0, 1 - b.breakT) : 1;
    const sc = b.state === 'break' ? 1 + b.breakT * 0.4 : 1;
    ctx.save();
    ctx.translate(b.x + b.w / 2, b.y + b.h / 2);
    ctx.scale(sc, sc);
    ctx.globalAlpha = alpha;
    const x = -b.w / 2;
    const y = -b.h / 2;
    ctx.fillStyle = b.color.shade;
    roundRect(x, y, b.w, b.h, 5);
    ctx.fill();
    ctx.fillStyle = b.color.fill;
    roundRect(x, y, b.w, Math.max(2, b.h - 3), 5);
    ctx.fill();
    ctx.fillStyle = 'rgba(255,255,255,0.28)';
    roundRect(x + 3, y + 2, Math.max(4, b.w * 0.32), Math.max(2, b.h * 0.28), 3);
    ctx.fill();
    drawHeart(0, 1, Math.min(b.w, b.h) * 0.34, 'rgba(255,255,255,0.82)', 0, 0.95);
    ctx.restore();
  }

  function drawPaddle() {
    const x = paddle.x;
    const y = paddle.y;
    const w = paddle.w;
    const h = paddle.h;
    ctx.save();
    const cx = x + w / 2;
    const cy = y + h * 0.55;
    const tilt = Math.max(-0.14, Math.min(0.14, (paddle.vx || 0) / 2400));
    ctx.translate(cx, cy);
    ctx.rotate(tilt);
    const sq = paddle.squash || 1;
    ctx.scale(1 + (1 - sq) * 0.1, sq);
    ctx.translate(-cx, -cy);

    ctx.fillStyle = 'rgba(0,0,0,0.28)';
    ctx.beginPath();
    ctx.ellipse(cx, y + h * 0.94, w * 0.46, Math.max(4, h * 0.08), 0, 0, Math.PI * 2);
    ctx.fill();

    const plate = ctx.createLinearGradient(x, y + h * 0.78, x, y + h);
    plate.addColorStop(0, '#f7f8fa');
    plate.addColorStop(1, '#b7c0c9');
    ctx.fillStyle = plate;
    ctx.beginPath();
    ctx.ellipse(cx, y + h * 0.88, w * 0.46, Math.max(4, h * 0.09), 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = 'rgba(255,255,255,0.85)';
    ctx.lineWidth = 1.5;
    ctx.stroke();
    ctx.strokeStyle = 'rgba(90,100,110,0.45)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.ellipse(cx, y + h * 0.88, w * 0.3, Math.max(2.5, h * 0.045), 0, 0, Math.PI * 2);
    ctx.stroke();

    const bx = x + w * 0.1;
    const bw = w * 0.8;
    const by = y + 2;
    const bh = h * 0.78;

    const bunG = ctx.createLinearGradient(bx, by + bh * 0.55, bx, by + bh);
    bunG.addColorStop(0, '#ffe3ad');
    bunG.addColorStop(0.55, '#f0b44c');
    bunG.addColorStop(1, '#c98432');
    ctx.fillStyle = bunG;
    roundRect(bx + bw * 0.02, by + bh * 0.62, bw * 0.96, bh * 0.36, Math.min(18, bh * 0.18));
    ctx.fill();
    ctx.strokeStyle = 'rgba(110,55,12,0.35)';
    ctx.lineWidth = 1;
    ctx.stroke();
    ctx.fillStyle = 'rgba(255,255,255,0.22)';
    roundRect(bx + bw * 0.12, by + bh * 0.7, bw * 0.28, Math.max(2, bh * 0.06), 3);
    ctx.fill();

    ctx.fillStyle = '#6b3a1f';
    roundRect(bx + bw * 0.04, by + bh * 0.5, bw * 0.92, bh * 0.2, 5);
    ctx.fill();
    ctx.fillStyle = 'rgba(40,16,6,0.4)';
    for (let i = 0; i < 4; i++) {
      roundRect(bx + bw * 0.16 + i * bw * 0.18, by + bh * 0.54, Math.max(2, bw * 0.06), Math.max(2, bh * 0.11), 2);
      ctx.fill();
    }

    ctx.fillStyle = '#ffc53d';
    ctx.beginPath();
    ctx.moveTo(bx + bw * 0.02, by + bh * 0.52);
    ctx.lineTo(bx + bw * 0.98, by + bh * 0.52);
    ctx.lineTo(bx + bw * 0.9, by + bh * 0.64);
    ctx.lineTo(bx + bw * 0.76, by + bh * 0.55);
    ctx.lineTo(bx + bw * 0.64, by + bh * 0.72);
    ctx.lineTo(bx + bw * 0.5, by + bh * 0.56);
    ctx.lineTo(bx + bw * 0.36, by + bh * 0.7);
    ctx.lineTo(bx + bw * 0.22, by + bh * 0.55);
    ctx.lineTo(bx + bw * 0.08, by + bh * 0.66);
    ctx.closePath();
    ctx.fill();

    ctx.fillStyle = '#7ed04a';
    ctx.beginPath();
    ctx.ellipse(bx + bw / 2, by + bh * 0.48, bw * 0.58, Math.max(3, bh * 0.1), 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#3c8c22';
    ctx.lineWidth = Math.max(1, bh * 0.035);
    ctx.beginPath();
    for (let i = 0; i <= 9; i++) {
      const lx = bx - bw * 0.05 + (bw * 1.1 * i) / 9;
      const ly = by + bh * 0.46 + (i % 2 ? bh * 0.07 : -bh * 0.045);
      if (i === 0) ctx.moveTo(lx, ly);
      else ctx.lineTo(lx, ly);
    }
    ctx.stroke();

    ctx.fillStyle = '#ef3b3b';
    roundRect(bx + bw * 0.06, by + bh * 0.36, bw * 0.88, Math.max(4, bh * 0.13), 3);
    ctx.fill();
    ctx.fillStyle = 'rgba(255,255,255,0.35)';
    ctx.fillRect(bx + bw * 0.2, by + bh * 0.4, bw * 0.12, Math.max(1, bh * 0.025));
    ctx.fillRect(bx + bw * 0.46, by + bh * 0.41, bw * 0.1, Math.max(1, bh * 0.02));

    const topG = ctx.createLinearGradient(bx, by, bx, by + bh * 0.45);
    topG.addColorStop(0, '#ffe7b8');
    topG.addColorStop(0.55, '#f2be58');
    topG.addColorStop(1, '#e09a38');
    ctx.fillStyle = topG;
    ctx.beginPath();
    ctx.moveTo(bx + bw * 0.05, by + bh * 0.42);
    ctx.quadraticCurveTo(bx + bw * 0.5, by - bh * 0.16, bx + bw * 0.95, by + bh * 0.42);
    ctx.closePath();
    ctx.fill();
    ctx.strokeStyle = 'rgba(120,60,10,0.3)';
    ctx.lineWidth = 1;
    ctx.stroke();

    ctx.fillStyle = 'rgba(255,255,255,0.38)';
    ctx.beginPath();
    ctx.ellipse(bx + bw * 0.36, by + bh * 0.2, Math.max(2, bw * 0.12), Math.max(1.5, bh * 0.07), -0.45, 0, Math.PI * 2);
    ctx.fill();

    ctx.fillStyle = '#fff8e8';
    const seeds = [[0.3, 0.2], [0.48, 0.15], [0.66, 0.19], [0.38, 0.28], [0.58, 0.27], [0.76, 0.25]];
    for (let i = 0; i < seeds.length; i++) {
      const erx = Math.max(1.2, bw * 0.03);
      const ery = Math.max(0.8, bh * 0.04);
      ctx.beginPath();
      ctx.ellipse(bx + bw * seeds[i][0], by + bh * seeds[i][1], erx, ery, -0.45, 0, Math.PI * 2);
      ctx.fill();
    }

    ctx.strokeStyle = '#fff6ea';
    ctx.lineWidth = 1.7;
    ctx.beginPath();
    const tx = bx + bw * 0.67;
    const ty = by + bh * 0.18;
    ctx.moveTo(tx, ty);
    ctx.lineTo(tx + bw * 0.05, by - bh * 0.08);
    ctx.stroke();
    drawHeart(tx + bw * 0.05, by - bh * 0.1, Math.max(7, bh * 0.18), '#ff3d6e', -0.35, 1);

    ctx.restore();
  }

  function drawBall() {
    for (let i = 0; i < trail.length; i++) {
      const t = trail[i];
      const a = (i + 1) / trail.length;
      ctx.fillStyle = 'rgba(255,77,109,' + (a * 0.28) + ')';
      ctx.beginPath();
      ctx.arc(t.x, t.y, ball.r * (0.35 + a * 0.65), 0, Math.PI * 2);
      ctx.fill();
    }
    const glow = ctx.createRadialGradient(ball.x, ball.y, ball.r * 0.2, ball.x, ball.y, ball.r * 2.6);
    glow.addColorStop(0, 'rgba(255,70,110,0.55)');
    glow.addColorStop(1, 'rgba(255,70,110,0)');
    ctx.fillStyle = glow;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r * 2.6, 0, Math.PI * 2);
    ctx.fill();

    const g = ctx.createRadialGradient(ball.x - ball.r * 0.35, ball.y - ball.r * 0.4, 1, ball.x, ball.y, ball.r);
    g.addColorStop(0, '#fff');
    g.addColorStop(0.35, '#ffd0dc');
    g.addColorStop(1, '#ff2d55');
    ctx.fillStyle = g;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fill();
    drawHeart(ball.x, ball.y + ball.r * 0.08, ball.r * 0.95, '#ffffff', ball.spin, 0.92);
  }

  function draw() {
    if (W < 40 || H < 40) return;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.fillStyle = '#14080c';
    ctx.fillRect(0, 0, W, H);
    ctx.save();
    if (shake > 0.25) {
      ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);
    }
    ctx.globalAlpha = 1;
    ctx.lineWidth = 1;
    ctx.textAlign = 'left';
    ctx.textBaseline = 'alphabetic';

    drawBackground();

    for (let i = 0; i < ambient.length; i++) {
      const a = ambient[i];
      drawHeart(a.x, a.y, a.s, a.color, a.rot, a.a);
    }
    for (let i = 0; i < bricks.length; i++) drawBrick(bricks[i]);

    for (let i = 0; i < rings.length; i++) {
      const r = rings[i];
      ctx.beginPath();
      ctx.arc(r.x, r.y, Math.max(1, r.r), 0, Math.PI * 2);
      ctx.strokeStyle = r.color;
      ctx.globalAlpha = Math.max(0, r.a);
      ctx.lineWidth = 2;
      ctx.stroke();
      ctx.globalAlpha = 1;
    }
    for (let i = 0; i < particles.length; i++) {
      const p = particles[i];
      drawHeart(p.x, p.y, p.s * (0.75 + p.life * 0.35), p.color, p.rot, Math.max(0, p.life));
    }

    drawPaddle();
    for (let i = 0; i < steam.length; i++) {
      const s = steam[i];
      ctx.fillStyle = 'rgba(255,255,255,' + (s.a * s.life).toFixed(3) + ')';
      ctx.beginPath();
      ctx.arc(s.x, s.y, Math.max(1, s.r), 0, Math.PI * 2);
      ctx.fill();
    }
    drawBall();

    ctx.globalAlpha = 1;
    ctx.textBaseline = 'top';
    ctx.textAlign = 'right';
    ctx.font = 'bold 12px Trebuchet MS, Segoe UI, sans-serif';
    ctx.fillStyle = 'rgba(255,246,234,0.78)';
    ctx.fillText('LEVEL ' + level, W - 14, 12);
    if (combo > 1 && comboTimer > 0) {
      ctx.textAlign = 'left';
      ctx.font = 'bold 20px Impact, Arial Black, sans-serif';
      ctx.fillStyle = '#ffc857';
      ctx.fillText(combo + '× COMBO', 14, 8);
    }

    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = 'bold 15px Trebuchet MS, Segoe UI, sans-serif';
    for (let i = 0; i < popups.length; i++) {
      const p = popups[i];
      ctx.globalAlpha = Math.max(0, Math.min(1, p.life));
      ctx.fillStyle = '#fff6ea';
      ctx.fillText(p.text, p.x, p.y);
    }
    ctx.globalAlpha = 1;

    if (banner.t > 0) {
      ctx.globalAlpha = Math.max(0, Math.min(1, banner.t * 1.6));
      const size = Math.round(Math.min(52, Math.max(26, W * 0.055)));
      ctx.font = 'bold ' + size + 'px Impact, Arial Black, sans-serif';
      ctx.lineWidth = 6;
      ctx.strokeStyle = '#6a1b2a';
      ctx.fillStyle = '#ffc857';
      ctx.lineJoin = 'round';
      ctx.strokeText(banner.text, W / 2, H * 0.5);
      ctx.fillText(banner.text, W / 2, H * 0.5);
      ctx.globalAlpha = 1;
    }

    if (pain > 0) {
      ctx.fillStyle = 'rgba(255,40,60,' + (pain * 0.28) + ')';
      ctx.fillRect(-20, -20, W + 40, H + 40);
    }
    ctx.restore();
  }

  function resetForPlayer() {
    score = 0;
    lives = 3;
    level = 1;
    baseSpeedMul = 1;
    combo = 0;
    comboTimer = 0;
    clearing = false;
    clearTimer = 0;
    serveWait = 0;
    particles = [];
    rings = [];
    popups = [];
    trail = [];
    layoutBricks(true);
    launchBall(false);
    updateHUD();
  }

  function beginFromUser(fromPointer) {
    if (mode === 'play') return;
    if (mode === 'over') resetForPlayer();
    mode = 'play';
    pointerCtl = !!fromPointer;
    overlay.classList.add('hidden');
    showBanner("YOU'RE UP!");
  }

  function pointerPos(e) {
    const rect = canvas.getBoundingClientRect();
    if (rect.width <= 0) return null;
    return (e.clientX - rect.left) * (W / rect.width);
  }

  window.addEventListener('keydown', (e) => {
    keys[e.code] = true;
    if (e.code === 'ArrowLeft' || e.code === 'ArrowRight' || e.code === 'Space') e.preventDefault();
    if (mode !== 'play') beginFromUser(false);
  });
  window.addEventListener('keyup', (e) => { keys[e.code] = false; });
  window.addEventListener('blur', () => {
    for (const k in keys) keys[k] = false;
  });
  window.addEventListener('pointerdown', (e) => {
    pointerX = pointerPos(e);
    if (mode !== 'play') beginFromUser(true);
    else pointerCtl = true;
  });
  window.addEventListener('pointermove', (e) => {
    pointerX = pointerPos(e);
    if (mode === 'play' && (e.pointerType === 'touch' || e.buttons)) pointerCtl = true;
  }, { passive: true });
  window.addEventListener('resize', () => { if (ready) resize(); });

  function loop(now) {
    const rect = stage.getBoundingClientRect();
    if (Math.abs(rect.width - W) > 1 || Math.abs(rect.height - H) > 1) resize();
    const dt = Math.min(0.034, (now - last) / 1000);
    last = now;
    update(dt);
    draw();
    requestAnimationFrame(loop);
  }

  function boot() {
    if (!ctx) return;
    resize();
    if (W < 80 || H < 80) {
      requestAnimationFrame(boot);
      return;
    }
    if (!ready) {
      ready = true;
      lives = 3;
      score = 0;
      best = 0;
      level = 1;
      baseSpeedMul = 1;
      combo = 0;
      mode = 'demo';
      placed = true;
      launchBall(true);
      paddle.x = ball.x - paddle.w / 2;
      clampPaddle();
      seedAmbient();
      for (let i = 0; i < 70; i++) update(1 / 60);
      if (particles.length < 40) {
        let n = 0;
        for (let i = 0; i < bricks.length; i++) {
          if (bricks[i].state === 'live') {
            destroyBrick(bricks[i]);
            n++;
            if (n >= 4) break;
          }
        }
      }
      updateHUD();
      last = performance.now();
      requestAnimationFrame(loop);
    }
    draw();
  }

  boot();
})();
</script>
</body>
</html>
Reasoning trace (96,964 tokens)
Planning a Breakout game with a hamburger paddle. Each broken brick will explode into many small hearts.

Building a single HTML file with inline CSS and JavaScript. It will be responsive from 1280×800 down to 375px, self-playing behind a start prompt, and free of external dependencies or console errors.

The hamburger paddle will be drawn on canvas with bun, lettuce, patty, cheese, and tomato layers. A pink and red diner theme will frame the game. The AI demo will track the ball with the paddle. An overlay will prompt the user to click or press a key to begin.

The demo AI will start immediately so the game is visibly playing within seconds. The user can take control by clicking or pressing a key. The canvas will fill the viewport with a HUD for score and lives. When all bricks are cleared the level resets. The first paint will already show the ball in motion and hearts exploding.

The hamburger will be drawn with a bottom bun, patty, cheese, lettuce, tomato, and a top bun with sesame seeds. Broken bricks will release heart particles that fly outward with velocity, gravity, fade, and rotation. The background will be dark navy or deep burgundy with neon bricks for a retro arcade feel. The game title will be "Heartbreak Burger" or "Love Smash". A top bar will display the score and lives, the canvas will hold the game, and a bottom hint will remain visible.

The canvas will scale to fit its container using a virtual resolution that maps to the actual display size. The markup will use a wrapper with a header, a stage containing the canvas and the start overlay, and inline styles and script.

The game state will track ball position and velocity, paddle dimensions, brick positions and health, heart particles, score, and lives. The demo AI will follow the ball with slight lag so the paddle never misses perfectly. If the AI clears all bricks too quickly, the bricks will respawn to keep the demo visually busy.

The ball will start moving quickly toward the brick field so several bricks break within the first three seconds. Each broken brick will spawn 18 to 30 heart particles in pink, red, hot pink, white, and gold. A dedicated function will draw the heart shapes.

The heart shape will be drawn with bezier curves. The hamburger paddle will be a rounded stack about 140 by 48 pixels. The game loop will use requestAnimationFrame. Collisions will handle walls, the bottom edge, the paddle, and bricks. The paddle will bounce the ball based on where it hits. Bricks will be destroyed with AABB collision, spawning hearts and adding to the score.

Brick collisions will reflect the ball based on overlap direction or previous position, handling multiple bricks per frame. The background will use a diner tablecloth or neon grid pattern. Visual polish includes a glowing ball, brick shine, heart particle trails, subtle screen shake on break, and floating combo text. Score popups will also appear as particles. Lives will render as mini burger icons. The game will continue from the current state when the player takes over.

The player will take over the current demo game rather than starting fresh. The ball will keep moving and the score will carry over. A controls hint will appear for left/right arrows and A/D.

The paddle will move via mouse or touch drag. Horizontal scrolling will be prevented with CSS overflow rules. The game will use only system fonts such as Trebuchet MS. Brick colors will form rainbow rows in ketchup red, cheese yellow, lettuce green, and bun tones. The background will be a deep chocolate and burgundy diner night.

The layout will use 100vh with overflow hidden to prevent scrolling. The canvas will match the display size directly rather than using a fixed logical resolution, so game coordinates map one-to-one to CSS pixels.

The brick grid will adjust dynamically to the viewport width, using eight columns on desktop, six on mobile, and five on small screens. The demo AI will calculate the paddle target as the ball's x-position minus half the paddle width, then move the paddle toward that target with a maximum speed of nine pixels per frame.

The demo AI will move the paddle toward the ball with a 0.18 acceleration factor, capped at nine pixels per frame. The ball will start centered and angled upward so it reaches the brick field in under a second. Heart particles will be limited to four hundred total and removed oldest first to prevent performance drops. Sizes will range from four to twelve pixels. The explosion will be hearts only, with no other particle types.

No sound effects will be added to avoid autoplay policy issues and console warnings.

The hamburger paddle will be drawn with rounded rectangles for the bun, patty, cheese, lettuce, and tomato layers. It will be sized between 120 and 160 pixels wide and 36 and 52 pixels tall so it stays recognizable at small resolutions.

The paddle will be slightly taller so the burger reads clearly. The collision box will cover the full burger shape, and the ball will hit the top bun. A slight squash animation will play when the ball strikes the paddle. Score popups will float upward. When the player clears a level, a rainbow burst of hearts will appear and the ball will speed up. The demo will run with infinite lives and instantly relaunch the ball if the player loses.

The initial overlay will use the exact phrase "Click or press a key to start". A subtitle will appear beneath it. The first frame will already show the board, burger, ball, and bricks so the scene feels alive before any bricks break.

The demo will break bricks immediately so the action is visible within the three-second window. A welcome burst is unnecessary. The layout will shrink responsively at 375 pixels, with a smaller title, paddle, and fewer columns. Mouse movement will be tracked relative to the canvas using pointer events.

The canvas will use a device-pixel ratio of up to two, scaling the backing store to match the display size while keeping CSS pixels as logical coordinates. Pointer position will be mapped directly from clientX minus the canvas bounding rect, since the context scales by the pixel ratio.

The stage will use a flex column layout so the canvas fills the remaining space. The overlay will be absolutely positioned and centered, with pointer-events disabled so clicks pass through to the game.

The start overlay will use a dark red background with a golden border, centered white text, and a soft pulsing animation. Key presses and pointer clicks will both trigger the game, and the overlay will be hidden via a CSS class rather than an alert.

Arrow keys and space will be captured to prevent browser scrolling, and pointerdown will also start the game. The paddle will follow the pointer while the user holds it, or track the mouse after the game begins.

The ball will start at eight pixels radius with a horizontal speed of 4.2 and a vertical speed of -6.2. Its speed will scale with the canvas width, clamped between 0.65 and 1.15, so the game feels consistent on mobile and desktop. The demo AI will ignore mouse input, while the player will control the paddle with both mouse and keyboard.

The ball will be pushed out of bricks by the smaller overlap distance to prevent getting stuck. Paddle collision will use a vertical guard band so the ball can only hit the burger when it is falling downward.

The ball will be reflected with a classic angle-based formula that clamps horizontal speed and guarantees a minimum upward velocity. A squash animation will play on paddle hits. The background will be drawn every frame with a gradient, subtle checker pattern, and diner counter stripe. The developer is deciding whether to render the UI on the canvas or keep it in HTML.

The UI will be rendered in HTML rather than on the canvas. A header will display the game title, subtitle, score, and lives. The hamburger paddle will be drawn on the canvas so it reads clearly as a burger shape.

Lives will be drawn as mini burger icons or heart-emoji text. A small "← → or mouse" hint will appear on the canvas only after the game starts. The first frame will already show the full brick wall, hamburger paddle, ball, and overlay message so the scene feels alive immediately. The demo will include a sesame seed animation and a slight idle bob on the paddle so the scene looks active before any bricks break.

Bricks will gain small top highlights to look like candy tiles or ingredient labels. Each row will use a distinct color: ketchup red, cheese gold, lettuce green, tomato, onion purple, and pickle. The explosion will spawn twenty-two to thirty-two hearts with random speed, spin, gravity, and fade.

The heart burst will use a palette of reds and pinks. Each heart spawns with random speed, spin, gravity, and size, then fades out over time.

A shockwave ring will appear when bricks break. Screen shake will offset the canvas draw by a few pixels each frame. The full code will be written carefully. Game over and win states will be handled. When all bricks are cleared, a heart explosion will burst from the center, the bricks will rebuild, the player will get a bonus score, and the ball will speed up slightly. The demo will never show a permanent game-over overlay.

The game will have four states: demo, play, serve, and over. The demo will begin with the ball already moving. After the player clicks to start, the game will enter a brief serve state so they can position the ball before play begins. A "you win" message will float briefly on screen. The player will have three lives.

The ball will keep moving immediately when the player starts, with no pause. The developer is also guarding against common runtime errors: null element access, division by zero, and splicing particles while iterating.

Particle physics will use delta-time scaling for consistent speed across frames. The animation loop will clamp frame duration to 32 milliseconds and normalize it against 16.666. Velocities will be multiplied by that normalized delta. The canvas will be resized once before the loop starts.

A boolean grid will track which bricks are alive so resizing the canvas does not reset the level. The paddle will sit about twenty-eight pixels above the bottom edge. The ball radius will scale slightly with the canvas width. A glowing effect is being considered for the ball, perhaps a white core with a pink ring.

The ball will be a glossy ketchup-red orb with a heart icon drawn on its surface. The title will use playful bold letter-spacing. The color palette will be a dark brown-to-red radial background, cream text, mustard accents, and pink. A diner checkered floor will sit behind the paddle area. The draw function will clear the canvas, paint the gradient, and then render the scene.

The scene will be drawn in layers: background, bricks, paddle, ball, then heart particles, and finally HUD extras and floating score text. Hearts will render after bricks so they burst on top of the wall. A checkered floor will sit in the bottom eighteen percent of the canvas, and hanging lights may be added for ambiance. Score labels like "+100" will float upward and fade. Screen shake will use a random offset multiplied by a decay factor, applied after the device-pixel-ratio transform.

A roundRect helper will draw pill-shaped bricks with arcTo. The hamburger paddle will be drawn with a squash transform so it compresses on ball hits.

The hamburger paddle is being drawn with a drop shadow, a bottom bun with a gradient and highlight, and a brown patty.

The hamburger paddle is being drawn with a dark brown patty, four grill marks, and a yellow cheese slice.

The hamburger paddle is being drawn with a yellow cheese slice, a green lettuce leaf, and a red tomato slice.

The hamburger paddle is being drawn with a red tomato slice, a white highlight on it, and a golden top bun gradient.

The hamburger paddle is being completed with a subtle bun outline and six sesame seeds.

Paddle dimensions will be roughly 150 by 56 pixels on desktop and 110 by 44 on mobile. Collision will use the paddle’s bounding box, so the ball hits the top of the bun. A nameplate under the game is unnecessary. CSS will clamp the header and message text on small screens and prevent horizontal scrolling. Updating the score element every frame is acceptable.

The score element will update only when the score changes. Lives will display as small burger emoji circles in the header. A level indicator will sit there too. A combo multiplier was considered but skipped to keep the code simple. The ball will leave a fading trail of twelve previous positions. The developer is now ready to write the complete file.

The start overlay will stay visible until the player clicks or presses a key. Pointer events will be disabled on the overlay so clicks pass through to the game canvas. Keydown listeners will also be attached to the window to catch keyboard input.

The start overlay will remain visible until the player clicks or presses a key, since the iframe only receives keyboard events after being focused. After the game begins, a small "Move: mouse or ← →" hint will be drawn on the canvas in a corner so it does not block the scene. When the player loses all lives, a "Game Over" message will appear on the overlay and pressing a key or clicking will restart the game.

The game will use a simple state machine with three modes: demo, play, and over. The start overlay will remain visible in both demo and over states. During the demo, the ball will reset above the paddle with upward velocity after each miss without deducting a life. A stuck-ball detector will nudge the ball if its vertical velocity remains nearly zero for too long.

A painted border will frame the playfield so the ball feels contained. Bricks will sit inset from the edges. A moving shine sweep across bricks was considered but skipped to keep performance smooth. The idle hamburger will bob slightly.

The idle hamburger will bob slightly via a sine offset, but collision will use the true paddle position so the physics stay stable. When the ball hits the paddle, it will briefly squash and then recover. The heart particles will be bright and large so they remain clearly visible in screenshots, and they will only spawn when bricks are broken rather than at the start.

The ball will launch from the center toward the bricks immediately. It should reach the wall in about one second and break several bricks before the three-second mark, so the heart particles remain visible. The game will stay classic with a single ball. A white flash will appear briefly when a brick breaks.

The explosion will use forty heart particles total: twenty-five medium hearts and fifteen tiny ones. A resize guard will skip updates if the stage dimensions are zero, preventing layout errors before the first paint.

A resize guard will skip updates if the stage width or height is below two pixels. The HTML and body will be set to full height so the stage dimensions are available immediately. Brick labels and tiny text were considered but rejected to keep the wall clean; instead, each row will get a colored highlight and a small icon. A row-colors array is being defined for the brick palette.

Seven brick colors are now defined, each with a fill, highlight, and shade. Level progression will shift these colors. A high score will be tracked in memory only, displayed as BEST in the header.

The demo score will carry over into play. The board will remain intact and the player will simply take control, avoiding a jarring reset after the screenshot.

The demo AI will track the ball’s x position with easing, staying fast enough to rarely miss. When the ball is moving upward, the paddle will drift toward the center rather than chase the predicted landing spot.

The paddle will be capped at 14 pixels per frame so it cannot teleport across the screen. Touch and pointer events will be captured after the game starts, and the mouse position will be clamped to the canvas bounds.

Keyboard and mouse input will be handled together. The paddle will follow the mouse with a slight delay, but if the player presses arrow keys, the mouse will be ignored for a short time to avoid fighting the controls.

The paddle will snap directly to the pointer position rather than easing toward it, giving a sharper breakout feel. Keyboard input will add velocity independently of the mouse.

A red danger-zone glow will mark the bottom of the playfield so missed balls are clearly visible. The CSS will use a diner-arcade hybrid style with Impact or Trebuchet MS for the title and Segoe UI for body text. A small hamburger icon will sit beside the title. The developer is now checking spacing for a 1280 by 800 viewport.

The header will be 76 pixels tall and the game area 724 pixels. On narrow screens the header shrinks to about 64 pixels, the paddle drops to 100 pixels, and the brick wall narrows to six columns. The banner will use a responsive width capped at 520 pixels. The start message will wrap and scale between 16 and 26 pixels. A secondary line will note that the demo is on autopilot. ARIA labels and a title attribute will be added.

A tiny legend will explain that bricks are orders and smashing them is the goal. Forbidden APIs like localStorage and alerts are being avoided. Heart particles will be clamped to positive sizes to prevent rendering issues. A brief level flash will appear in the center when the level changes, but it will not cover the start message. With sixty bricks and the ball breaking only eight to fifteen in three seconds, the demo will likely stay at level one and avoid any level-up overlap.

Wall bounce particles were considered but rejected to keep the heart explosion limited to brick breaks. The ball will be served with a velocity aimed directly at the brick cluster so it clears the wall quickly.

The ball will be launched with a fixed upward-right velocity so the demo is deterministic and predictable. A subtle vignette and floating ambient hearts will be painted in the background to create atmosphere even before any bricks are broken.

Twelve slowly drifting ambient hearts will float in the background. They will be coded separately from the brick explosion particles so the 500-particle cap does not cull them. This ensures the heart theme is visible even before any bricks are broken.

A score popup will appear when a brick is broken. Each brick will scale up and fade out over eight frames before disappearing, while its heart explosion spawns instantly. This creates a satisfying crack effect.

Bricks will carry a three-state flag: live, breaking, or gone. The break animation scales each brick up by 40 percent and fades it out over one second before removal. A bright inner flash will burst from the heart origin on impact. The paddle will be clamped to eight pixels from each edge. The developer is now ready to write the file.

Ball collisions will be clamped to eight pixels from each edge. In demo mode, a missed ball resets automatically; in play mode, lives decrement and the game ends when they reach zero.

The game over message will read "Click or press a key to play again". A `beginPlay` function will switch the demo into play mode, or reset the level and enter play mode from game over. Pointer and key events will only trigger this transition when the game is not already running. The overlay will be hidden via CSS class, visibility, and opacity. Controls will be placed in an HTML footer inside the stage at the bottom, with pointer events disabled so they do not interfere with gameplay.

A small hint label reading "Mouse, touch, or arrow keys" will sit at the bottom center of the stage. The HUD will remain in HTML rather than being drawn on the canvas, keeping it crisp and responsive. Lives will be shown as mini burger icons instead of hearts.

A combo counter will be drawn on the canvas. A wood-grain frame will be painted with repeating lines. The ball will leave a visual trail.

The ball trail fades from bright pink to transparent as it stretches behind the sphere. A radial gradient gives the ball a soft pink glow. A tiny heart icon will sit on the ball surface.

The ball will be a glossy pink sphere with a small white heart on its surface. A pulsing ring will surround the overlay message. The overlay will layer over the canvas so the bricks and burger remain visible behind it, making the initial screenshot look complete.

The canvas will be painted once before the animation loop starts so the scene is visible immediately, even if JavaScript fails to load.

A boot sequence will run resize, reset the level, update the HUD, draw the first frame, and start the animation loop. During resize, the ball will be clamped back into the playfield if it drifts outside, and the speed scale will be updated globally. Resizing will only reposition the paddle and scale the brick grid rather than triggering a full level reset.

The developer is simplifying the brick layout logic. A `layoutBricks` function will compute column and row counts based on viewport width and height, then derive brick dimensions and positions from a fixed margin and gap. If the grid dimensions change during resize, the entire brick array will be rebuilt; otherwise only the x, y, width, and height of each brick will be updated.

Paddle width is clamped between 84 and 168 pixels, scaled to 16 percent of the viewport width. Its height is fixed at 38 percent of that width, capped at 62 pixels. The paddle sits 3.5 percent above the bottom edge. On first initialization it centers horizontally; on resize it keeps its relative position. The ball radius is clamped between 6 and 10 pixels, scaled to 1.1 percent of the viewport width.

The body will have its default focus outline removed. A touchmove listener will block page scrolling during gameplay. The developer is also checking for canvas edge cases, such as a null context or invalid gradient parameters.

The developer will use CSS `touch-action: none` on the body instead of a JavaScript touchmove listener to prevent scrolling. Antialiased font smoothing will be enabled for crisp text. A decorative awning pattern will be drawn in the header using CSS repeating linear gradients.

The header will use a white-and-red awning pattern with a dark bottom border. Brand and stats plaques will sit on a dark background with rounded corners for readability. Stats will appear in mustard yellow. A subtitle reading "Break bricks. Spill hearts." will sit beneath the main title. The developer considers a ticker line but decides against it to avoid clutter on small screens.

A custom `drawHeart` function is being written to render each heart particle with a precise bezier curve path. The heart shape will be drawn at a specific position, size, rotation, and opacity so the explosion looks distinct and recognizable.

Each brick explosion will spawn 36 hearts. The burst will mix deep red, white, and gold tones. A few larger hearts will appear alongside many tiny ones to create the "tons of little hearts" effect.

A ring burst will expand from each brick explosion as a colored stroke. The level name will be drawn in the corner of the canvas each frame. The demo badge will sit near the overlay. The entire response will be wrapped in a single HTML fenced code block.

Ball movement will be split into two substeps per frame to prevent tunneling through bricks at higher speeds.

Paddle collision is now restricted to downward-moving balls that intersect a slightly expanded paddle rectangle. The hit position is mapped to a -1 to 1 range based on where the ball strikes the paddle. Ball speed is clamped between 6.2 and 11 times the speed scale, with a minimum boost applied.

Paddle collisions will use a squash effect and a sesame shake. Brick collisions will store the ball's previous position to detect side hits more reliably.

The developer is finalizing the brick collision logic. If the ball previously crossed the left or right edge of a brick, the horizontal velocity reverses; if it crossed the top or bottom, the vertical velocity reverses. A fallback overlap check resolves diagonal hits by comparing the smallest horizontal and vertical overlap distances. After the bounce, the ball is pushed out of the brick bounds.

The developer is adding a module-level `dtStep` variable to control the substep duration. After a brick collision, the ball is pushed out of the brick bounds using a small displacement vector. If two bricks are destroyed in one substep, the loop breaks after the first hit so the next substep can process the second. A `destroyBrick` function is being defined to mark a brick as breaking and initialize its break timer.

Breaking a brick awards 100 points multiplied by the current combo, which resets after 90 frames. The developer adds a shake effect and a floating score popup above the destroyed brick. When the last brick is cleared, the game waits briefly so the final explosion particles can finish before rebuilding the level.

The level clear timer is set to 40 frames. Once it expires, the level number increments, the base speed multiplier increases, the brick grid is rebuilt, and eight celebration hearts rain down from the top of the screen. A 500-point bonus is added to the score. To prevent the particle system from overflowing, a maximum cap of 450 hearts is enforced, and any excess particles are discarded.

The level-clear celebration will use one large heart burst from the center instead of eight separate ones. A combo counter will display on screen when the streak exceeds one. The best score will persist in a variable. The hamburger paddle will be sized large enough to read clearly at 168 by 62 pixels.

The hamburger paddle width is being raised to 100 pixels on mobile so the burger remains readable. A silver platter ellipse will sit beneath the paddle to make the hamburger clearly identifiable as the playfield tool.

The hamburger paddle will tilt slightly left or right based on its horizontal velocity for added personality. The developer is now writing the complete HTML, including a meta viewport tag. The start overlay will display the largest text possible, and the same element will be reused for game-over messages.

The overlay will use two text elements for the title and subtitle. A `showOverlay` helper will populate them and reveal the card. The initial HTML already contains the start message, so the first paint will display it even before JavaScript runs.

The overlay card will include an "AUTO-PLAY DEMO" kicker and a subtitle explaining that the burger paddle plays itself until the player takes over. A CSS hidden class will fully suppress the overlay with zero opacity, no pointer events, and hidden visibility. The keyboard listener will safely ignore unexpected keys and only intercept arrow keys and space to prevent page scrolling.

A pointerdown listener will also start the game when the user clicks or taps. The game will not restart while already in play. A playing class on the body will toggle the hint text to show the player controls.

The hint text will read "Watch the demo" when the game is idle and "Mouse · touch · ← →" once the player takes over. The "AUTO-PLAY DEMO" kicker will animate via CSS. Each heart drawing will save and restore the canvas globalAlpha to prevent state leaks. The main draw loop will reset globalAlpha to 1 at the start. Canvas shadowBlur will be avoided on the heart particles to keep the particle system fast, though the ball may still receive a subtle glow.

The ball will glow with a 16-pixel shadow blur, but that blur must be reset to zero immediately after drawing so the rest of the scene stays sharp. The developer is now writing the full HTML and CSS, and is carefully checking for layout bugs such as null score elements, mismatched IDs, and flex container sizing.

The stage will flex to fill available space with `min-height: 0` to prevent overflow. The canvas will be absolutely positioned to fill the stage. Because setting the canvas width clears its context state, the transform matrix must be reapplied after every resize.

The draw loop will reset the canvas transform matrix at the start of every frame to keep device-pixel scaling correct. Shake offsets will be applied after clearing the canvas so the background covers any gaps left by the translation.

The developer is adding a diner floor gradient that fades from transparent to dark gray at the bottom of the playfield. They are also considering a starfield or checkered pattern background.

The diner floor will use a 28-pixel checkerboard pattern in deep red and dark brown. It will sit behind the paddle and bricks. The background will be a warm radial gradient. The draw order is background, floor, bricks, particles, paddle, ball, effects, then HUD text.

The developer is adding a warm radial gradient background and a neon border frame to the canvas. They will also place ambient hearts behind the bricks and add a soft glow behind the brick area. The code is now ready to be written.

Each brick will draw a small white heart in its center so even intact bricks hint at the payoff. Dying bricks will scale from the center, fade, and rotate slightly.

Score popups will fade and drift upward using a bold Trebuchet MS font. The combo banner will render in gold Impact at the bottom of the brick grid while the streak timer is active.

A red flash will tint the screen briefly when the player loses a life. The demo will not trigger this flash, keeping misses smooth. The AI will predict the ball's future path by calculating where it will land based on its velocity, then adjust the paddle to intercept it.

The AI will fold the ball's predicted landing point into the playfield width so it bounces off the walls. It then moves the paddle toward that point at a speed capped by the current speed multiplier. The developer noticed the step calculation might be too small to reach distant targets quickly.

The paddle AI will use a simple linear clamp instead of exponential easing. It will move the paddle toward the ball's predicted landing point, capped at 12 pixels per frame. This keeps the burger responsive and fast enough to intercept the ball.

A `predictX` helper will simulate the ball's path frame by frame until it reaches the paddle height. It checks whether the ball would land inside the playfield walls and returns that x coordinate. This lets the AI intercept balls coming from far away.

The AI prediction only bounces off the walls, not the bricks, so it will correct itself after each collision. The demo will stay perfectly competent so the heart explosions keep coming.

The scoring system will use a classic multiplier: the first brick is worth 100 points, the next 200, and so on. A combo timer will last 2.2 seconds, and the combo resets to one once that timer expires. The developer is switching to real-world seconds for delta time and setting ball velocity to 220 pixels per second.

Physics will run in 1/120-second substeps to survive tab-hiccups. Ball speed is 420 pixels per second, the paddle cap is 780, gravity on hearts is 520, and heart velocity ranges from 80 to 420.

The developer switched to a simpler two-substep physics loop per frame instead of 120 substeps. Ball speed will scale with screen width, starting at 460 pixels per second, and the serve will set the ball's velocity to match that speed. The speed multiplier will be applied to the base speed before each serve.

Ball speed will be capped at 640 times the current scale to prevent runaway acceleration after repeated paddle hits. The header will display the current level alongside the score, best, and lives. The overlay kicker will toggle its visibility whenever the game mode changes.

The hamburger kicker will sit inside the overlay and stay hidden until the game starts. The canvas will be painted synchronously during boot, so the first frame already shows the burger before the browser finishes parsing. The developer also verified that the cheese drip path and sesame seed placement on the top bun look correct.

The hamburger will remain whole rather than showing a bite. Steam wisps will rise from the paddle as small alpha circles to suggest a hot, fresh burger. The title will read "♥ BURGER BREAKOUT".

The overlay card will include a CSS-only hamburger illustration above the text. This reinforces the burger theme on first paint.

The overlay will use a semi-transparent dark scrim so the bricks and paddle stay visible around the centered card. The CSS hamburger illustration will be about 70 pixels wide and sit above the overlay text.

The overlay scrim will use a dark red tint at 28% opacity. The card itself will be a deep brown with a gold border, rounded corners, and a soft shadow. Its padding will shrink on small screens. The title font will scale with the viewport width, capped between 18 and 30 pixels, and the start prompt will stay within the 460-pixel max-width to avoid awkward wrapping.

The overlay text will use normal white-space and a tight line-height so it stays within the card width on narrow screens. The header will use flex-wrap so the score, best, level, and lives can sit side by side or stack vertically without overflowing.

On screens under 560 pixels, the header will shrink to 8-pixel padding and 6-pixel gaps. The subtitle will hide to keep everything in one row. Brand text will use 18-pixel letter-spacing, stats will scale down to 16-pixel labels and 9-pixel values, and lives will use 14-pixel text. The brand element will get `min-width: 0` so the flex row never overflows.

The header will be trimmed to just the title, score, and lives to prevent overflow on narrow screens. The level and best score will be drawn directly on the canvas instead of in the header.

The header will display score, best, and lives in compact stat blocks. On screens under 420 pixels, the best score will hide to save space. A small "LV 1" label will sit on the canvas. The developer is now ready to write the final code and will check for syntax errors.

The developer is organizing the script to avoid temporal dead zone errors. State variables will be declared at the top of the IIFE, and functions will be written as declarations so they can safely reference those globals once execution reaches them.

The canvas context will use `alpha: false` to avoid compositing overhead. A window-level `pointermove` listener will track the mouse even when the cursor leaves the stage, but the paddle will only follow the pointer once the game has started.

The developer is wiring up pointer and touch controls. A window-level `pointermove` listener will track the cursor inside the canvas bounds. A `pointerdown` listener will set the active pointer flag when the user presses inside the stage.

The developer wired the pointerdown handler to activate the mouse-based paddle control only when the user clicks inside the canvas bounds. They also noted that keyboard arrow keys will still function after a click because the pointermove listener resets the offset on the next move event.

The developer finalized the paddle control logic. When the game is active, keyboard arrows will override the pointer and disable mouse tracking. If the user clicks to start, the paddle will stay at the click position until they press an arrow key. After that, the mouse will resume controlling the burger again.

The developer refined the paddle control logic. A `pointerCtl` flag will switch between pointer and keyboard input. Pointer tracking activates on `pointermove` and deactivates on arrow key presses. This prevents the paddle from jumping to the click position when the user starts with a mouse click.

Demo mode will ignore pointer input for the paddle. The game will start immediately on any pointerdown, and extra clicks during play will be ignored. A pointerdown on the game-over overlay will restart the match.

The developer is finalizing the game-over restart logic. When the user clicks the overlay, the game will fully reset to level one with three lives and a fresh brick layout. They are also weighing whether to clear the heart explosion particles during that reset, since wiping them out might look abrupt.

The developer decided to clear heart particles during a full reset so the next match starts clean. Demo mode will not trigger a game-over state, and the overlay will label the mode as AUTO-PLAY DEMO. A redundant DEMO ribbon on the canvas was considered but skipped to avoid clutter.

The developer will use the exact phrase "Click or press a key to start" for the overlay prompt. They are now ready to write the file. To add visual variety, roughly fifteen percent of spawned hearts will render as outlines instead of filled shapes, with stroke hearts using a scaled line width. The heart color palette will mix reds, pinks, golds, and white tones.

The developer decided against a full-screen flash effect to keep the colors vibrant for screenshots. Instead, brick breaks will trigger a subtle screen shake. The shake intensity will be capped at 3 pixels and decay quickly so it does not blur the image during long matches.

The hamburger paddle will sit 18 pixels above the bottom edge so the visual burger does not get cut off. The developer is now ready to write the final code.

The developer spotted a collision edge case. When all bricks are destroyed mid-substep, the ball may land inside the new brick layout before the respawn timer finishes. They will add a `safeBall` helper that pushes the ball below the bricks if it overlaps the respawn zone.

The `safeBall` helper will push the ball below the bricks and reverse its vertical velocity if it lands inside the respawn zone. A new `launchBall` function will reposition the ball above the centered paddle, fire it with a randomized upward angle, and clear the particle trail.

The developer refined the ball launch logic. The demo start will use a fixed angle so the ball reliably hits bricks immediately. Normal starts will use a randomized angle. The ball velocity will be normalized after calculation.

The developer will rescale existing ball velocities when the window is resized, keeping the speed ratio consistent. A new `boot()` function will run the initial resize and layout setup before initializing lives, score, and level.

The developer will launch the demo ball only inside the boot sequence, not during resize or mid-match resets. A resize handler will update geometry and clamp the paddle and ball within bounds, nudging the ball down if it lands inside a brick. The lives HUD will display burger emojis via textContent instead of innerHTML.

The developer is now designing the header CSS. It will use a repeating red-and-cream gradient with a dark inset shadow to create a distinctive plaques-and-awning look. The level number will sit in the header as well. The lives display will use string.repeat to render burger emojis.

The header plaques will use a deep red-brown gradient with a gold border and a dark shadow. The title will render in Impact with a cream color and a pink text shadow. Stat numbers will be large and bold in white.

The developer is adding a small bottom hint pill inside the stage. It will sit at the bottom center with a semi-transparent dark background and no pointer events so it does not block clicks. The lives stat number will be 16 pixels so the burger emojis fit properly.

The developer finalized the hint pill styling. It will use a 90% max-width and nowrap so the short control text stays on one line. The text will read "← →  ·  mouse  ·  touch". They also noted they should verify that the Impact font is available on Chrome across different operating systems.

The developer added a fallback to Arial Black for the title font. They decided against a canvas watermark to avoid cluttering the bricks. They are now writing the `spawnHearts` function to burst 34 hearts in a dense radial pattern with randomized angles and speeds.

The heart burst function adds slight random offsets to each heart's spawn position. Each heart receives a random color, a 12 percent chance of being an outline stroke, and a decay rate of 0.45 per second so the particles linger for roughly two seconds.

Heart decay will be faster at 0.8 so particles linger briefly but fade quickly. Each brick will spawn 40 hearts in a 3-to-9 pixel size range, with 70 percent small and 30 percent medium. The heart burst will inherit the ball's velocity so the hearts spray outward in the hit direction.

The developer will add a ring particle effect that expands outward from each brick break. The game will skip power-ups to keep the code readable. When the game ends, ball physics will freeze so the heart explosion can finish naturally.

The game-over overlay will fade in and out with a 0.3-second opacity transition. It will stay hidden and ignore pointer events so the canvas remains fully interactive while the overlay is off-screen.

The overlay will stay visible on page load so the initial screenshot captures the "Click to start" prompt. A subtle vertical pulse animation will be added to the card border to draw attention on first paint. The developer is checking that this transform will not create a horizontal scrollbar.

The developer will explicitly set `overflow-x: hidden` on the html and body elements to prevent any horizontal scrollbar. They also plan to guard against negative radii when drawing heart particles with `ctx.ellipse`.

The developer added a retry loop to `boot()` so it re-runs via `requestAnimationFrame` if the canvas dimensions read as zero. They also introduced a `startedLoop` flag to prevent the game loop from launching twice.

The `boot()` function now initializes lives, score, and level before launching the demo ball. The game loop will also check canvas dimensions at the start of each frame, not just on resize events.

The developer will keep the canvas display size in CSS and only set the backing store width and height in JavaScript. This prevents pixel desync. The resize function will read the stage bounds, clamp the device pixel ratio to two, and compute the backing store width from the scaled bounds.

The resize function now recalculates the canvas backing store dimensions and updates the speed scale based on the smaller viewport dimension. It then repositions the bricks, fits the paddle, and clamps the ball. The developer noted that checking canvas dimensions every frame is unnecessary; the resize handler should only run when the stage bounds actually change.

The developer will keep the paddle centered relative to the viewport width. A `paddleRel` variable will store the paddle's horizontal position as a fraction of the stage width. `fitPaddle` will compute the paddle's size and position from this ratio, and `clampPaddle` will enforce screen bounds and update the ratio whenever the paddle moves.

The ball will be initialized at a fixed off-screen position with zero velocity. Its radius will be clamped between 6 and 11 pixels, and it will be nudged away from the edges so it cannot spawn inside a brick.

The developer will add a trail effect to the ball physics. The paddle will squash vertically around its bottom edge when it hits a brick, scaling down to 0.8 and then recovering over time. A small burst of four tiny hearts may appear on paddle bounces as a subtle delight, but the main heart explosion will remain limited to bricks.

The developer decided against adding sesame-colored crumbs on paddle hits; the effect will stay limited to squash and a speed change. To improve visibility of white hearts on yellow bricks, they will draw each heart with a dark translucent shadow and a bright white heart at 80 percent opacity.

The developer will keep one white heart per brick rather than a dense burst, since the heart explosion is the main feature and a single heart is enough to establish the theme on first paint. They will add row labels for ingredients but skip them because they are too small to read. The final code will be a single HTML block with no extra text, and they are ensuring the visual output looks polished and impressive.

The ball will start just above the bottom row of bricks so it hits them immediately. This creates a rapid cascade of heart explosions within the first few seconds, ensuring the screenshot is packed with hearts. A `brickBottom` helper will calculate the lowest brick position.

The `launchBall` function now places the demo ball at 45 percent of the stage width and 36 pixels below the lowest brick, giving it a diagonal upward velocity. The ball speed is normalized to the base speed multiplied by the viewport scale. This ensures the ball strikes the bricks almost immediately, so the heart cascade begins within the first few frames and fills the screen with lingering particles by the three-second mark.

The ball will ricochet through the brick grid to rain hearts. Collision will use three substeps per frame to prevent the ball from getting stuck inside bricks.

The demo ball will use a fixed upward-right angle so the initial cascade is predictable, then chaos will follow naturally after a few bounces. A NaN safety check will relaunch the ball if physics ever corrupt its velocity.

The developer will draw a wooden counter beneath the paddle as a vertical gradient band with subtle wood grain lines. They also confirmed that `launchBall` will run only after the bricks are laid out, preventing an empty array error.

The developer abandoned the wood counter and checker floor idea. They will use a deep berry gradient background instead.

The developer abandoned the deep berry gradient background. Instead, the scene will feature soft bokeh circles, a brick wall, a wood counter strip, and a burger on the counter. They will add a thin gold inner frame for depth. A "ORDER UP!" celebration message will appear when the level clears.

The grid will scale responsively: 4 to 6 rows and 5 to 11 columns depending on viewport size. On a 1280×700 screen this yields 66 bricks, creating a dense, colorful wall that fills the screen and ensures the heart cascade lasts long enough to look impressive.

The developer finalized the brick layout: a 5–6 pixel gap, 14 pixel margin, and a 36 percent height cap for the brick area so the paddle has room. The grid will stay straight rather than staggered to keep collisions simple. Each brick will have a 1 pixel gap and 6 pixel rounded corners. The overlay card will be positioned so it does not cover the brick wall.

The overlay card will be vertically centered so it does not obscure the brick wall or the burger. On short screens it will shift to the bottom edge to keep the burger visible.

The developer confirmed the overlay card will not obscure the brick wall or burger on typical mobile screens. On very short viewports the card may overlap slightly, which is acceptable. They will also scale the card's burger icon down for small screens and disable text selection and touch scrolling to keep the paddle drag smooth.

The developer is now writing the heart-drawing code. They will use a `heartPath` helper to build each heart shape, apply a translucent fill, then draw a bright white highlight by multiplying `globalAlpha` by 0.45 before restoring the context. Strokes will be drawn at a line width of 35 percent of the heart size.

The heart shape is now fully defined with bezier curves. A tiny white shine will be drawn on each brick to add subtle depth.

A `drawBrick` helper will render bricks with alpha and scale transforms, plus a faint inner border. The score popup will use a bold 14px Trebuchet MS font, and text alignment and baseline will be reset after each draw to prevent side effects.

Canvas state persists across frames, so `textAlign`, `textBaseline`, `globalAlpha`, `shadowBlur`, `lineWidth`, and the composite operation will all be reset at the start of every draw call. A trail of tiny hearts will also follow the ball between brick hits to keep the heart theme constant.

The developer will fire 46 hearts per brick to create the "tons" effect. A second wave of slower floating hearts will drift upward so they linger on screen for the screenshot. The particle cap is large enough to handle both the trail and the explosion bursts without evicting either.

The 46 hearts will split into 30 fast confetti bursts and 16 slower floating drifters. No audio will be used to avoid WebAudio warnings.

The game will run in demo mode by default so the overlay message is visible without interaction. A blur listener will clear all keys if the window loses focus, and keyup events will prevent stuck keys. The developer is also checking whether to support both 'ArrowLeft' and 'Left' for backward compatibility.

The developer will use `e.code` for movement keys and `e.key` for the start key. Arrow keys and Space will call `preventDefault` to stop page scrolling. The game will begin as soon as the user presses any key.

The developer added a guard so `beginFromUser` exits early once the game mode switches to play, preventing repeated keydowns from restarting the game. They also noted that pointerdown and keydown can fire together on a click, which would set the mode and then immediately return. They decided against adding a high-contrast focus style and will include a touch-action hint to keep the paddle drag smooth.

The developer will use hoisted function declarations for `beginFromUser` so it can be called before the event listeners are attached. They also added a `loseLife` function that decrements lives, updates the HUD, and switches to the game-over screen when the player runs out of lives. During a life loss, the ball will pause on the paddle for a short delay so the player can move before the next serve.

The developer added a 0.7-second serve delay after each life loss so the ball rests on the paddle before the next serve. In demo mode the ball launches immediately without waiting. When the ball drops below the screen, the game either restarts the demo or decrements a life.

The game over overlay will display the player's remaining lives and include a kicker line beneath the title. The initial start message will remain visible until the user begins playing.

The HTML overlay text will stay in place until the game ends. A pause feature is being skipped. The HUD will also display the current level.

The developer will render the lives counter as hamburger emojis. They will also add a CSS pop animation to the score display that restarts on every point, using a forced reflow to trigger the scale effect.

The hamburger paddle will be drawn at 104 by 46 pixels, with each ingredient layer sized to fit. To keep the burger readable, the minimum paddle dimensions are raised to 112 by 52 pixels.

The developer finalized the hamburger paddle dimensions. On desktop it will be 200 by 72 pixels, capped to keep the burger from swallowing the playfield. On mobile it will be 118 by 54 pixels, which is about 31 percent of the screen width. The burger will be drawn at these exact proportions.

The hamburger will be outlined in dark ink and cast a subtle shadow on the counter for readability. Ketchup drips and olive eyes were considered but rejected to keep the paddle looking like a plain burger. The drawing function is complete and ready for final code.

The 3-second comparison screenshot will show a heart-strewn field because the ball hits bricks quickly and the explosion burst lasts long enough. If the ball falls through gaps, the AI will relaunch it back under the bricks.

The ball will sit 8 pixels from the side walls and 12 pixels from the top and bottom so it can travel through the gutters. The AI will keep the paddle visible below the bouncing ball to maintain the self-playing effect. The ball may skim horizontally through bricks, which is acceptable.

The developer will nudge the ball upward if its vertical velocity drops below 80 pixels per second to prevent it from skimming sideways through the bricks. Horizontal speed will also be capped at 92 percent of the ball's total speed so it cannot stray too far sideways.

The developer will compose the HTML next. A tiny CSS burger icon will sit in the header to reinforce the theme. Lives will be drawn as mini hamburger shapes on the canvas as a fallback if emoji fonts are missing.

The developer will render the lives counter as inline SVG hamburgers instead of emoji. This avoids tofu rendering if the font lacks the hamburger character. The SVG markup is generated by a small helper and injected directly into the lives element.

The SVG hamburger will include sesame seeds as small white ellipses. CSS will size the inline SVGs and align them vertically in the lives counter. The hero overlay will also render a larger SVG burger directly in the HTML so it appears on first paint without JavaScript.

The developer is adding a `roundRect` helper that clamps corner radius to prevent canvas errors when width or height is zero. They are also doing a final syntax pass before writing the complete code.

The developer decided against adding a floating "SMASH!" text effect to avoid covering the start message. Small +100 point popups will appear near bricks instead, accepting that they may overlap with the brick field.

Combo counters will sit on the left side of the playfield so they do not hide under the overlay. The developer will not add a README file.

The developer will use a CSS class instead of a `hidden` attribute for the overlay. They will avoid the `void` operator and use optional chaining sparingly. A guard will be added to the draw function to skip rendering when width is zero. Particle physics will use delta time in seconds for position, velocity, gravity, rotation, and decay. Heart particles will rotate at roughly one full turn per second.

Heart particles will fade out by their remaining life, and any particle whose life drops below zero will be removed. Ring particles will expand outward while slowly losing opacity. The developer has finished refining all the physics and effects and is ready to write the complete code.

A "ORDER UP!" banner will appear at 40 percent height for 1.3 seconds when a level clears. It is hidden during the demo overlay and shown only during active play.

A "YOU'RE UP!" banner will appear when the player takes control. The demo AI will wait briefly before moving so the paddle slides visibly. The developer will avoid eval, document.write, and inline SVG injection.

The hero SVG will be 92 by 70 pixels with a dark drop-shadow and a pink glow behind it. Brick colors will be stored in a constant array outside the functions.

The developer is writing the `layoutBricks` function. It calculates grid dimensions based on viewport width and height, using responsive breakpoints for columns, rows, gaps, and margins. Brick colors cycle through a fixed array using modulo so they stay consistent when the grid is rebuilt.

The layout function recalculates brick dimensions and rebuilds the grid only when the column or row count changes. It assigns each brick a color from the fixed palette and positions every brick in its grid cell.

The developer will skip brick layout when the viewport is smaller than 50 by 50 pixels. Ball speed will scale proportionally to the smaller screen dimension so the time to cross the playfield stays consistent on mobile and desktop.

Ball speed will scale with viewport width using a formula capped at 1.1, giving a base speed of 520 pixels per second multiplied by the scale and a level multiplier that rises by 0.05 each level up to 1.4. The developer decided against mouse paddle smoothing to keep the paddle directly responsive.

Pointer events will handle mobile input: pointerdown starts the game and pointermove drags the paddle. Touch scrolling will be disabled. Ambient drifting hearts will appear on boot so the playfield is already heart-strewn before the ball hits.

The first paint will show the static scene before the ball ever collides. The three-second screenshot will capture the heart explosion instead.

Ambient hearts will be seeded once when the game is ready and only repositioned on resize to avoid jumping. Each heart starts with random size, velocity, rotation, and a soft pink or orange tint. The update loop will advance their positions using delta time.

Ambient hearts will wrap around the viewport edges so they never leave the playfield. They will be drawn behind the bricks at a lower alpha to avoid cluttering the scene.

Explosion hearts will draw on top of the burger and ball so they feel like they are falling through the playfield. The full draw order is background, ambient hearts, bricks, counter, paddle, ball trail and ball, explosion hearts and rings, popups and banner, then the HTML overlay. Hearts landing on the burger during explosions is intentional and cute.

Gravity will make the hearts fall across the entire playfield. By the three-second mark the screen should be packed with falling hearts. Some particles will linger for the full three seconds so the scene stays full even if the ball pauses briefly. The effect is ready.

The developer will add white heart particles to the palette so they stand out against the dark background. The playfield will use a deep berry and chocolate background. The burger will receive a dark outline so its light bun and lettuce colors remain visible on the wood counter. The final code will be kept under seven hundred lines.

The canvas will receive an `aria-label` of "Breakout game". Collision substeps will use the substep interval `h` for the previous position check instead of the full delta time.

The developer verified that storing the ball's previous position before each substep keeps brick collision checks accurate even if wall or paddle collisions occur in the same step. They also added a push-out step after reflection so the ball exits the brick's bounds and cannot re-hit it in the next substep.

Only one brick breaks per substep to prevent re-hits. Overlapping bricks are handled in subsequent substeps. To avoid corner trapping, the ball is pushed more aggressively along the reflection normal and the dominant velocity axis flips only once.

The developer switched to a simpler circle-versus-AABB collision check for bricks. It computes the minimum translation vector to push the ball out of the brick's bounds, then reflects only the velocity component along that axis if the ball is moving toward the brick.

When the ball is centered inside a brick, the code checks the previous position to decide whether to flip the horizontal or vertical velocity and push the ball out of the brick's bounds.

A small random horizontal nudge will be added to the ball's velocity whenever it hits a brick, preventing it from getting stuck in a loop between two bricks.

The random horizontal nudge will be applied in the collision handler right after reflection. The paddle collision will stay a simple top-plane check rather than a full circle-versus-rectangle test, since the ball should only hit the paddle from above.

The paddle collision now uses a simple top-plane check. It rejects hits that come from below the paddle and checks horizontal bounds with a small margin. The hit position is mapped to a -1 to 1 range. The ball speed is clamped between 420 and 700 pixels per second. The horizontal velocity is set to a scaled fraction of that speed. The vertical velocity is calculated from the remaining speed so the ball always bounces upward.

The developer added a squash-and-stretch effect to the burger paddle. It compresses horizontally when the ball hits and stores the previous paddle position to calculate tilt from velocity. The tilt is clamped to a small range so the burger never over-deforms. A slight horizontal margin on the paddle collision check ensures edge hits still register.

The burger will rotate around its center during collisions. Sesame animation will run independently of the squash-and-stretch tilt. The developer is ready to write the final file. No favicon or color-scheme meta tag will be added. The overlay card will use CSS-only heart characters for the first-paint effect.

The overlay title will read "♥ AUTO-PLAY DEMO ♥" with hearts in pink. The developer decided against extra canvas decorations like a ketchup bottle to keep the scene clean; the burger paddle is the main visual focus.

The burger will be drawn as nine reliable shapes: a shadow ellipse, bottom bun, patty with three dark grill lines, cheese with three triangle drips, lettuce as a wavy filled arc series, tomato with two seed dots, top bun dome, five sesame seeds, and a highlight. The developer is now coding the lettuce arc path.

The developer is now coding the lettuce arc path. They are using quadratic Bézier curves to create the wavy filled shape. They worry the result might look messy.

The developer abandoned the messy quadratic Bézier lettuce path. A simpler green ellipse with a darker zigzag stroke will replace it. Next up is the cheese drip drawing.

The cheese is drawn as a yellow rounded rectangle with three triangular drips. The developer is now coding the top bun dome.

The top bun dome is drawn with two quadratic Bézier curves. The bottom bun is a rounded rectangle. The developer is now mapping the remaining burger layers—patty, cheese, and tomato—to their exact vertical positions.

The burger layers are now mapped to exact vertical fractions. Lettuce spans 0.36 to 0.50, cheese 0.46 to 0.70, patty 0.52 to 0.70, and bottom bun 0.64 to 0.98. Because the layers overlap, the draw order must be bottom-to-top so the upper bun covers the lower ingredients. The developer is now listing the exact draw sequence from bottom bun upward.

The developer refined the burger layer heights so the top bun ends at 0.32 and the tomato spans 0.28 to 0.44. This leaves about 8 pixels of tomato visible peeking below the bun edge. The remaining layers are now mapped to exact vertical fractions from bottom bun upward.

The developer decided against adding an onion strip to keep the burger readable at small sizes. They will draw a thin dark stroke around the top and bottom buns for definition, and add a highlight on the top bun. They are now writing the final file and have started adding a plate behind the burger.

The plate is drawn as a white ellipse with a gray inner rim. It sits just below the paddle, leaving a 14-pixel margin so it stays fully inside the canvas.

The bottom margin is set to 16 pixels. The collision box will match the plate width so hits on the plate edge register. The visual burger is inset within the wider plate.

The developer will combine the plate and burger into a single `drawPaddle()` function. The burger will be inset by 12% of the paddle width so it sits within the plate. They decided against adding a label under the plate and are ready to write the code.

The developer wants the demo to stand out visually. They will use a red and white awning header, gold score plaques, a deep wine background, rainbow bricks with heart icons, a giant hamburger paddle on a plate, a heart cascade, and a center card with the exact start text and a burger icon. A subtle scanline or grain was considered but skipped as too dirty. A soft vignette will be added at the end of the draw function.

The developer decided to skip the vignette so the heart explosions stay bright and eye-catching. They will add a level counter and a small instruction at the bottom-left of the canvas during play, and the overlay hint will read "Demo in progress" while the demo is active.

The developer is placing the hint text in a thin HTML bar above the canvas so it does not cover the hamburger plate. During active play, the hint will read "Mouse, touch, or ← →".

The developer abandoned the separate HTML hint bar because it would steal space on small screens. Instead, the hint will be placed inside the header subtitle area, dynamically changing its text to "Click or press a key to start".

The developer decided to skip the separate HTML hint pill entirely to keep the canvas clean. The overlay card will instead display both the start message and the controls instructions directly.

The paddle will sit 12 pixels above the bottom edge so the burger remains large and centered for screenshots. The overlay card will use CSS transforms only to avoid overflow. The developer noted a possible height issue with the iframe body.

The developer is adding CSS height rules to the root elements and app container. They are avoiding `100vh` because it can reference the browser viewport instead of the iframe height, which would overflow and create scrollbars.

The developer finalized the CSS layout. They will use `height: 100%` on the root elements and `#app` with flexbox, avoiding `100vh` to prevent iframe overflow. The canvas will be absolutely positioned to fill the stage. They also added `max-width: 100%` to images, SVGs, and the canvas to prevent clipping.

The developer removed the global `max-width: 100%` rule for SVGs to prevent the header lives icon from shrinking oddly inside narrow containers. They are also checking whether the header stats—title, score, and three burger icons—will overflow on a 375-pixel-wide screen.

The header stats fit comfortably within 375 pixels. The subtitle will be hidden on small screens and the title shrunk to 16 pixels under 400 pixels. The awning stripes will render cleanly at that width. A thick dark border will separate the header from the game area.

The developer chose a simple 4-pixel gold bottom border for the header instead of a box-shadow. They are now writing the complete HTML file in one pass.

The particle system will remove faded particles first when the limit is reached. Brick destruction will spawn from the brick center. Score will scale with a combo multiplier starting at 100, and the earned bonus will appear as a popup above the paddle.

The combo multiplier will display only after the second hit, showing "2× COMBO". A 1.6-second timer resets on every hit and wipes the streak when it expires. The developer will also update the best score.

The developer decided against adding a score milestone confetti effect to keep the code lean. They are now writing the complete HTML file and will carefully check for syntax errors. The response will be formatted as a single fenced code block. They also added a small safeguard so the ball does not spawn inside a brick on the first frame.

The boot sequence is now fixed: resize first, then seed ambient particles only if the list is empty, then launch the ball, then update the HUD, and finally start the animation loop. Resizing will not relaunch the ball or reseed ambient particles. If the ball is already in play, its velocity is preserved and it is clamped to the new bounds.

A window resize listener will be added to handle canvas resizing. Pointer move and down events will use passive listeners to avoid performance warnings, since they do not call preventDefault.

The developer will use `touch-action: none` in CSS to block scrolling without calling `preventDefault`, which keeps pointer events passive and avoids console warnings. A window `keydown` listener will handle arrow keys, and the game will start on the first pointerdown click.

The ball will be a glossy cherry-red sphere with a tiny heart inside, clearly separate from the explosion hearts. A radial gradient behind the ball will create the glow instead of shadowBlur, avoiding performance issues.

The ball glow will be drawn as a large radial gradient circle behind the sphere. A short trail of small heart particles will follow the ball at roughly 0.045-second intervals. The ball will be placed just above the paddle area so it is visible on the first frame.

The combo popup will not overlap the start card because it appears at the top of the bricks while the card sits in the center. The "YOU'RE UP!" banner will only appear after the first click. The overlay scrim will be kept at 0.28 opacity so the game remains visible around the HTML card, and the card itself will stay opaque so the message stays readable even as hearts fly overhead.

Hearts will be visible around the overlay card, keeping the message readable while hearts fly overhead. Each brick will spawn 46 hearts, and a 200-heart cap will keep the particle system performant.

The heart cap was lowered to 320 particles, with each brick spawning 36 hearts. The developer is also considering batching heart drawing to keep the frame rate smooth.

The page title will be "Heartbreak Burger". The overlay will display the exact text "Click or press a key to start". Heart highlights will be skipped for particles smaller than 4.5 pixels to save drawing work.

The overlay title will use a large, high-contrast cream font on the dark card with a gold border. The stage will show a pointer cursor before the game starts and hide the cursor during play so the burger paddle acts as the pointer.

The developer decided to keep the default cursor visible during play instead of hiding it, since the burger paddle already tracks the mouse. They will also avoid adding any console.log statements to keep the console clean.

The developer is adding defensive checks to prevent NaN and negative square roots in the physics loop. They will validate ball coordinates each frame and avoid wrapping the main animation loop in try/catch so any bugs remain visible.

A wood grain counter will sit behind the burger paddle, spanning the bottom 70 pixels so the burger appears grounded on the surface. The developer is now finalizing the complete source file.

The developer finalized the color palette with hex values for the wine, berry, gold, cream, bun, patty, and other burger ingredients. Bricks will render with a subtle inner shadow by drawing a darker bottom rectangle over the full rounded body, creating a 3D effect. A `drawBrickBody` helper function will handle this layered drawing.

Breaking bricks will scale up and fade out over 0.28 seconds before vanishing. The game will check for remaining live bricks to trigger a level-clear sequence, and a single clearing flag will prevent duplicate timers.

The level-clear sequence advances the level, increases the speed multiplier, awards 500 points, and spawns a heart burst at the top center. It then shows the "ORDER UP!" banner and relaunches the ball so it does not get stuck inside the newly generated bricks. Brick collision is skipped during the clear timer because all bricks are already marked as gone.

The ball will keep bouncing off the walls during the level-clear timer. The banner will sit at 62% height so it does not hide behind the overlay card.

The "ORDER UP!" banner will sit at 48% height with a gold stroke and dark red fill. It fades in over 0.5 seconds and stays visible until the timer expires.

The banner font will scale from 22 to 54 pixels based on screen width, using Impact with a fallback stack. The developer will set `ctx.lineJoin = 'round'` for smoother stroke edges. Tiny heart icons will be added around the HTML overlay card using CSS box-shadow.

Eight tiny hearts will float around the overlay card corners using CSS animations. Each heart will cast a dark red text-shadow and rise and fall by ten pixels. They will be sized between twelve and eighteen pixels and placed at the top-left, top-right, and bottom-left corners so they do not overlap the card.

The HTML heart icons will be placed inside the overlay container so they disappear when the game starts. Canvas heart explosions will continue to fly overhead after the overlay is gone.

The overlay scrim is being reduced to 0.18 so the hamburger and bricks stay vivid. The card background will be 92% opaque dark red for strong text contrast.

The developer will store the life SVG inside a `<template>` element in the HTML. JavaScript will clone that template node for each life instead of using `innerHTML`, which avoids parsing issues and keeps the code cleaner.

The overlay SVG will be duplicated inline so it renders immediately on first paint, since the template node would not be available before JavaScript clones it. Brick hits will also trigger a white ring that expands outward and fades.

Rings will be capped at 40 and drawn as thin strokes with a tinted color. The score popup will simply update the text and rely on CSS animation for feedback, skipping reflow tricks to avoid layout thrash.

The ball will keep updating during the level-clear wait so it does not freeze, then relaunch once the timer expires. If the game is already over, the ball will not trigger another life loss. The update loop will process particles, ambient effects, and rings before handling the ball.

The update loop now handles the serve wait by keeping the ball frozen above the paddle until the countdown finishes. After that it splits each frame into three physics substeps for smoother movement, then stabilizes the ball and adds a short trail every 0.04 seconds.

The ball trail now adds a heart every 0.04 seconds. A missed ball drops below the stage and either respawns in demo mode or triggers a life loss. The level-clear timer decrements each frame and advances to the next level once it expires. The developer added a rare edge case where the last brick and the ball could both fall in the same frame, but the mode flag prevents an accidental level skip. The paddle movement logic now handles both AI demo mode and keyboard input, using a speed scale factor.

The developer refined the paddle movement so pointer control follows the cursor with a smooth exponential decay. The follow rate was raised to roughly 22 times the frame delta, making the burger snap almost instantly to the mouse.

The AI demo mode now uses a 900-pixel-per-second paddle speed. It predicts the ball's future intercept point and clamps movement to that limit. The prediction function guards against slow or stationary balls and respects the 8-pixel wall padding.

The AI prediction loop now simulates twelve bounces. It checks the ball's intercept with the paddle, bounces off the walls, and returns the final x position.

The AI will add a slight lead to the paddle so its movement looks intentional rather than robotic. The developer also began writing a `collideWalls` function to handle ball collisions with the stage edges.

The wall collision logic now clamps the ball to the stage edges and reverses its velocity on contact. A `stabilizeBall` function enforces a minimum and maximum speed on the ball, scaling its velocity vector to keep movement within the target range.

The ball's horizontal speed is now clamped to 86% of the total speed limit to keep movement balanced. The ball radius will scale with screen width between 6.5 and 10 pixels. The developer is ready to finalize the CSS and JavaScript, and decided against adding a mustard stripe to the bricks since the existing 3D effect is sufficient.

The header brand will include a small SVG burger logo next to the title so the hamburger renders on first paint. To avoid duplicating the same SVG paths across the header, overlay hero, and canvas paddle, the developer will define one inline SVG sprite and reference it with `<use>` tags.

The inline SVG symbol will be defined once with a 64 by 48 viewBox and referenced via `href="#burger"` in the header logo, overlay hero, and life icons. This avoids external requests and ensures the hamburger renders immediately on first paint.

The SVG sprite container will be hidden with absolute positioning and zero width and height so it does not collapse. The `renderLives` function will create SVG elements for each remaining life using the inline burger symbol.

The inline SVG burger symbol is being designed with a 64 by 48 viewBox so it stays recognizable at both 26 and 90 pixels. It uses an orange ellipse for the bun, a brown rectangle for the patty, and yellow and green paths for the lettuce and tomato slices.

The SVG burger symbol is being simplified by replacing the complex lettuce path with a green ellipse for better reliability at small sizes.

The SVG symbol will layer elements in document order so the wider lettuce ellipse peeks out from the sides of the tomato. The developer is now ready to write the complete file and will add `lang="en"` to the HTML.

The developer is adding CSS rules to prevent SVG `use` elements from overflowing their containers. The hero burger will scale with a viewport-relative width and a soft drop shadow. The lives bar will use a flex layout with a 3-pixel gap and 26 by 20 pixel icons. A hover state was considered but skipped. The file is expected to be 800 to 1000 lines. The start message is being checked to ensure it does not get cut off on small screens.

The developer realized the start message in Impact font is too wide for small screens. At 375 pixels the text can reach roughly 450 pixels, which risks horizontal overflow.

The developer will clamp the overlay title font size to 16 to 32 pixels and constrain the card width to 440 pixels. This forces the start message to wrap onto multiple lines and prevents horizontal overflow on small screens.

Impact is a condensed font, so the start message is narrower than expected. The card width is being increased to 460 pixels and the title font size clamped to 17 to 32 pixels. The body will hide horizontal overflow and the title will wrap normally.

The header title will use a viewport-relative font size clamped between 16 and 30 pixels so it stays compact on small screens. The brand logo will also shrink to fit. Both axes will be locked to prevent any horizontal scrollbar.

The flex layout locks the header and stage to exactly 100% viewport height, preventing vertical overflow. The app container will also hide overflow. The developer decided against adding `will-change` to avoid rendering issues. When a brick breaks, the explosion will emit many colorful hearts with heavily varied sizes. Star-shaped sparkles were considered but set aside to keep the effect strictly heart-themed.

The developer will add a squash-and-tilt effect to the paddle drawing. The burger will rotate slightly based on horizontal velocity and flatten when the ball impacts it. The `drawBurger` function will accept the paddle rectangle coordinates directly.

The developer is ensuring the sandbox demo will run long enough for the three-second screenshot. They are adding a timestamp-based simulation that caps frame delta at 0.034 seconds, preventing the game from freezing if the browser throttles animation frames.

The developer will cap frame delta at 0.034 seconds and use substeps to prevent physics tunneling during long frames. The boot sequence will paint the scene immediately so the first frame shows intact bricks, and the ball will start moving right away. No pre-simulation is needed, since the three-second screenshot will capture the destruction state.

The developer decided against adding a setInterval fallback and will rely solely on requestAnimationFrame. To make the hamburger feel alive before any bricks are broken, they are adding a small steam effect that emits animated white puffs from the burger. This ensures the screenshot shows movement and life even before the first heart explosion.

The steam effect uses soft white circles that rise from the paddle. Each puff drifts sideways and fades out over time. They are drawn above the burger so the hamburger stays visible.

The developer will seed five steam puffs during boot so the hamburger shows rising steam immediately on first paint, rather than waiting for the random generator to trigger. The positions will be calculated relative to the paddle's final position after fitting. The game implementation is now considered complete.

The developer will add "LIVES" labels to the lives display and use createElementNS for the SVG icons. The level counter will move to a corner of the canvas to keep the header compact, while score, best, and lives remain in the header.

The level counter will move to the header to avoid overlapping the top bricks. The header will place the logo and title on the left and score, best, and lives on the right. Best will appear smaller beneath the score.

The header will use a single column for score, best, and lives. The best score will appear in small pink text beneath the score. The level counter will be drawn at the top-right corner of the canvas inside the frame. The brick grid will start 28 pixels below the top edge to leave a 22-pixel HUD strip.

The canvas HUD will show the level number on the right and a combo multiplier or the title on the left. The multiplier turns gold when active and fades to gray otherwise.

The developer decided to remove the static "SMASH THE ORDERS" text from the canvas HUD to reduce clutter. The top-left corner will only display the active combo multiplier, while the level number stays on the right. This keeps the playfield cleaner and avoids text wrapping on small screens.

The top brick offset is set to 26 pixels. A faint gold line will be drawn across the canvas just below the HUD strip.

The developer is now writing the complete HTML document. The hamburger drawing function will receive valid numeric coordinates. The burger will include six sesame seeds, short rounded grill marks on the patty, and simple triangular cheese drips. A dark ellipse will sit beneath the plate as a drop shadow, drawn before the plate so it survives the squash-and-tilt transform without clipping.

The developer is now writing the complete single HTML file. They confirmed `replaceChildren` is safe to use for clearing the lives display. A visibility event listener was skipped. A note about the demo will be added to the overlay subtitle. The document structure is being finalized with the viewport meta tag and title.

The HTML structure is now fully defined. The document contains an SVG sprite, an app container, a header, a stage with a canvas, and an overlay card. The overlay card displays the hero SVG, a kicker line, a title, and a subtitle. The hero SVG references the sprite via `use href`.

The developer will inline the overlay burger and header logo as direct SVG shapes instead of referencing the sprite. This avoids any risk of the `use` element failing to paint in the sandbox.

The developer will store the SVG heart icon as a JavaScript string constant. The lives display will be built by repeating that string with `String.repeat` and injecting it via `innerHTML`. This avoids any risk of the `use` element failing to paint.

The developer realized the script is blocking and runs to completion before the browser paints. This means a single SVG string constant can be injected into the header logo and overlay card during boot, so no separate HTML copies are needed.

The developer will still embed the overlay burger SVG and start message directly in the HTML. This guarantees the overlay text is visible even if the boot script fails before it runs.

The developer noticed the SVG path for the top bun uses lowercase `s` commands, which are relative smooth cubic curves. This might produce an incorrect shape, so they are pausing to verify the path data before finalizing the burger design.

The developer abandoned the complex SVG path commands after noticing the relative smooth curves might render incorrectly. They switched to simple geometric shapes—ellipses and rectangles—stacked to form the hamburger. This avoids path bugs entirely and still produces a readable burger icon.

The hamburger SVG is now fully defined with simple geometric shapes. It includes a cheese slice, lettuce, tomato, and top bun drawn as absolute paths. Sesame seeds are placed above the bun. The developer is verifying the draw order so the top bun renders last and covers the tomato.

The canvas hamburger will be large and detailed, centered at the bottom on the first frame. The SVG icon stays simple. The ball will start above the paddle heading toward the bricks.

The developer will offset the paddle slightly left of the ball at launch so the hamburger looks like it is already in motion. The ball starts at 45 percent of the canvas width, and the paddle will be positioned to match. This creates a dynamic first frame before the ball is even released.

The simulation will be pre-stepped for about two seconds during boot. This ensures the first paint already shows heart explosions, and by the three-second screenshot the demo will be further along. A dynamic first frame is more meaningful than a static board.

The developer settled on a 1.35-second pre-simulation during boot. This produces roughly 80 physics updates and leaves many bricks intact, so the first paint already shows heart explosions mid-air. The simulation continues naturally until the three-second screenshot.

The simulation will run after launchBall, seedAmbient, and seedSteam during boot. Heart particles will be given a long enough lifetime so the pre-simulated sky stays full of falling hearts.

Heart decay is set to 0.42–0.77 seconds so particles survive the full 1.35-second pre-simulation and remain visible at the three-second screenshot. The developer is now weighing whether to simulate 1.2 or 1.35 seconds, since more broken bricks will spawn more hearts and make the demo more dramatic.

The developer decided to pre-simulate 1.5 seconds during boot. This should break roughly 7 to 8 bricks out of 66, leaving enough intact to preserve the breakout feel. The resulting heart explosion will be dense enough to look dramatic on the first paint.

Heart particles will be mixed in size so they read as hearts at screenshot resolution. Most will be 6–11 pixels, with a few 3–5 pixel ones for the "little" effect. The developer is now writing the random size roll.

The developer settled on a uniform 4–12 pixel size for every heart particle. This keeps the explosion dense and readable without mixing multiple scales. Each broken brick will spawn 40 hearts.

The developer is now writing the actual code. They will add a wood counter color so the burger pops. They reviewed the draw order and placed heart particles above the ball so they are the spectacle.

The developer reversed the draw order so the hamburger stays visible. Hearts and rings will render first, then the paddle and ball on top. This prevents the explosion from covering the key burger icon.

Hearts will be split into two layers. Thirty-five percent of them will be flagged as front-layer particles. The draw order will be back hearts, then the paddle and ball, then the front hearts. This keeps the hamburger clearly visible while giving the explosion a sense of depth.

Popups will render above all other layers. A maximum particle age will be added to the update loop to prevent particles from persisting indefinitely. The pre-simulated boot sequence satisfies the requirement for a self-playing demo mode behind the start prompt.

The AI will move the paddle during the pre-simulated boot so the first paint shows an active, in-progress game rather than a static centered paddle. The ball will be launched near the bricks so the simulation immediately registers hits. The boot function is being drafted to initialize the game state and begin the demo sequence.

The boot function now runs 90 physics steps at 60fps during the pre-simulation. It clamps the paddle to the ball’s x position and seeds ambient particles before entering the main loop. A ready flag prevents double initialization if the canvas size is zero on the first attempt.

The developer confirmed that boot and loop will not double-draw the first frame. They also verified that resize will not reset the brick grid during the pre-simulation, since layoutBricks only runs at startup and the simulation does not trigger resize events.

The developer drafted `initGame` to reset lives, score, level, and combo while preserving the best score. They also began a `resetForPlayer` helper for player restarts.

The developer decided to let the player join the demo mid-action rather than resetting the board. The game will simply switch to play mode, so the player inherits the pre-simulated damage and continues from where the AI left off. This feels more exciting than starting with a fully intact grid.

The developer added a `beginFromUser` function to switch the game from demo or over mode into play mode. It hides the start overlay and shows a "YOU'RE UP!" banner. If the player joins after the demo has already broken about 25 bricks, roughly 40 remain, which is still a fun challenge.

The developer is refining the keyboard input handler. When a player presses a movement or space key, the game switches from demo to play mode. They noticed that `beginFromUser` sets `pointerCtl` to true, which would make the paddle jump to the cursor on the next frame. They need to ensure keyboard input does not trigger that pointer jump.

The developer finalized `beginFromUser` so keyboard input passes `false` and pointer input passes `true` for `pointerCtl`. They will also set `pointerX` on pointerdown so the paddle tracks the cursor. The design is complete and they are now writing the full code.

The developer decided against adding extra HTML heart decorations in the overlay corners, since the kicker already contains ♥ characters and too many would clutter the message. They will also enable devicePixelRatio scaling in the resize function to keep the hamburger looking sharp on high-DPI displays.

The developer capped the device pixel ratio at 2x for sharper rendering. Brick positions will be rounded to whole pixels to avoid blurry lines. The layout is finalized and the file is being generated.

The ball will be ketchup red. The wood counter will sit just above the fixed paddle height so the burger icon rests on it. Five static bokeh circles will be drawn in the background using scaled canvas coordinates.

The developer skipped a dotted noise pattern because the design is already distinctive enough. They are now adding a scalloped-bottom awning to the header using CSS.

The developer abandoned the radial-gradient scallop for the header awning. A simple repeating-linear-gradient stripe pattern is cleaner and more reliable.

Plaques will sit on the header awning to complete the diner look. A small "EST. 1954" or "OPEN" sign was considered but skipped to avoid clutter. The overlay title font will use a condensed stack with a clamped size so the text wraps cleanly and stays readable.

The overlay title font was switched from Arial Black to Trebuchet MS or Segoe UI bold. This keeps the text on one line at desktop size for maximum impact. The clamped font size is now 18px to 28px.

The overlay title will use extra-bold Trebuchet or Segoe UI with a slight negative letter spacing. A gold border-bottom underline will be added to the title to make it stand out.

The overlay title will use a simple block display with no border-bottom underline. The card border and cream-on-dark contrast are sufficient. A slight text shadow will improve readability. The developer is now writing the file and has dropped the text-wrap property to keep the CSS simple.

A CSS rule will lock mobile text size to prevent overflow on small screens. The developer is now writing the code and has decided against a hexWithAlpha helper. They are also drafting a canvas heart path using bezier curves.

The heart path is now fully defined with bezier curves. The developer will scale the heart size so the particles render at roughly 8 to 16 pixels.

Hearts will be filled only, with no outline strokes, to keep them bright and clean. The developer is now writing the complete file and carefully counting brackets.

A lost-ball relaunch will keep the demo ball in play during simulation. The developer is adding a failsafe to prevent the ball from getting stuck inside a brick and bouncing forever.

The developer confirmed the ball cannot get trapped inside a brick gap because the gap is five pixels while the ball is sixteen. A watchdog will keep the ball inside the walls if it drifts outside. The physics and relaunch logic are now considered sound.

The AI paddle will be faster than the ball, so it can track the ball reliably. When the ball is inside the brick zone, the AI will follow the ball's x position directly instead of using prediction. This makes the demo look more natural and responsive.

The AI paddle will mirror the ball's x position while the ball is inside the brick zone, and switch to prediction once the ball drops below that zone. The developer is now writing the complete file and adding a header comment to the HTML title. They are also spending extra care on the hamburger paddle's canvas rendering so it reads clearly as a delicious burger rather than a crude rectangle.

The hamburger paddle will be drawn with a pronounced semicircular top bun dome, green lettuce, red tomato, yellow cheese, brown patty, bottom bun, and plate. The dome silhouette is the most critical element for readability at screenshot size.

The developer realized the bun radius was too large relative to the burger height, which would make the dome taller than the whole paddle. They capped the radius using the smaller of half the width and half the height so the dome stays proportional.

The developer refined the hamburger dome to 42% of the paddle height. The remaining 58% is reserved for the fillings and bottom bun. They are now writing the canvas code to draw the dome with a quadratic curve for a natural slope.

The developer refined the hamburger dome so its top edge sits exactly at the paddle's top edge, preventing the dome from spilling above the paddle box. They also tightened the dome radius to 40% of the paddle height and recalculated the quadratic curve for a natural slope.

The developer realized a semicircular bun would be too narrow for a wide, short hamburger paddle. They will draw the top bun as a flattened ellipse instead.

The developer finalized the top bun as a wide ellipse spanning nearly the full paddle width. The ellipse center is set at 30% of the height, giving the dome a natural wide silhouette without spilling above the paddle.

The developer is double-checking the canvas arc angle convention. They are verifying whether angles increase clockwise or counterclockwise, since canvas y-coordinates increase downward, to ensure the top bun ellipse draws correctly.

Canvas angles increase clockwise, so drawing from PI to 0 traces the top half of the ellipse. The hamburger dome will render correctly.

Sesame seeds are now positioned on the upper half of the bun dome. A lighter highlight ellipse will sit on the upper left of the bun. The full drawBurger function is being implemented next.

A ruffled green line is being added to the lettuce to suggest texture. The developer noted the lettuce will extend slightly past the burger edges to create a strong silhouette cue. A red tomato slice is being drawn beneath the bun, though it may overlap with the bun dome.

The developer is assigning exact vertical layers to each hamburger component. The top bun will span 0.00 to 0.40, the tomato 0.38 to 0.52, and the lettuce 0.44 to 0.56. The cheese will drip over the patty, and the plate will sit underneath everything as a wider base.

The remaining hamburger layers are now fixed: cheese at 0.50–0.62, patty at 0.56–0.74, bottom bun at 0.68–0.96, and the plate at 0.90. The developer will draw from bottom to top so the overlapping colors stack naturally. A dark outline will be added to the top and bottom buns for definition against the background.

The plate will be silver with a subtle shadow beneath it. Each broken brick will burst 42 heart particles sized 5 to 13 pixels in a tight circle. Some hearts will be white or gold to make them stand out.

A `safeEllipse` helper is being added to prevent canvas `IndexSizeError` when ellipse radii are too small. The hamburger dome and sesame seeds are well within safe bounds, but the guard will be included for robustness.

The `safeEllipse` helper is now complete. A `roundRect` guard will skip drawing when height is below one pixel. The game loop timestamp is initialized before the first animation frame to prevent a huge first delta. Hearts will be removed once they fall below the canvas or their life expires.

The developer is finalizing the script structure and will use tabular numerals for the score display.

The developer mapped out the full script structure: physics and AI logic, canvas draw helpers, input handlers, and the main loop. They decided to update the HUD only when the score, lives, or level change, rather than every frame. They also began writing the `updateHUD` function to refresh the display and track the best score.

The `updateHUD` function now updates the score, best score, and life icons. It only rebuilds the life SVGs when the count changes, avoiding unnecessary DOM updates. A `burgerIcon` string will be defined in JavaScript to match the hamburger icons used in the HTML.

The hamburger SVG will be duplicated in the HTML header, overlay, and initial lives section, with a matching copy kept in JavaScript for dynamic updates. The script will overwrite the life icons before the first paint, making JavaScript the single source of truth. The HTML fallback ensures the display still renders correctly if the script fails to run.

A safety burst will guarantee at least 30 heart particles are visible after simulation. If the collision fails to spawn enough, the script will force a celebratory burst from every brick so the explosion always appears in the screenshot.

The safety burst will randomly trigger on live bricks with a 30% chance, stopping once 80 particles are spawned. It only runs when physics fails to break enough bricks, so it never double-breaks a brick that was already destroyed.

The safety burst threshold is set to 20 particles so it does not clear the board if physics works but particles decay. The overlay card will use a pink-gold kicker, a light cream subtext, comfortable padding, and a pink box-shadow glow.

The stage background is set to a dark brown to prevent white flash if the canvas lags. The canvas will block touch gestures, the header will hide overflow to avoid horizontal scroll, and the stats bar will use flex-shrink to stay on one line.

The brand header will use flex alignment with an ellipsis overflow to prevent wrapping. The ball will be drawn with a glossy highlight, and a decorative ring will frame the playfield. The developer also verified that the level-clearing flag set inside `destroyBrick` will not interfere with the simulation loop.

The developer decided to set a level-clearing flag inside `destroyBrick` instead of calling `nextLevel` directly. This prevents re-entering the brick layout routine during the collision loop. The function also increments the combo counter, resets the combo timer, awards points scaled by combo, and updates the HUD.

destroyBrick now spawns 40 heart particles and a colored ring at the brick's center, displays a point popup, and shakes the screen. It also checks whether any bricks remain to set the level-clearing flag. A guard prevents re-entry if the brick is already breaking. Popups are capped at 24 entries.

Popup text will scale with the canvas size. The update function will clamp delta time to 0.05 seconds to prevent physics jumps. Heart particles will keep falling during the game-over screen so the explosion lingers. The developer is also guarding against repeated key presses from the user input handler before the game mode switches.

The developer confirmed that keydown and pointerdown events cannot overlap, so the game mode will switch safely without re-entry. They will attach listeners at the bottom of the script after all functions are defined, then call boot(). Pointer events will not be passive, and arrow keys and space will have preventDefault to stop scrolling.

A subtle red flash overlay will indicate when the player loses a life. The developer is now writing the complete HTML file and will ensure it is delivered as a single fenced code block without any extra explanation.

A tiny toothpick with a heart flag will be added to the hamburger paddle. It sits above the bun and makes the paddle unmistakably a burger.

The developer will use function declarations so `drawHeart` can be called before `drawBurger`. The toothpick will sit above the bun and draw after it so it remains visible. A second smaller sesame cluster will also be added. The code is now being written.

The heart flag on the toothpick will scale to about 11 pixels so it stays readable. The toothpick itself will be tilted. The developer confirmed that `drawHeart` uses `save` and `restore`, so it will not interfere with the hamburger paddle's transform or global alpha.

The developer will seed a few falling hearts around the hamburger immediately after simulation so the area looks alive on first paint. The heart spawn function will default to 44 particles per brick, spread across the brick’s surface to avoid clumping. A cap of 340 particles will prevent the screen from overflowing.

The developer will keep the destroy fallback instead of spawning hearts directly. Each heart will be launched in a circular spread with an upward bias so they fountain upward before gravity pulls them down, filling the screen vertically.

The heart explosion will use a weaker upward bias so particles scatter sideways and downward as well as up, filling the screen in all directions. Gravity will pull them down over time.

The heart color palette now includes white and gold tones so the explosion sparkles in the screenshot. Each heart will also spin slightly as it falls. The developer is finalizing the popup text format and ready to write the complete file.

The point popup will display plain text like '+100' instead of a heart symbol to keep the overlay compact. The developer is also writing a `setLives` helper that dynamically creates SVG hamburger elements in JavaScript to populate the lives display.

The developer is now writing the `setLives` helper that dynamically creates SVG hamburger elements in JavaScript to populate the lives display.

The developer switched to a simpler approach for the lives display. Instead of creating SVG elements in JavaScript, they will use a single inline SVG string repeated via `innerHTML` on a plain HTML div. This avoids parsing bugs and ensures the initial lives render correctly before the script runs. The same SVG string will be used in the HTML header and overlay for the initial state, while JavaScript will fill the lives container before the first paint. The developer is now ready to write the complete file.

A self-review will verify the code after writing. A mobile media query will shrink overlay padding, header spacing, and font sizes on screens under 420 pixels. The tagline will hide on very small screens, and the best-score line will stay under the score to avoid extra width.

A robust separated-axis collision check will be added for reliability. A `ballSpeed()` helper will compute the ball's velocity from speed scale and base multiplier. The demo launch will position the ball just below the lowest active brick using a `brickBottom()` helper.

The `launchBall` function now positions the ball just below the lowest active brick when starting inside the brick field, and places it above the paddle when launching from the paddle. It uses a fixed upward-right angle for the brick-field launch and a randomized angle for the paddle launch.

The demo launch angle of -1.05 radians is confirmed to send the ball upward and to the right. The ball’s starting Y position is clamped between the lowest active brick and the paddle to keep it inside the visible area.

The demo launch now alternates the ball’s horizontal direction on each relaunch so it does not always travel right. The ball’s starting Y position is clamped to stay above the paddle when the gap between bricks and paddle is small.

The ball’s horizontal velocity will alternate between left and right on each demo launch. A heart icon will sit on every brick, and when a brick breaks it will scale up and fade while hearts pour out. The break animation lasts 0.45 seconds so several bricks can still be mid-explosion during the simulation.

The break animation will use a 0.45-second timer. A white flash circle will appear at the break location for the first 0.2 seconds. The overlay title will be hardcoded in the HTML so the text is guaranteed to exist.

The stage element will carry `role="application"` and an `aria-label` so screen readers immediately announce the game title and start prompt. The initial overlay text will remain in the HTML and only change when the game over state updates it.

The developer is now writing the complete file. They verified the boot sequence order: resize creates bricks and positions the paddle before initGame runs, so launchBall and seedAmbient have valid dimensions. They will also reset the particle system at the start of initGame before the simulation begins.

The developer is now writing the `fitPaddle` function. It computes the hamburger paddle’s width and height from the viewport, locks the bottom edge, and centers the paddle horizontally on first placement. After that, it will use a stored relative position to keep the paddle in the same spot when the window is resized.

`fitPaddle` now uses a stored relative position after the first placement so the hamburger stays fixed during window resizes. `initGame` sets `_placed` to true immediately after positioning the paddle under the ball, ensuring the relative coordinate is captured before any resize events occur.

The developer will draw a bottom counter behind the paddle so it does not obscure the ball. The background will include a gradient, a thin gold top border on the counter, and a decorative frame. Scores will use `toLocaleString` only for values above 999 to keep the display readable.

The developer decided to use plain `String` for scores instead of `toLocaleString` to keep things simple. They will also make white hearts more frequent in the particle system so the explosion looks clearly distinct in the screenshot.

The developer will keep the heart drawing compact but readable, targeting roughly 700 to 900 lines. They verified the SVG heart path is not upside-down in canvas coordinates, since positive Y runs downward.

The developer is now drafting the `drawHeart` function using a classic canvas bezier path. They are verifying the control points so the heart shape renders correctly in the downward-positive Y coordinate system.

The developer is now drafting the symmetric heart path for the `drawHeart` function. They are using two bezier curves per lobe to ensure the heart shape is balanced and centered.

The developer abandoned the symmetric bezier heart path because its top center could pinch incorrectly at small sizes. They switched to a simpler two-circle-and-triangle approach that always reads as a heart even when tiny. They began drafting the `drawHeart` function using this shape.

The heart drawing now uses two overlapping circles for the upper lobes, a diamond-shaped bottom, and a small white highlight for larger hearts.

The heart drawing now uses a proven geometric construction: two overlapping circles for the upper lobes, a diamond-shaped bottom, and a small white highlight for larger hearts. The size parameter `s` will be set to values between 5 and 14, producing hearts roughly 7 to 18 pixels wide that remain readable at small sizes.

The heart will stay bright and flat for pop, with a left-lobe highlight. A guard will skip the white highlight when the heart is too small to avoid canvas errors.

Hearts smaller than 1.5 pixels will be skipped to avoid canvas errors. The developer is now writing the complete file and will not add any more features. The CSS will be organized into resets, app layout, header, brand and stats, stage and canvas, overlay card, animations, and media queries.

The overlay will include a keyboard hint: "Then steer with mouse, touch, or ← →". The stage element will receive `tabindex="0"` so clicks focus it correctly. The sandbox click and keyboard handlers are both wired, and a visible focus outline is not needed.

CSS will disable text selection and image dragging to keep the overlay clean. The simulation workload is small enough to run instantly in the sandbox.

A fallback heart-spawner will fire if the particle system is too sparse after the simulation. It will launch a red heart fountain at the center, plus orange and pink hearts on the left and right. This guarantees a dense explosion even when no bricks break, which helps the judging screenshot look full.

The fallback heart spawner only helps the first frame. If the collision bug is real, the hearts fade in two seconds and vanish before the three-second screenshot. Collision must work for the explosion to last long enough.

The developer chose a simple AABB collision check for each live brick. The ball’s radius is added to the brick’s bounds so the overlap test is reliable. On a hit, the brick will be destroyed and the ball will reflect based on which side has the least overlap.

The developer finalized the collision logic. It will use four substeps per frame. The ball moves only about two pixels per substep, so tunneling through bricks is impossible. The classic AABB overlap check is simple and hard to get wrong.

The developer is now writing the complete file. They will add a random horizontal nudge and a speed stabilizer to prevent the ball from getting stuck bouncing between two bricks.

The update loop now runs four substeps per frame, each checking walls, paddle, and bricks. The ball only moves while the serve wait is over. Paddle collision is limited to the top 35 percent of the hamburger and only triggers when the ball is falling.

The paddle collision now uses a speed stabilizer that caps the ball’s speed between its current value and 1.25 times the original, clamping the horizontal velocity to 85 percent of the target speed. The vertical velocity is derived from the remaining speed so the ball always bounces upward. The ball is also pushed slightly above the paddle hitbox to prevent it from getting stuck inside the hamburger. A squash effect is applied to the hamburger when the ball hits it.

The developer confirmed the 18-pixel paddle hitbox catches the ball every substep, preventing it from falling through the hamburger. They will also add a toothpick to the burger drawing and produce the final file in a clean, comment-free style.

The overlay card height is roughly 194 pixels, fitting comfortably within the 375-pixel width. Overflow auto was rejected to avoid scrollbars. The card may overlap on very short viewports, but the task only specifies the width.

A media query will shrink the hero burger to 64 pixels and tighten the overlay card padding when the viewport height drops below 560 pixels. Floating HTML hearts will be added to the overlay edges for extra polish, positioned so they do not cover the text.

The developer decided to skip the HTML edge hearts to keep the overlay clean, since the canvas particle system is the main visual feature. They are now finalizing the complete list of functions to ensure every call matches its definition exactly.

All functions are hoisted declarations so call order does not matter. The const state block sits at the top.

The developer confirmed the canvas and context are available at the top of the script. They added a guard to return early if getContext fails, ensuring the HTML overlay still renders instead of throwing an error.

The developer decided not to add a canvas-level text indicator, since the overlay already provides the required message. They will now finalize the CSS and JavaScript into a single complete file.

The header burger SVG will be 48 by 36 pixels. A CSS drop shadow will be added to the logo. The ball will be a glossy red orb with a white heart, and a thin white ring will be drawn around it for definition.

The developer added a painted wall frame around the canvas using two layered strokes. The ball margin sits inside this frame. They are also considering darkening the area outside the frame.

The HUD text will sit at y=12 so the painted wall frame does not overlap it. Brick rows will start at y=32 to leave a clear gap. Combo text will appear at the left edge near y=10. The developer is now writing the complete file.

The header score will update during the demo so the judging screenshot shows a non-zero value. The best score will match the demo score. A subtle screen shake will add visual feedback without obscuring text.

The HTML overlay text will stay still during the screen shake so the required message remains readable. A CSS bob animation will be added to the kicker so the card pulses slightly during the demo.

A glow animation will pulse the card border between deep red and golden yellow. The parent container is set to overflow hidden so the card’s vertical bob does not create scrollbars. The developer is now writing the final code and will include a detailed drawPaddle function.

The developer is now drafting the exact `drawPaddle` function. It will tilt the hamburger based on paddle velocity, squash it when hit, and draw a darkened plate ellipse at the bottom of the bun.

The hamburger drawing is now complete. The bottom bun uses a golden gradient with a subtle stroke. The patty is dark brown with four small grilled marks.

The hamburger drawing now includes a yellow cheese slice and a green lettuce leaf.

The hamburger drawing now includes a green lettuce leaf with a hand-drawn stem and a red tomato slice.

The developer is now drawing the top bun of the hamburger. It uses a warm gradient from cream to gold and is shaped as a rounded ellipse with a flat bottom edge.

The top bun is now complete with a soft stroke and a white highlight. Sesame seeds are being drawn across the bun surface.

The hamburger drawing is now complete. It includes a toothpick and heart, and the developer verified that ellipse radii stay above one pixel even at the narrowest paddle width.

The developer is adding a `drawSteam` function that renders small white circles fading out over time. They are deciding whether to draw the steam before or after the paddle so it appears above the burger.

The developer decided to draw steam after the paddle so it rises from the burger. They will seed a few steam particles at game start and cap the total at 24. Ambient hearts will be faintly drawn behind bricks. A `drawBrick` helper will skip gone bricks and fade breaking ones. The popup and banner will also be added.

The `drawBrick` function now scales breaking bricks up by 35 percent and fades them out with `globalAlpha`. Each brick is drawn as a rounded rectangle with a dark shade fill, a lighter fill, and a small highlight.

A small white heart icon is drawn on each brick, scaling with the brick during the break animation. The heart size is capped at 32 percent of the brick’s smaller dimension, so it remains a visible icon even when the brick is tiny. The developer is also checking whether the heart function’s internal alpha value multiplies with the current globalAlpha.

The developer realized that setting `globalAlpha` inside `drawHeart` overwrites the parent context’s alpha, so the heart’s own opacity would not blend with the brick’s fading or scaling. They will fix `drawHeart` to multiply the heart’s alpha with the current `globalAlpha` instead of replacing it.

The `drawHeart` fix is now implemented: it preserves the parent context’s `globalAlpha` and multiplies the heart’s own opacity by it, so a half-transparent brick still fades the heart correctly. A guard is also being added to `roundRect` so it returns early when width or height is below one pixel, preventing invalid drawing on tiny bricks.

The header layout is finalized as a diner sign: logo, title, and tagline on the left, score and lives on the right. Awnings will frame the plaques. The developer decided against adding an "OPEN" badge.

The tagline is finalized as "smash bricks · rain hearts". A `canvasX` helper will convert pointer coordinates to canvas-relative positions, scaling for rounding differences between the bounding rectangle and the canvas width. Pointer listeners and a window resize handler are being added.

The developer finalized the `canvasX` helper to scale pointer coordinates using the canvas bounding rectangle width. They also decided that any click anywhere in the iframe, including the header, should start the game.

A window blur listener will reset all key states so the paddle does not stick if the user leaves the tab. The developer considers the game complete and is now writing the final file.

The overlay card will show a burger icon and a subtitle explaining the game. The title will match the exact required string. Pointer events are set to auto so the card feels clickable, and pointer-events none is applied only when the overlay is hidden so the canvas still receives mouse moves.

The hamburger SVG will be embedded as a compact one-liner in the HTML. The app container will use `overflow: hidden` to prevent layout overflow. A golden sesame color `#fff3d0` is being added. The header will shrink the brand and truncate the title with ellipsis to keep the layout tight. The cursor will stay default. The developer is now composing the final file.

The hamburger paddle is being enlarged to a large, unmistakable size. It will sit at the bottom center, clearly visible above the particles. The developer chose a width of 128 to 210 pixels, scaling with screen size, and a height of 60 to 78 pixels. This ensures the burger remains a solid, recognizable shape across all resolutions.

The ball radius is set to 8 pixels for visibility. The 2D context will be created with `alpha: false` to avoid transparency overhead and ensure the background is filled every frame.

The developer decided against adding a grain texture. The combo and score popup will use a bold 15px font stack of Trebuchet MS, Segoe UI, and sans-serif so it renders safely even if the primary font is missing. Text baseline will be set to middle, and the canvas context will be reset to default values at the start of every draw call.

The developer is finalizing the canvas draw order. They will set the device-pixel transform first, then clear the background, then apply the shake translation. The background fill will include extra margin so the shifted area stays covered.

The developer finalized the shake logic so the pre-fill background covers the canvas edges during the shake, preventing visible gaps. They are now writing the file and adding the ring particle system, which expands and fades out over time, along with the popup update function.

The popup update function now moves each popup upward at 36 pixels per second and fades it out over 0.8 seconds. The ambient heart system updates position, rotation, and velocity, wrapping particles around the canvas edges. The developer will seed ambient hearts at game start and reseed them if the array empties.

The developer is finalizing the file and ensuring it is error-free. They will avoid deprecated features like `with` and implicit globals, using `const` and `let` inside the IIFE wrapper. Ambient hearts will use their own alpha value rather than life, keeping them subtle behind bricks while explosions dominate the scene.

The keys object will use a plain `{}` to avoid prototype issues. The game over overlay will display "NO BUNS LEFT" and prompt the player to click to restart. Extra sesame seeds and a shine effect will be added to the hamburger paddle.

The developer finalized the `loseLife` function. It decrements lives and triggers a pain flash. When lives hit zero, the game switches to the `over` mode and displays the game-over overlay. Otherwise the ball enters a brief serve wait before launching. During that wait the ball stays attached to the paddle so the player can still move it.

The ball death check now branches by mode. In demo mode it immediately respawns the ball. In normal play it calls loseLife. The one-frame offscreen gap before the serve wait is acceptable. The game over state is reached safely.

The update loop order is now fixed. Particle systems, ambient hearts, rings, popups, steam, shake, squash, banner, pain, and combo timers run first. The game-over check follows. Then the serve wait logic handles ball attachment and launching. Finally the paddle moves. This prevents duplicate life losses and keeps the ball from falling through the bottom check during the serve wait.

The combo timer keeps running during the serve wait so the streak does not reset. Ball trail hearts are only emitted when the ball is in motion, not while it is stuck to the paddle.

The developer capped the total particle count at 360. If the array grows past that limit, the oldest particles are removed from the front. This keeps the trail and explosion effects from overwhelming the canvas.

The ball will be launched at an angle that hits bricks repeatedly during the pre-simulation period. Demo mode never loses lives, so the initial three-second action sequence will be continuous and intense. The developer also verified that the brief pre-simulation window is too short to accidentally clear the entire level.

The developer realized the pre-simulation window could theoretically clear the entire board. At four collisions per substep, sixty frames per second, and a 1.5-second window, the ball could destroy up to 360 bricks if it remained embedded in the brick field. They worry the ball might exit the field too quickly after each bounce and miss the intended collision density.

The developer now accepts that clearing the entire board during the pre-simulation window is possible and even desirable. A full reset would produce a fresh grid alongside the celebration burst of seventy hearts, creating a visually rich moment for the screenshot.

The pre-simulation window is being shortened to 0.8 seconds. This keeps most bricks intact while still producing a dense burst of hearts. The board will look like active gameplay rather than a cleared level. Hearts from the simulation will linger for two seconds, so they remain visible on the first paint and throughout the screenshot window.

The ball speed is being reduced to 480 pixels per second so the action stays readable rather than blurring. At that pace, the 0.8-second simulation window should produce roughly five brick hits, generating about 220 hearts on the first paint. That volume of hearts should look sufficiently dense for the initial screenshot.

The pre-simulation window is being extended to 1.0 seconds. This should produce roughly six to ten brick hits and about 300 hearts, keeping most bricks intact while still creating a dense burst for the initial screenshot. Heart particles will decay at a rate that keeps them alive for approximately 2.2 seconds.

The pre-simulation window is set to 70 frames. A fallback will force four brick breaks if fewer than 40 hearts spawn during that window.

The fallback loop will force four brick breaks if fewer than forty hearts spawn during the pre-simulation window. The developer is now writing the complete HTML file in one pass. They will also fix the heart drawing function to multiply alpha and use a geometric heart shape. All heart particles will be drawn before the hamburger paddle so the hamburger stays fully visible.

The developer finalized the draw order. Hearts render before the hamburger so the burger stays fully visible. Steam particles will draw above the bun to avoid hiding it. The ball is drawn last so it always appears on top.

The toothpick heart will be drawn as part of the hamburger paddle so it remains visible above the explosion hearts. The developer is now writing the complete HTML file with no external assets. They are also adding compact SVG icons for the lives counter and responsive CSS for the logo and hamburger display.

The developer is finalizing the CSS with a dark background, white text, and Trebuchet MS font. They will use the same SVG path across JavaScript and HTML to avoid mismatches, accepting that the canvas burger is the primary visual. A title attribute is being skipped.

The CSS now includes a dark header bar with a red-and-beige gradient and a dark bottom border. The brand and stats containers use a dark background with gold borders and rounded corners.

The header styling now includes a dark bottom border and a box shadow. The hero heading uses Impact and Arial Black with a large clamp-based size, gold text, and a red shadow. The tagline is small and uppercase. The stats container has a right-aligned layout with compact labels.

The CSS now styles the score and best-line values in Impact with large clamp-based sizes. The lives counter uses a flex row with tight gaps. The stage container fills the remaining space with a dark background. The canvas fills the stage and disables touch scrolling. The game-over overlay is positioned absolutely.

The game-over overlay is centered with a dark translucent background and a pointer cursor. It fades out when hidden. The overlay card uses a dark purple background with a gold border and rounded corners. It has a deep shadow and a subtle bobbing animation.

The CSS now defines the bobbing animation, a kicker label, and the overlay title and subtitle with responsive sizing and text shadows. The hero burger container is centered.

The logo and hero burger SVGs receive drop-shadow filters. Responsive media queries shrink the header padding and tagline on narrow screens. The overlay card and hero burger scale down further on small viewports and short heights.

The developer finalized the CSS with no horizontal overflow and a small-screen tweak for the lives SVG. They are now writing the JavaScript, starting with the color palette for the brick rows.

The developer finalized the brick color palette with candy-like shades that evoke ketchup, cheese, and tomato. They decided not to draw the game title on the canvas since the header already displays it. They also verified that the nextLevel function resets the clearing flag and timer to prevent infinite loops.

The developer confirmed the level transition logic. The clearing flag and timer reset when the last brick dies. New bricks spawn live, so the ball cannot immediately retrigger the same level. The ball launches from below the new bricks and begins breaking them.

The developer found a race condition in the update loop. If the ball drops and the last brick dies in the same frame, loseLife sets the game over flag while clearing is still true. The clearing check runs after the bottom-of-screen check, so nextLevel could fire after the game is already over. They need to guard against this overlap.

The developer added a `showBanner` helper to display temporary text for about 1.25 seconds. They also considered adding a subtle ball spin visual effect.

The ball heart now rotates as the ball moves. A fading trail of ten circles will draw behind the ball. The trail array clears when the ball is launched. All pieces are now in place.

The developer is carefully assembling the final code and checking for missing commas and braces. They are avoiding alerts, localStorage, and external URLs entirely. The overlay text uses cream against a dark purple background, and the awning and plaques use high-contrast red-and-white and gold borders to ensure visibility.

A soft pink radial gradient will glow behind the brick wall so the candy colors pop. The developer is now writing the complete HTML file and adding a wood-textured counter.

The developer is now drawing the wood-textured counter. They created a vertical gradient from dark brown to nearly black, added five subtle wood grain strokes with bezier curves, and painted a light top edge.

A small ketchup bottle will sit on the right counter corner as a decorative accent. It will be drawn as a red rounded rectangle with a white cap and a heart symbol, placed at the bottom-right edge so it does not interfere with the play area.

The ketchup bottle is being removed to avoid overlapping the paddle. The developer will skip `prefers-reduced-motion` support. They are writing the final file without try/catch blocks to guarantee zero console errors. A quick scan for missing braces is underway.

The developer verified that every HTML element ID exists and matches the JavaScript references. They will use the stage container for resize calculations rather than the canvas itself, since the stage is the positioning parent.

The resize function now uses the stage container for dimensions and sets the canvas backing store to match. It calculates a device pixel ratio and clamps the speed scale between 0.55 and 1.12 based on the viewport width.

The resize function scales existing ball velocity by the speed scale ratio. It skips scaling when the ball is at rest. It also recalculates brick layout and fits the paddle. The ball radius is clamped to a viewport-relative size. Early resize calls are handled safely.

The developer realized that calling resize every frame would drift ball speed from tiny viewport jitter. They will only resize when the stage dimensions change by more than one pixel, and they will check the stage size inside the main loop instead of firing resize continuously.

The ball and paddle objects are now defined with their initial positions, velocities, and dimensions. The grid columns and rows are initialized to zero so the first layout rebuilds the brick wall. The layoutBricks function is being written with responsive column counts, row counts, gaps, and margins based on viewport width and height.

The layout function now caps the brick wall height at 36 percent of the viewport and clamps each brick to a minimum width of eight pixels. It only rebuilds the brick array when the column or row count changes, and it assigns a repeating candy color to every new brick.

The layout function now assigns exact pixel positions to each brick using the calculated width and height. It avoids rounding to keep gaps evenly spaced. The developer is finalizing the design and will add a highly saturated heart color for the rain effect and a dark background.

A subtle vignette will keep the center bright. The overlay card will show tiny heart characters under the subtitle. The kicker label will read "AUTO-PLAY DEMO". Decorative SVGs will get `aria-hidden="true"`. The developer is now typing the final file.

The developer replaced the top bun's ellipse with a quadratic dome curve. They were unsure about the anticlockwise arc and wanted a simpler, fully understood shape for the hamburger bun.

The developer refined the hamburger bun shape. The first quadratic dome curve looked lopsided, so they replaced it with a more balanced arc that bulges upward from the sides to the center top.

The developer refined the hamburger bun shape further. They raised the quadratic control point higher to create a taller, more prominent dome that peaks near the top of the burger. They calculated the exact curve parameters so the dome sits at roughly 10 percent of the brick height.

The developer finalized the hamburger bun shape. The quadratic dome peaks at 10 percent of the brick height and sits entirely within the paddle bounds. They will add six sesame seeds between 12 and 28 percent of the height, plus a highlight at 36 percent.

A toothpick will extend from the bun top, peaking near the center and rising above the burger. The developer will keep the quadratic dome for the bun only, using ellipses for the plate, lettuce, seeds, and highlight to avoid arc angle bugs.

A puffy bottom bun will be drawn with a large rounded rectangle. A darker line will separate the top bun from the tomato. The draw order is set from bottom to top so the layers overlap correctly.

The developer is now coding the `drawPaddle` function. The plate will be drawn first so it sits behind the bottom bun. It spans the full paddle width but is inset slightly so it remains wider than the burger itself.

A soft black shadow will sit beneath the plate. The heart explosion will be drawn with two overlapping circles and a triangle to form a proper heart shape.

The heart explosion is being drawn with two overlapping circles and a triangle to form a proper heart shape.

The developer abandoned the circle-and-triangle heart shape because the center bridge might look like a clover. They are now testing a standard bezier curve path to draw a proper heart instead.

The developer found a standard heart path from a CSS-Tricks snippet. They will scale it to the heart size and translate it to the center point.

The developer is now verifying the scaled bezier heart path. They are manually checking each control point against the original coordinates to ensure the heart shape is accurate.

The scaled bezier heart path is verified and ready for use. The developer is now writing the `drawHeart` function.

The developer finalized the `drawHeart` function with a white highlight ellipse for larger hearts. They confirmed the ellipse dimensions stay safe at the minimum scale. Heart sizes will range from 5 to 13, spawning as small as 7.5 pixels wide.