Models 44

GPT-6 Luna Pro

by OpenAIreasoning defaultClean
Open in new window

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

How it went

Total time
2 min 1 s
Cost
$0.011
Output tokens
18,749
Spent thinking
6,005 tokens
Speed
927.6 tok/s
Console errors
0
First token after
1 min 41 s
First visible text after
2 min 1 s
Prompt tokens
19,058
Code size
20.5 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-6-luna-pro
Reasoning sent
model default
Max output
128,000 tokens
Generated
Nov 13, 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 · 678 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Heartbreaker — Burger Breakout</title>
<style>
  :root {
    color-scheme: dark;
    --ink: #171323;
    --muted: #a9a0b7;
    --pink: #ff6eab;
    --cream: #fff0d7;
  }
  * { box-sizing: border-box; }
  html, body { margin: 0; min-height: 100%; }
  body {
    min-height: 100vh;
    padding: 21px 24px 14px;
    overflow-x: hidden;
    color: #fff8f0;
    font-family: ui-rounded, "Trebuchet MS", system-ui, sans-serif;
    background:
      radial-gradient(ellipse at 50% -20%, rgba(112, 68, 139, .32), transparent 60%),
      radial-gradient(ellipse at 7% 72%, rgba(255, 85, 142, .08), transparent 36%),
      #171323;
  }
  .shell { width: min(1120px, 100%); margin: 0 auto; }
  header {
    min-height: 82px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    margin: 0 auto 13px;
  }
  .brand { display: flex; align-items: center; gap: 13px; min-width: 0; }
  .brand-mark {
    display: grid;
    place-items: center;
    flex: 0 0 50px;
    width: 50px;
    height: 50px;
    border: 1px solid rgba(255,255,255,.15);
    border-radius: 16px;
    background: linear-gradient(145deg, #382843, #231b31);
    box-shadow: inset 0 1px rgba(255,255,255,.08), 0 8px 24px #0c0a12;
    font-size: 27px;
  }
  h1 {
    margin: 0;
    color: var(--cream);
    font-size: clamp(22px, 3vw, 31px);
    font-weight: 950;
    line-height: 1;
    letter-spacing: .065em;
  }
  .tagline {
    margin-top: 7px;
    color: #b9aabb;
    font-size: 10px;
    font-weight: 800;
    letter-spacing: .2em;
  }
  .live-pill {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 10px 14px;
    border: 1px solid rgba(255, 137, 177, .23);
    border-radius: 999px;
    background: rgba(255, 110, 171, .08);
    color: #ffd4e3;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .1em;
    white-space: nowrap;
  }
  .live-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: #ff71a9;
    box-shadow: 0 0 10px #ff71a9;
    animation: blink 1.6s infinite;
  }
  @keyframes blink { 50% { opacity: .35; } }

  .hud {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 14px;
    margin-bottom: 10px;
  }
  .stat {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    padding: 7px 14px;
    border: 1px solid rgba(255,255,255,.09);
    border-radius: 13px;
    background: rgba(255,255,255,.045);
  }
  .stat:last-child { justify-self: end; }
  .stat-icon { font-size: 18px; filter: drop-shadow(0 0 7px rgba(255,110,171,.35)); }
  .stat-label {
    color: #aa9eb4;
    font-size: 9px;
    font-weight: 900;
    letter-spacing: .16em;
  }
  .stat-value {
    margin-top: 1px;
    color: #fff4e8;
    font-size: 15px;
    font-weight: 900;
    font-variant-numeric: tabular-nums;
  }
  .center-label {
    color: #c6b8ca;
    font-size: 10px;
    font-weight: 900;
    letter-spacing: .18em;
    text-align: center;
    white-space: nowrap;
  }
  .center-label span { color: #ff9fc2; }

  .game-frame {
    position: relative;
    overflow: hidden;
    width: 100%;
    height: clamp(390px, 68vh, 610px);
    min-height: 390px;
    border: 1px solid rgba(255, 209, 231, .23);
    border-radius: 19px;
    background: #211a30;
    box-shadow:
      0 22px 65px rgba(0,0,0,.38),
      0 0 40px rgba(244, 91, 160, .07),
      inset 0 0 0 4px rgba(255,255,255,.025);
  }
  canvas { display: block; width: 100%; height: 100%; }
  .play-hint {
    position: absolute;
    z-index: 2;
    top: 51%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    gap: 11px;
    max-width: calc(100% - 24px);
    padding: 12px 17px;
    border: 1px solid rgba(255,255,255,.2);
    border-radius: 14px;
    background: rgba(27, 19, 39, .8);
    box-shadow: 0 8px 30px rgba(0,0,0,.2), inset 0 1px rgba(255,255,255,.08);
    color: #fff3eb;
    font-size: 10px;
    font-weight: 900;
    letter-spacing: .11em;
    white-space: nowrap;
    backdrop-filter: blur(8px);
    pointer-events: none;
    transition: opacity .25s, transform .25s;
  }
  .play-hint .keycap {
    display: grid;
    place-items: center;
    width: 27px;
    height: 27px;
    flex: 0 0 27px;
    border: 1px solid rgba(255,255,255,.19);
    border-radius: 8px;
    background: rgba(255,255,255,.09);
    color: #ff9fc2;
    font-size: 15px;
  }
  .play-hint.hidden { opacity: 0; transform: translate(-50%, -42%); }
  footer {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 11px 3px 0;
    color: #9e92a9;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .055em;
  }
  footer strong { color: #d9c8d5; }
  .controls-right { color: #c6b6c4; text-align: right; }
  @media (max-width: 600px) {
    body { padding: 12px 12px 10px; }
    header { min-height: 60px; gap: 8px; margin-bottom: 10px; }
    .brand { gap: 9px; }
    .brand-mark { width: 41px; height: 41px; flex-basis: 41px; border-radius: 13px; font-size: 22px; }
    h1 { font-size: 21px; letter-spacing: .045em; }
    .tagline { margin-top: 5px; font-size: 8px; letter-spacing: .14em; }
    .live-pill { padding: 8px 9px; gap: 6px; font-size: 8px; letter-spacing: .06em; }
    .live-dot { width: 6px; height: 6px; }
    .hud { gap: 6px; margin-bottom: 8px; }
    .stat { gap: 6px; padding: 6px 8px; min-height: 40px; border-radius: 11px; }
    .stat-icon { font-size: 15px; }
    .stat-label { font-size: 8px; letter-spacing: .1em; }
    .stat-value { font-size: 13px; }
    .center-label { font-size: 8px; letter-spacing: .1em; }
    .game-frame { height: min(58vh, 470px); min-height: 340px; border-radius: 15px; }
    .play-hint { padding: 10px 12px; gap: 8px; font-size: 8px; letter-spacing: .07em; }
    .play-hint .keycap { width: 23px; height: 23px; flex-basis: 23px; }
    footer { padding-top: 9px; font-size: 8px; letter-spacing: 0; }
  }
  @media (max-width: 370px) {
    .live-pill { font-size: 0; }
    .live-pill .live-dot { margin-right: 1px; }
    .center-label { letter-spacing: .03em; }
  }
</style>
</head>
<body>
  <main class="shell">
    <header>
      <div class="brand">
        <div class="brand-mark" aria-hidden="true">🍔</div>
        <div>
          <h1>HEARTBREAKER</h1>
          <div class="tagline">BREAKOUT, BUT MAKE IT SWEET</div>
        </div>
      </div>
      <div class="live-pill"><i class="live-dot"></i><span id="modeText">DEMO IN PROGRESS</span></div>
    </header>

    <section class="hud" aria-label="Game stats">
      <div class="stat">
        <span class="stat-icon">✦</span>
        <div><div class="stat-label">SCORE</div><div class="stat-value" id="score">000000</div></div>
      </div>
      <div class="center-label">STAGE <span id="stage">01</span> &nbsp;·&nbsp; HEARTS INCOMING</div>
      <div class="stat">
        <span class="stat-icon">♥</span>
        <div><div class="stat-label">LIVES</div><div class="stat-value" id="lives">♥ ♥ ♥</div></div>
      </div>
    </section>

    <div class="game-frame" id="gameFrame">
      <canvas id="game" aria-label="Breakout game with a hamburger paddle"></canvas>
      <div class="play-hint" id="hint"><span class="keycap">↗</span> CLICK OR PRESS A KEY TO PLAY</div>
    </div>

    <footer>
      <span><strong>Every brick pops into a heartstorm.</strong></span>
      <span class="controls-right">Move with mouse, ← →, or A / D</span>
    </footer>
  </main>

<script>
(() => {
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const frame = document.getElementById('gameFrame');
  const hint = document.getElementById('hint');
  const modeText = document.getElementById('modeText');
  const scoreEl = document.getElementById('score');
  const livesEl = document.getElementById('lives');
  const stageEl = document.getElementById('stage');

  let W = 0, H = 0, dpr = 1, cols = 10, rows = 5;
  let bricks = [], particles = [], trail = [];
  let score = 0, lives = 3, stage = 1;
  let manual = false;
  let keys = {};
  let last = performance.now();
  let paddleX = 0, targetX = 0, paddleY = 0;
  let ball = { x: 0, y: 0, vx: 220, vy: -320, r: 7 };

  const palettes = ['#ff77aa', '#ff9c89', '#ffcf78', '#bd9aff', '#75dfc8'];

  function resize() {
    const rect = frame.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);
    cols = Math.max(6, Math.min(10, Math.floor((W - 40) / 87)));
    paddleY = H - (W < 600 ? 54 : 57);
    bricks = [];
    for (let r = 0; r < rows; r++) {
      for (let c = 0; c < cols; c++) bricks.push({ r, c, alive: true });
    }
    paddleX = W / 2;
    targetX = paddleX;
    resetBall();
  }

  function resetBall() {
    ball.x = paddleX;
    ball.y = paddleY - 23;
    const dir = Math.random() < .5 ? -1 : 1;
    ball.vx = dir * (W < 600 ? 125 : 190);
    ball.vy = -(W < 600 ? 265 : 315);
    ball.r = Math.max(5.5, Math.min(7.5, W / 150));
    trail = [];
  }

  function brickRect(brick) {
    const margin = W < 600 ? 18 : 27;
    const gap = W < 600 ? 6 : 9;
    const bw = (W - 2 * margin - gap * (cols - 1)) / cols;
    const bh = W < 600 ? 19 : 25;
    const top = Math.max(68, H * .14);
    return { x: margin + brick.c * (bw + gap), y: top + brick.r * (bh + gap), w: bw, h: bh };
  }

  function heartPath(c, x, y, size) {
    c.beginPath();
    c.moveTo(x, y + size * .34);
    c.bezierCurveTo(x - size * .82, y - size * .16, x - size * .53, y - size * .82, x, y - size * .38);
    c.bezierCurveTo(x + size * .53, y - size * .82, x + size * .82, y - size * .16, x, y + size * .34);
    c.closePath();
  }

  function burst(x, y, color) {
    for (let i = 0; i < 29; i++) {
      const a = Math.random() * Math.PI * 2;
      const speed = 75 + Math.random() * 260;
      particles.push({
        x, y,
        vx: Math.cos(a) * speed,
        vy: Math.sin(a) * speed - 35,
        size: 3.2 + Math.random() * 4.7,
        color: Math.random() < .55 ? color : palettes[Math.floor(Math.random() * palettes.length)],
        life: .8 + Math.random() * .85,
        maxLife: 1.65,
        spin: (Math.random() - .5) * 8,
        angle: Math.random() * 6
      });
    }
    for (let i = 0; i < 8; i++) {
      const a = Math.random() * Math.PI * 2;
      const speed = 50 + Math.random() * 160;
      particles.push({
        x, y, vx: Math.cos(a) * speed, vy: Math.sin(a) * speed,
        size: 1.3 + Math.random() * 2, color: '#fff3e7',
        life: .45 + Math.random() * .35, maxLife: .8, spark: true
      });
    }
  }

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

  function drawBackground() {
    const bg = ctx.createLinearGradient(0, 0, 0, H);
    bg.addColorStop(0, '#282035');
    bg.addColorStop(.58, '#211b2d');
    bg.addColorStop(1, '#1b1727');
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, W, H);

    ctx.save();
    ctx.globalAlpha = .15;
    ctx.strokeStyle = '#786785';
    ctx.lineWidth = 1;
    const spacing = W < 600 ? 32 : 42;
    for (let x = 18; x < W; x += spacing) {
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
    }
    for (let y = 20; y < H; y += spacing) {
      ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
    }
    ctx.restore();

    const glow = ctx.createRadialGradient(W * .5, H * .36, 10, W * .5, H * .36, W * .55);
    glow.addColorStop(0, 'rgba(255,105,164,.075)');
    glow.addColorStop(1, 'rgba(255,105,164,0)');
    ctx.fillStyle = glow;
    ctx.fillRect(0, 0, W, H);

    ctx.save();
    ctx.strokeStyle = 'rgba(255,225,244,.16)';
    ctx.lineWidth = 1;
    rounded(ctx, 8, 8, W - 16, H - 16, 13);
    ctx.stroke();
    ctx.fillStyle = 'rgba(255,221,239,.55)';
    ctx.font = '800 9px system-ui, sans-serif';
    ctx.letterSpacing = '2px';
    ctx.fillText('SWEET SPOT', 23, 30);
    ctx.textAlign = 'right';
    ctx.fillStyle = 'rgba(255,156,194,.75)';
    ctx.fillText('♥  ♥  ♥', W - 23, 30);
    ctx.restore();
  }

  function drawBricks() {
    bricks.forEach(b => {
      if (!b.alive) return;
      const r = brickRect(b);
      const color = palettes[b.r % palettes.length];
      ctx.save();
      ctx.shadowColor = color;
      ctx.shadowBlur = 10;
      const g = ctx.createLinearGradient(r.x, r.y, r.x, r.y + r.h);
      g.addColorStop(0, color);
      g.addColorStop(1, shade(color, -24));
      ctx.fillStyle = g;
      rounded(ctx, r.x, r.y, r.w, r.h, Math.min(7, r.h * .32));
      ctx.fill();
      ctx.shadowBlur = 0;
      ctx.fillStyle = 'rgba(255,255,255,.24)';
      rounded(ctx, r.x + 4, r.y + 3, Math.max(4, r.w - 8), 3, 2);
      ctx.fill();
      ctx.globalAlpha = .55;
      ctx.fillStyle = '#fff9f1';
      heartPath(ctx, r.x + r.w / 2, r.y + r.h / 2 + 2, Math.min(7, r.h * .29));
      ctx.fill();
      ctx.restore();
    });
  }

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

  function drawPaddle() {
    const pw = Math.min(W * .22, W < 600 ? 104 : 124);
    const ph = W < 600 ? 22 : 26;
    const x = paddleX - pw / 2;
    const y = paddleY;
    ctx.save();
    ctx.shadowColor = 'rgba(255,151,89,.45)';
    ctx.shadowBlur = 18;

    // Bottom bun
    const bottom = ctx.createLinearGradient(0, y + ph * .65, 0, y + ph);
    bottom.addColorStop(0, '#e99745');
    bottom.addColorStop(1, '#b95e32');
    ctx.fillStyle = bottom;
    rounded(ctx, x + 3, y + ph * .61, pw - 6, ph * .35, ph * .18);
    ctx.fill();

    // Patty
    ctx.fillStyle = '#59333a';
    rounded(ctx, x, y + ph * .49, pw, ph * .27, 5);
    ctx.fill();

    // Wavy lettuce
    ctx.beginPath();
    ctx.moveTo(x + 2, y + ph * .57);
    for (let i = 0; i <= 8; i++) {
      const xx = x + 2 + (pw - 4) * i / 8;
      ctx.lineTo(xx, y + ph * (.49 + (i % 2 ? .1 : 0)));
    }
    ctx.lineTo(x + pw - 2, y + ph * .63);
    ctx.lineTo(x + 2, y + ph * .63);
    ctx.closePath();
    ctx.fillStyle = '#70c96c';
    ctx.fill();

    // Toasty top bun
    const bun = ctx.createLinearGradient(0, y, 0, y + ph * .59);
    bun.addColorStop(0, '#ffd486');
    bun.addColorStop(.68, '#f1a850');
    bun.addColorStop(1, '#cf753c');
    ctx.fillStyle = bun;
    ctx.beginPath();
    ctx.moveTo(x + 1, y + ph * .5);
    ctx.quadraticCurveTo(x + 1, y + ph * .02, x + pw * .5, y + ph * .03);
    ctx.quadraticCurveTo(x + pw - 1, y + ph * .02, x + pw - 1, y + ph * .5);
    ctx.quadraticCurveTo(x + pw * .5, y + ph * .63, x + 1, y + ph * .5);
    ctx.fill();
    ctx.strokeStyle = 'rgba(255,237,180,.65)';
    ctx.lineWidth = 1.2;
    ctx.stroke();

    ctx.shadowBlur = 0;
    ctx.fillStyle = 'rgba(255,239,192,.95)';
    const sesame = [[.25,.24],[.39,.15],[.55,.21],[.7,.14],[.8,.29]];
    sesame.forEach(([sx, sy]) => {
      ctx.save();
      ctx.translate(x + pw * sx, y + ph * sy);
      ctx.rotate(-.45);
      ctx.beginPath();
      ctx.ellipse(0, 0, 2.4, 1, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    });
    ctx.restore();
  }

  function drawBall() {
    trail.forEach((p, i) => {
      const alpha = (i / trail.length) * .23;
      ctx.beginPath();
      ctx.fillStyle = `rgba(255,132,184,${alpha})`;
      ctx.arc(p.x, p.y, ball.r * (i / trail.length), 0, Math.PI * 2);
      ctx.fill();
    });
    ctx.save();
    ctx.shadowColor = '#ff78b3';
    ctx.shadowBlur = 23;
    const g = ctx.createRadialGradient(ball.x - 2, ball.y - 2, 1, ball.x, ball.y, ball.r * 1.6);
    g.addColorStop(0, '#fff9ed');
    g.addColorStop(.35, '#ffc5da');
    g.addColorStop(1, '#ff6da9');
    ctx.fillStyle = g;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  function drawParticles() {
    particles.forEach(p => {
      const alpha = Math.max(0, Math.min(1, p.life / p.maxLife));
      ctx.save();
      ctx.globalAlpha = alpha;
      if (p.spark) {
        ctx.fillStyle = p.color;
        ctx.shadowColor = p.color;
        ctx.shadowBlur = 8;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
        ctx.fill();
      } else {
        ctx.translate(p.x, p.y);
        ctx.rotate(p.angle || 0);
        ctx.shadowColor = p.color;
        ctx.shadowBlur = 9;
        ctx.fillStyle = p.color;
        heartPath(ctx, 0, 0, p.size);
        ctx.fill();
      }
      ctx.restore();
    });
  }

  function update(dt) {
    if (manual) {
      const direction = (keys.ArrowRight || keys.d || keys.D ? 1 : 0) - (keys.ArrowLeft || keys.a || keys.A ? 1 : 0);
      targetX += direction * 520 * dt;
      if (direction) targetX = Math.max(55, Math.min(W - 55, targetX));
    } else {
      // The demo player smoothly tracks the ball on its way down.
      if (ball.vy > 0) targetX = ball.x + ball.vx * Math.min(.22, (paddleY - ball.y) / Math.max(1, ball.vy) * .45);
      targetX = Math.max(55, Math.min(W - 55, targetX));
    }
    paddleX += (targetX - paddleX) * Math.min(1, dt * (manual ? 12 : 6.8));

    trail.push({ x: ball.x, y: ball.y });
    if (trail.length > 8) trail.shift();
    ball.x += ball.vx * dt;
    ball.y += ball.vy * dt;

    if (ball.x < ball.r + 10) { ball.x = ball.r + 10; ball.vx = Math.abs(ball.vx); }
    if (ball.x > W - ball.r - 10) { ball.x = W - ball.r - 10; ball.vx = -Math.abs(ball.vx); }
    if (ball.y < 42) { ball.y = 42; ball.vy = Math.abs(ball.vy); }

    const pw = Math.min(W * .22, W < 600 ? 104 : 124);
    if (ball.vy > 0 && ball.y + ball.r >= paddleY && ball.y < paddleY + 16 &&
        ball.x >= paddleX - pw / 2 - ball.r && ball.x <= paddleX + pw / 2 + ball.r) {
      ball.y = paddleY - ball.r;
      const hit = (ball.x - paddleX) / (pw / 2);
      const speed = Math.min(470, Math.hypot(ball.vx, ball.vy) * 1.015);
      const angle = hit * .93;
      ball.vx = speed * Math.sin(angle);
      ball.vy = -Math.abs(speed * Math.cos(angle));
    }

    for (const b of bricks) {
      if (!b.alive) continue;
      const r = brickRect(b);
      if (ball.x + ball.r > r.x && ball.x - ball.r < r.x + r.w &&
          ball.y + ball.r > r.y && ball.y - ball.r < r.y + r.h) {
        b.alive = false;
        score += 100;
        burst(r.x + r.w / 2, r.y + r.h / 2, palettes[b.r % palettes.length]);
        const px = ball.x - r.x, py = ball.y - r.y;
        if (Math.min(px, r.w - px) < Math.min(py, r.h - py)) ball.vx *= -1;
        else ball.vy *= -1;
        break;
      }
    }

    if (ball.y > H + 20) {
      lives--;
      if (lives <= 0) {
        lives = 3;
        score = Math.floor(score * .8);
        resetBricks();
      }
      resetBall();
    }

    if (bricks.every(b => !b.alive)) {
      stage++;
      resetBricks();
      ball.vx *= 1.06;
      ball.vy *= 1.06;
    }

    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.life -= dt;
      p.x += p.vx * dt;
      p.y += p.vy * dt;
      if (!p.spark) {
        p.vy += 250 * dt;
        p.vx *= Math.pow(.985, dt * 60);
        p.angle += p.spin * dt;
      }
      if (p.life <= 0) particles.splice(i, 1);
    }
    scoreEl.textContent = String(score).padStart(6, '0');
    livesEl.textContent = '♥ '.repeat(lives).trim() || '—';
    stageEl.textContent = String(stage).padStart(2, '0');
  }

  function resetBricks() {
    bricks.forEach(b => b.alive = true);
  }

  function render() {
    drawBackground();
    drawBricks();
    drawParticles();
    drawPaddle();
    drawBall();
  }

  function loop(now) {
    const dt = Math.min(.033, Math.max(0, (now - last) / 1000));
    last = now;
    update(dt);
    render();
    requestAnimationFrame(loop);
  }

  function takeOver() {
    if (manual) return;
    manual = true;
    hint.classList.add('hidden');
    modeText.textContent = 'YOUR TURN';
  }

  frame.addEventListener('pointerdown', e => {
    takeOver();
    const rect = frame.getBoundingClientRect();
    targetX = Math.max(55, Math.min(W - 55, e.clientX - rect.left));
    if (e.pointerType === 'touch') e.preventDefault();
  });
  frame.addEventListener('pointermove', e => {
    if (!manual || e.pointerType === 'touch') return;
    const rect = frame.getBoundingClientRect();
    targetX = Math.max(55, Math.min(W - 55, e.clientX - rect.left));
  });
  window.addEventListener('keydown', e => {
    const key = e.key;
    if (['ArrowLeft', 'ArrowRight', ' ', 'a', 'A', 'd', 'D'].includes(key)) e.preventDefault();
    takeOver();
    keys[key] = true;
  });
  window.addEventListener('keyup', e => { keys[e.key] = false; });

  new ResizeObserver(resize).observe(frame);
  resize();
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>
Reasoning trace (6,005 tokens)
**Planning game details**

I need to create a polished game that plays itself behind a "Click or press a key to start" overlay. The demo should be engaging, showcasing gameplay like a breakout-style game with burger paddles and exploding bricks that release hearts. It's important to have a responsive canvas, and visually appealing graphics are essential. The simulation must start right away while making it interactive for the user to take over if they wish. I’m considering a dark navy and cream pink color scheme with striking neon elements for a compelling look.**Adjusting UI and canvas**

I’m refining the UI to include a DOM header and a better canvas size of 1120x620 with rounded edges. I need to ensure the canvas fits the actual width using CSS while maintaining the aspect ratio. For desktops, I aim for an internal logical size of 1000x560. Then, I’ll adapt for mobile, but the game may look quite small at a width of 375 pixels, so I’ll consider a vertical adjustment. I plan to keep the coordinates dynamic based on the canvas dimensions for optimal layout.**Refining game board aspect ratio**

I’m updating the CSS for the game board to use a 16/10 aspect ratio for desktops, while mobile can adopt a 0.68 ratio for a vertical layout. It’s crucial to ensure that the game coordinates are normalized to make everything align correctly. I need to define the game's coordinate system based on the canvas dimensions, which includes responsive grids for the bricks and adapting the width for the paddle and ball. This all needs to work harmoniously together!**Finalizing game setup**

I’ve fixed the brick layout and populated everything. The game starts with the ball's location set to launch from the center horizontally and just above the paddle, flying upwards. For taking a screenshot, I think about giving it about three seconds of collision time since the ball might take a moment to hit the bricks. Overall, I’m aiming for a smooth start!**Planning the game design**

I'm fleshing out the game mechanics and visuals. The paddle will be a hamburger design, which is kind of fun! I’m considering using a canvas for drawing the paddle and the bricks, which feature tinted variations and heart icons. It’s all coming together with details like a static header titled "HEARTBREAKER" and a playful subhead "BREAKOUT, BUT MAKE IT SWEET." The game will include scoring, levels, and lives, and everything should be responsive and visually appealing. Let’s get creative!**Finalizing game mechanics**

I’m figuring out the game reset mechanics; should the ball reset automatically if I miss, while keeping the bricks intact? Maybe having three lives would keep it interesting. The AI paddle should smoothly follow the ball as it descends, and the bounce angle will depend on where the ball hits. I also want to ensure that clicking doesn't block gameplay, so I'll place an overlay banner carefully. It could be centered at the bottom with some nice gradient text, which sounds appealing!