Models 44

GPT-6 Luna

by OpenAIReasoningClean
Open in new window

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

How it went

Total time
1 min 23 s
Cost
$0.0049
Output tokens
9,683
Spent thinking
2,784 tokens
Speed
126.3 tok/s
Console errors
0
First token after
6.4 s
First visible text after
34.5 s
Prompt tokens
357
Code size
19.9 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-6-luna
Reasoning sent
{"effort":"medium"}
Max output
64,000 tokens
Generated
Nov 10, 2026

What it was asked

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

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

Code run-1.html · 619 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Heartbreaker — Breakout</title>
<style>
  * { box-sizing: border-box; }
  html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; }
  body {
    background: #fff0de;
    font-family: ui-rounded, "Arial Rounded MT Bold", system-ui, sans-serif;
  }
  canvas { display: block; width: 100%; height: 100%; outline: none; }
</style>
</head>
<body>
<canvas id="game" tabindex="0" aria-label="Breakout game. Click or press a key to start."></canvas>
<script>
(() => {
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const TAU = Math.PI * 2;
  const palette = ['#ff6d77', '#ff9d55', '#ffcf58', '#65d5b0', '#68b9ef', '#b18af4', '#f17bc0'];
  let W = 0, H = 0, dpr = 1, layout;
  let bricks = [], particles = [];
  let started = false, gameOver = false, lives = 3, wave = 1, score = 0;
  let pointerX = null, keys = {};
  let last = performance.now();
  let ball = { x: 0, y: 0, vx: 0, vy: 0, r: 8 };
  let paddle = { x: 0, y: 0, w: 110, h: 26 };

  function resize() {
    const rect = canvas.getBoundingClientRect();
    W = rect.width;
    H = rect.height;
    dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = Math.round(W * dpr);
    canvas.height = Math.round(H * dpr);
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    layout = makeLayout();
    makeBricks(true);
    placeBall();
  }

  function makeLayout() {
    const bw = Math.min(W - 34, 1060);
    const bh = Math.max(300, Math.min(H - 154, 630));
    const x = (W - bw) / 2;
    const y = Math.min(120, Math.max(88, H * 0.15));
    const s = Math.max(0.48, Math.min(1.06, bw / 1000));
    const cols = Math.max(4, Math.min(11, Math.floor((bw - 44) / 86)));
    const rows = 5;
    const gap = Math.max(4, 8 * s);
    const side = Math.max(17, 24 * s);
    const brickW = (bw - side * 2 - gap * (cols - 1)) / cols;
    const brickH = Math.max(15, 23 * s);
    return {
      x, y, w: bw, h: bh, s, cols, rows, gap, side, brickW, brickH,
      brickY: y + Math.max(71, 82 * s),
      paddleY: y + bh - Math.max(53, 64 * s)
    };
  }

  function makeBricks(force) {
    if (!force && bricks.length && bricks[0].length === layout.cols) return;
    bricks = Array.from({ length: layout.rows }, (_, r) =>
      Array.from({ length: layout.cols }, (_, c) => ({
        alive: true,
        color: palette[(r * 2 + c + wave - 1) % palette.length]
      }))
    );
  }

  function placeBall() {
    if (!layout) return;
    const s = layout.s;
    paddle.w = Math.max(84, 116 * s);
    paddle.h = Math.max(23, 29 * s);
    paddle.y = layout.paddleY;
    paddle.x = layout.x + layout.w / 2;
    ball.r = Math.max(5.5, 8 * s);
    ball.x = paddle.x;
    ball.y = paddle.y - 78 * s;
    const speed = 410 * s;
    ball.vx = speed * 0.29;
    ball.vy = -speed;
  }

  function roundRect(x, y, w, h, r) {
    ctx.beginPath();
    ctx.roundRect(x, y, w, h, r);
  }

  function heartPath(x, y, size) {
    ctx.beginPath();
    ctx.moveTo(x, y + size * .34);
    ctx.bezierCurveTo(x - size * .8, y - size * .2, x - size * .56, y - size * .88, x, y - size * .43);
    ctx.bezierCurveTo(x + size * .56, y - size * .88, x + size * .8, y - size * .2, x, y + size * .34);
    ctx.closePath();
  }

  function drawBackground() {
    const g = ctx.createLinearGradient(0, 0, W, H);
    g.addColorStop(0, '#fff5e8');
    g.addColorStop(.55, '#ffe8d2');
    g.addColorStop(1, '#ffd9d1');
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, W, H);

    // Soft floating candy dots
    for (let i = 0; i < 24; i++) {
      const x = (i * 173 + 54) % Math.max(1, W);
      const y = (i * 97 + 18) % Math.max(1, H);
      ctx.globalAlpha = .16 + (i % 3) * .04;
      ctx.fillStyle = palette[i % palette.length];
      ctx.beginPath();
      ctx.arc(x, y, 2 + i % 4, 0, TAU);
      ctx.fill();
    }
    ctx.globalAlpha = 1;

    // Brand mark and header
    const small = W < 560;
    const titleSize = small ? 19 : 27;
    ctx.save();
    ctx.shadowColor = 'rgba(161, 63, 83, .16)';
    ctx.shadowBlur = 14;
    ctx.fillStyle = '#fffaf3';
    roundRect(20, 15, Math.min(W - 40, 1240), 65, 22);
    ctx.fill();
    ctx.restore();

    ctx.fillStyle = '#ff6d77';
    heartPath(44, 45, 14);
    ctx.fill();
    ctx.fillStyle = '#482b4b';
    ctx.font = `1000 ${titleSize}px ui-rounded, system-ui, sans-serif`;
    ctx.textBaseline = 'middle';
    ctx.fillText('HEARTBREAKER', 66, 39);
    ctx.fillStyle = '#ad7780';
    ctx.font = `700 ${small ? 9 : 11}px system-ui, sans-serif`;
    ctx.letterSpacing = '1.1px';
    ctx.fillText('BREAKOUT, WITH EXTRA LOVE', 68, 62);
    ctx.letterSpacing = '0px';

    if (W > 660) {
      drawPill(W - 352, 27, 194, 38, '#fff0e8', '#a7656f', 'SCORE  ' + String(score).padStart(4, '0'));
      drawPill(W - 146, 27, 112, 38, '#fff0e8', '#a7656f', '♥  ' + lives + ' LIVES');
    } else {
      drawPill(W - 101, 28, 73, 34, '#fff0e8', '#a7656f', '♥ ' + lives);
    }
    if (!started) {
      const text = 'CLICK OR PRESS A KEY TO PLAY';
      ctx.font = `900 ${small ? 9 : 11}px system-ui, sans-serif`;
      const width = ctx.measureText(text).width + 30;
      const px = W > 660 ? W - width - 26 : W / 2 - width / 2;
      drawPill(px, 87, width, 28, '#ff7180', '#fff', text);
    } else if (W > 660) {
      drawPill(W - 146, 87, 112, 28, '#fffaf3', '#ad7780', 'WAVE  ' + wave);
    }
  }

  function drawPill(x, y, w, h, fill, ink, text) {
    ctx.fillStyle = fill;
    roundRect(x, y, w, h, h / 2);
    ctx.fill();
    ctx.fillStyle = ink;
    ctx.font = '900 10px system-ui, sans-serif';
    ctx.textBaseline = 'middle';
    ctx.textAlign = 'center';
    ctx.fillText(text, x + w / 2, y + h / 2 + .5);
    ctx.textAlign = 'left';
  }

  function drawBoard() {
    const { x, y, w, h, s } = layout;
    ctx.save();
    ctx.shadowColor = 'rgba(91, 42, 70, .22)';
    ctx.shadowBlur = 28;
    ctx.shadowOffsetY = 12;
    const bg = ctx.createLinearGradient(x, y, x + w, y + h);
    bg.addColorStop(0, '#422a50');
    bg.addColorStop(1, '#302640');
    ctx.fillStyle = bg;
    roundRect(x, y, w, h, 25 * s);
    ctx.fill();
    ctx.restore();

    ctx.strokeStyle = 'rgba(255,255,255,.25)';
    ctx.lineWidth = 1.5;
    roundRect(x + 1, y + 1, w - 2, h - 2, 25 * s);
    ctx.stroke();

    // Inner glow and subtle stars
    ctx.save();
    roundRect(x, y, w, h, 25 * s);
    ctx.clip();
    const glow = ctx.createRadialGradient(x + w * .5, y + h * .12, 10, x + w * .5, y + h * .2, w * .72);
    glow.addColorStop(0, 'rgba(247,126,164,.13)');
    glow.addColorStop(1, 'rgba(247,126,164,0)');
    ctx.fillStyle = glow;
    ctx.fillRect(x, y, w, h);
    for (let i = 0; i < 18; i++) {
      const sx = x + ((i * 137 + 31) % Math.max(1, w - 30)) + 15;
      const sy = y + 38 + ((i * 89 + 17) % Math.max(1, h - 70));
      ctx.globalAlpha = .18 + (i % 3) * .06;
      ctx.fillStyle = '#ffe4ee';
      ctx.beginPath();
      ctx.arc(sx, sy, 1 + i % 2, 0, TAU);
      ctx.fill();
    }
    ctx.globalAlpha = 1;
    ctx.restore();

    ctx.fillStyle = 'rgba(255,231,241,.56)';
    ctx.font = `800 ${Math.max(9, 11 * s)}px system-ui, sans-serif`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('♥  HEARTS ARE BREAKABLE  ♥', x + w / 2, y + 36 * s);
    ctx.textAlign = 'left';

    drawBricks();
  }

  function drawBricks() {
    const L = layout;
    for (let r = 0; r < L.rows; r++) {
      for (let c = 0; c < L.cols; c++) {
        const b = bricks[r] && bricks[r][c];
        if (!b || !b.alive) continue;
        const x = L.x + L.side + c * (L.brickW + L.gap);
        const y = L.brickY + r * (L.brickH + L.gap);
        ctx.save();
        ctx.shadowColor = b.color;
        ctx.shadowBlur = 9 * L.s;
        const grad = ctx.createLinearGradient(x, y, x, y + L.brickH);
        grad.addColorStop(0, lighten(b.color, 20));
        grad.addColorStop(1, b.color);
        ctx.fillStyle = grad;
        roundRect(x, y, L.brickW, L.brickH, Math.min(8, 7 * L.s));
        ctx.fill();
        ctx.restore();

        ctx.fillStyle = 'rgba(255,255,255,.30)';
        roundRect(x + 3, y + 2, Math.max(0, L.brickW - 6), Math.max(2, L.brickH * .23), 4);
        ctx.fill();
        heartPath(x + L.brickW / 2, y + L.brickH * .64, Math.max(3, 5 * L.s));
        ctx.fillStyle = 'rgba(255,255,255,.76)';
        ctx.fill();
      }
    }
  }

  function lighten(hex, amount) {
    const n = parseInt(hex.slice(1), 16);
    const r = Math.min(255, (n >> 16) + amount);
    const g = Math.min(255, ((n >> 8) & 255) + amount);
    const b = Math.min(255, (n & 255) + amount);
    return `rgb(${r},${g},${b})`;
  }

  function drawBurger() {
    const x = paddle.x, y = paddle.y, w = paddle.w, h = paddle.h;
    ctx.save();
    ctx.shadowColor = 'rgba(12,5,25,.4)';
    ctx.shadowBlur = 12;
    ctx.shadowOffsetY = 5;
    // Bun bottom
    ctx.fillStyle = '#bd653a';
    roundRect(x - w / 2, y + h * .53, w, h * .38, h * .2);
    ctx.fill();
    ctx.restore();

    // Patty
    ctx.fillStyle = '#5b2d27';
    roundRect(x - w * .49, y + h * .39, w * .98, h * .28, h * .12);
    ctx.fill();
    ctx.fillStyle = '#8f4730';
    roundRect(x - w * .47, y + h * .39, w * .94, h * .1, h * .06);
    ctx.fill();

    // Cheese corners
    ctx.fillStyle = '#ffd34f';
    ctx.beginPath();
    ctx.moveTo(x - w * .38, y + h * .46);
    ctx.lineTo(x + w * .42, y + h * .46);
    ctx.lineTo(x + w * .32, y + h * .78);
    ctx.lineTo(x + w * .22, y + h * .68);
    ctx.lineTo(x + w * .08, y + h * .78);
    ctx.lineTo(x - w * .07, y + h * .68);
    ctx.lineTo(x - w * .25, y + h * .78);
    ctx.closePath();
    ctx.fill();

    // Lettuce frill
    ctx.fillStyle = '#52b866';
    ctx.beginPath();
    ctx.moveTo(x - w * .48, y + h * .43);
    for (let i = 0; i <= 10; i++) {
      const xx = x - w * .48 + (w * .96 * i / 10);
      const yy = y + h * (.4 + (i % 2 ? .14 : 0));
      ctx.lineTo(xx, yy);
    }
    ctx.lineTo(x + w * .48, y + h * .52);
    ctx.lineTo(x - w * .48, y + h * .52);
    ctx.closePath();
    ctx.fill();

    // Top bun
    const bun = ctx.createLinearGradient(0, y - h * .2, 0, y + h * .52);
    bun.addColorStop(0, '#ffd98a');
    bun.addColorStop(.55, '#f7b956');
    bun.addColorStop(1, '#d9843e');
    ctx.fillStyle = bun;
    ctx.beginPath();
    ctx.moveTo(x - w * .5, y + h * .48);
    ctx.lineTo(x - w * .5, y + h * .26);
    ctx.quadraticCurveTo(x - w * .47, y - h * .22, x, y - h * .2);
    ctx.quadraticCurveTo(x + w * .47, y - h * .22, x + w * .5, y + h * .26);
    ctx.lineTo(x + w * .5, y + h * .48);
    ctx.quadraticCurveTo(x, y + h * .59, x - w * .5, y + h * .48);
    ctx.fill();

    // Sesame seeds
    ctx.save();
    ctx.translate(x, y);
    ctx.fillStyle = '#fff0bf';
    for (const [sx, sy, rot] of [[-.31,.12,-.4],[-.12,-.02,.45],[.08,.08,-.2],[.29,.13,.5],[.2,-.02,-.5],[-.32,.3,.4]]) {
      ctx.save();
      ctx.translate(sx * w, sy * h);
      ctx.rotate(rot);
      ctx.beginPath();
      ctx.ellipse(0, 0, 2.6 * layout.s, 1.15 * layout.s, 0, 0, TAU);
      ctx.fill();
      ctx.restore();
    }
    ctx.restore();

    // Gloss
    ctx.strokeStyle = 'rgba(255,255,255,.52)';
    ctx.lineWidth = Math.max(1, 1.5 * layout.s);
    ctx.beginPath();
    ctx.arc(x - w * .08, y + h * .2, w * .29, Math.PI * 1.12, Math.PI * 1.68);
    ctx.stroke();
  }

  function drawBall() {
    const glow = ctx.createRadialGradient(ball.x - ball.r * .3, ball.y - ball.r * .3, 1, ball.x, ball.y, ball.r * 2.7);
    glow.addColorStop(0, '#fff');
    glow.addColorStop(.34, '#ffe8a9');
    glow.addColorStop(1, 'rgba(255,177,87,0)');
    ctx.fillStyle = glow;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r * 2.7, 0, TAU);
    ctx.fill();
    ctx.fillStyle = '#fff4c4';
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, TAU);
    ctx.fill();
    ctx.stroke();
    ctx.fillStyle = '#ff8993';
    heartPath(ball.x, ball.y + 1, ball.r * .48);
    ctx.fill();
  }

  function burst(x, y, color) {
    const count = 34 + Math.floor(Math.random() * 19);
    for (let i = 0; i < count; i++) {
      const a = Math.random() * TAU;
      const speed = 65 + Math.random() * 260;
      particles.push({
        x, y, vx: Math.cos(a) * speed, vy: Math.sin(a) * speed - 45,
        size: 3 + Math.random() * 5.5,
        life: .7 + Math.random() * .85, max: 1.55,
        color: Math.random() < .65 ? palette[Math.floor(Math.random() * palette.length)] : color,
        rot: Math.random() * TAU, spin: (Math.random() - .5) * 8
      });
    }
    // A few extra big hearts make each pop feel deliciously dramatic.
    for (let i = 0; i < 5; i++) {
      const a = Math.random() * TAU;
      const speed = 100 + Math.random() * 170;
      particles.push({
        x, y, vx: Math.cos(a) * speed, vy: Math.sin(a) * speed - 65,
        size: 8 + Math.random() * 4, life: 1.05 + Math.random() * .5, max: 1.55,
        color: ['#ff526f', '#ff98b1', '#ffd467'][i % 3],
        rot: Math.random() * TAU, spin: (Math.random() - .5) * 5
      });
    }
  }

  function drawParticles(dt) {
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.life -= dt;
      if (p.life <= 0) { particles.splice(i, 1); continue; }
      p.x += p.vx * dt;
      p.y += p.vy * dt;
      p.vy += 220 * dt;
      p.vx *= Math.pow(.985, dt * 60);
      p.rot += p.spin * dt;
      ctx.save();
      ctx.globalAlpha = Math.min(1, p.life / .22) * Math.min(1, p.life / p.max * 2.2);
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rot);
      ctx.fillStyle = p.color;
      heartPath(0, 0, p.size);
      ctx.fill();
      ctx.restore();
    }
  }

  function allGone() {
    return bricks.every(row => row.every(b => !b.alive));
  }

  function update(dt) {
    if (!layout || gameOver) return;
    const L = layout;
    const s = L.s;
    paddle.y = L.paddleY;
    paddle.w = Math.max(84, 116 * s);
    paddle.h = Math.max(23, 29 * s);

    if (started) {
      if (pointerX !== null) {
        paddle.x += (pointerX - paddle.x) * Math.min(1, dt * 13);
      } else {
        const direction = (keys.ArrowRight || keys.KeyD ? 1 : 0) - (keys.ArrowLeft || keys.KeyA ? 1 : 0);
        paddle.x += direction * 550 * s * dt;
      }
    } else {
      // Self-playing attract mode: follow the ball with a gentle, imperfect AI.
      const target = ball.x + Math.sin(performance.now() / 560) * 13 * s;
      paddle.x += (target - paddle.x) * Math.min(1, dt * 5.5);
    }
    paddle.x = Math.max(L.x + paddle.w / 2 + 8, Math.min(L.x + L.w - paddle.w / 2 - 8, paddle.x));

    ball.x += ball.vx * dt;
    ball.y += ball.vy * dt;

    if (ball.x - ball.r < L.x + 5) { ball.x = L.x + 5 + ball.r; ball.vx = Math.abs(ball.vx); }
    if (ball.x + ball.r > L.x + L.w - 5) { ball.x = L.x + L.w - 5 - ball.r; ball.vx = -Math.abs(ball.vx); }
    if (ball.y - ball.r < L.y + 8) { ball.y = L.y + 8 + ball.r; ball.vy = Math.abs(ball.vy); }

    // Brick collisions
    let hit = false;
    for (let r = 0; r < L.rows && !hit; r++) {
      for (let c = 0; c < L.cols && !hit; c++) {
        const b = bricks[r][c];
        if (!b.alive) continue;
        const bx = L.x + L.side + c * (L.brickW + L.gap);
        const by = L.brickY + r * (L.brickH + L.gap);
        if (ball.x + ball.r > bx && ball.x - ball.r < bx + L.brickW &&
            ball.y + ball.r > by && ball.y - ball.r < by + L.brickH) {
          b.alive = false;
          burst(Math.max(bx, Math.min(ball.x, bx + L.brickW)), Math.max(by, Math.min(ball.y, by + L.brickH)), b.color);
          score += 10;
          const overlapX = Math.min(ball.x + ball.r - bx, bx + L.brickW - (ball.x - ball.r));
          const overlapY = Math.min(ball.y + ball.r - by, by + L.brickH - (ball.y - ball.r));
          if (overlapX < overlapY) ball.vx *= -1;
          else ball.vy *= -1;
          hit = true;
        }
      }
    }

    // Hamburger paddle bounce
    if (ball.vy > 0 && ball.y + ball.r >= paddle.y - paddle.h * .2 &&
        ball.y - ball.r <= paddle.y + paddle.h * .65 &&
        ball.x >= paddle.x - paddle.w / 2 && ball.x <= paddle.x + paddle.w / 2) {
      const offset = (ball.x - paddle.x) / (paddle.w / 2);
      const speed = Math.hypot(ball.vx, ball.vy);
      const angle = offset * .92;
      ball.vx = Math.sin(angle) * speed;
      ball.vy = -Math.cos(angle) * speed;
      ball.y = paddle.y - paddle.h * .2 - ball.r;
    }

    if (ball.y - ball.r > L.y + L.h) {
      if (started) {
        lives--;
        if (lives <= 0) { gameOver = true; return; }
      }
      placeBall();
    }

    if (allGone()) {
      wave++;
      makeBricks(true);
      placeBall();
    }
  }

  function draw() {
    drawBackground();
    if (layout) {
      drawBoard();
      // Constrain particle spray to the playfield.
      ctx.save();
      roundRect(layout.x, layout.y, layout.w, layout.h, 24 * layout.s);
      ctx.clip();
      drawParticles(0);
      ctx.restore();
      drawBurger();
      drawBall();
      drawBottomNote();
      if (gameOver) drawGameOver();
    }
  }

  function drawBottomNote() {
    const L = layout;
    if (H < 410) return;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillStyle = 'rgba(255,255,255,.58)';
    ctx.font = `700 ${Math.max(9, 11 * L.s)}px system-ui, sans-serif`;
    const note = started ? '← →  MOVE YOUR BURGER     •     BREAK EVERY BRICK' : 'THE BRICKS ARE FULL OF LOVE. GO ON, BREAK A FEW.';
    ctx.fillText(note, L.x + L.w / 2, L.y + L.h - 19 * L.s);
    ctx.textAlign = 'left';
  }

  function drawGameOver() {
    const L = layout;
    ctx.fillStyle = 'rgba(39, 27, 52, .76)';
    roundRect(L.x + 18, L.y + L.h * .35, L.w - 36, 150, 22);
    ctx.fill();
    ctx.textAlign = 'center';
    ctx.fillStyle = '#fff5e9';
    ctx.font = `1000 ${W < 500 ? 22 : 31}px system-ui, sans-serif`;
    ctx.fillText('LOVE GOT AWAY!', W / 2, L.y + L.h * .35 + 52);
    ctx.font = '800 13px system-ui, sans-serif';
    ctx.fillStyle = '#ffd4da';
    ctx.fillText('CLICK OR PRESS A KEY TO TRY AGAIN', W / 2, L.y + L.h * .35 + 91);
    ctx.textAlign = 'left';
  }

  // Keep particles moving independently of drawing.
  function tick(now) {
    const dt = Math.min(.033, (now - last) / 1000 || 0);
    last = now;
    update(dt);
    draw();
    // particles are advanced during update so the drawing stays pure
    requestAnimationFrame(tick);
  }

  // Particle updates, separated from rendering.
  const originalUpdate = update;
  update = function(dt) {
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.life -= dt;
      if (p.life <= 0) { particles.splice(i, 1); continue; }
      p.x += p.vx * dt;
      p.y += p.vy * dt;
      p.vy += 220 * dt;
      p.vx *= Math.pow(.985, dt * 60);
      p.rot += p.spin * dt;
    }
    originalUpdate(dt);
  };
  // drawParticles only paints; its dt argument intentionally unused.
  drawParticles = function() {
    for (const p of particles) {
      ctx.save();
      ctx.globalAlpha = Math.min(1, p.life / .22) * Math.min(1, p.life / p.max * 2.2);
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rot);
      ctx.fillStyle = p.color;
      heartPath(0, 0, p.size);
      ctx.fill();
      ctx.restore();
    }
  };

  function startGame() {
    if (gameOver) {
      gameOver = false;
      lives = 3;
      score = 0;
      wave = 1;
      particles = [];
      makeBricks(true);
      placeBall();
    }
    started = true;
    canvas.focus({ preventScroll: true });
  }

  canvas.addEventListener('pointerdown', e => {
    if (!started || gameOver) startGame();
    const rect = canvas.getBoundingClientRect();
    pointerX = e.clientX - rect.left;
  });
  canvas.addEventListener('pointermove', e => {
    if (!started) return;
    const rect = canvas.getBoundingClientRect();
    pointerX = e.clientX - rect.left;
  });
  canvas.addEventListener('pointerleave', () => { pointerX = null; });
  window.addEventListener('keydown', e => {
    if (['ArrowLeft', 'ArrowRight', ' '].includes(e.key)) e.preventDefault();
    if (!started || gameOver) startGame();
    keys[e.code] = true;
  });
  window.addEventListener('keyup', e => { keys[e.code] = false; });
  window.addEventListener('resize', resize);

  resize();
  requestAnimationFrame(tick);
})();
</script>
</body>
</html>
Reasoning trace (2,784 tokens)
**Considering game layout design**

I'm working on an HTML overlay UI with a canvas background. The plan is to have a header height of 80 at 1280 pixels wide, with a 720-height game canvas centered beneath it. The user wants to create a breakout game, so the canvas will fill the viewport while the UI sits on top. I need to think about the canvas dimensions and scaling for different screen sizes to avoid distortion — especially on mobile devices.**Optimizing canvas responsiveness**

I’m thinking about how to make better use of canvas coordinates by normalizing them from the 1280 width and adjusting the height ratio. I need to compute W and H, and scale the positions based on that ratio. For example, setting the game board width by looking at dimensions — like using a minimum of W-48 or scaling it based on 1080. On mobile, the board should take up nearly the full width and height, but I wonder if the paddle and other elements will end up too small if I use a scale factor of around 0.39.**Designing the game board**

I'm determining the board's dimensions: width will be the minimum between W-32 and 1060, while height will be the minimum between H-145 and 620. For mobile, the board height will be around 617, but for desktop, it's close to 620. I need to ensure the grid layout is well-defined, with the bricks adjusting dynamically. Additionally, I’m thinking about HUD placement and ensuring the playfield has a rounded border. The ball and paddle specifics are also crucial.**Planning game mechanics**

I’m considering how to implement paddle movement using the mouse and keyboard arrows. I think clicking the canvas should start the game, and then any click afterward would launch the ball, which continues moving. I want to ensure there's a DOM button for accessibility, and I’ll need to handle key events correctly. Also, I’ll keep track of physics positions and update everything each frame to manage the ball’s layout and reset as necessary. This should keep the game dynamics smooth!**Considering mouse interaction and drawing**

I’m working on a demo where the mouse movements won’t change anything unless I click “start.” The board click might initiate the session. I’ll need to visualize elements like a little paddle and highlights for ingredients like sesame, lettuce, cheese, and a patty as part of a burger. I plan to draw the burger with multiple rounded layers, centered at a specific position. I'll also make sure the paddle has a rectangular collision box for interactions.