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 52 s
Cost
$0.0064
Output tokens
12,639
Spent thinking
3,760 tokens
Speed
116.8 tok/s
Console errors
0
First token after
3.9 s
First visible text after
47.4 s
Prompt tokens
508
Code size
25.4 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
Build a polished Breakout (brick breaker) game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A paddle controlled with the mouse and with the left/right arrow keys. - The bounce angle off the paddle depends on where the ball hits it (center = straight up, edges = sharp angle). - One level with at least 6 rows of bricks, mixing normal bricks and tougher bricks that need 2 hits and visibly crack after the first one. - Some bricks visibly carry a power-up icon; when broken they drop it, and catching it with the paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls). - The ball must never pass through bricks or the paddle. - Particles when a brick breaks. - 3 lives, score displayed. Game over and victory screens with a restart option.
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 · 714 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Breakout — Neon Arcade</title>
<style>
  :root {
    color-scheme: dark;
    --ink: #eef4ff;
    --muted: #8493b1;
    --cyan: #64e9ff;
    --panel: rgba(16, 25, 48, .88);
  }
  * { box-sizing: border-box; }
  html, body { margin: 0; min-height: 100%; }
  body {
    min-height: 100vh;
    padding: 23px 24px 18px;
    color: var(--ink);
    background:
      radial-gradient(ellipse at 50% 38%, rgba(27, 49, 94, .43), transparent 54%),
      radial-gradient(circle at 12% 5%, rgba(34, 68, 122, .2), transparent 30%),
      #080d19;
    font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    display: flex;
    justify-content: center;
    overflow-x: hidden;
  }
  .app { width: min(1120px, 100%); }
  .topbar {
    height: 72px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    margin-bottom: 13px;
  }
  .brand { display: flex; align-items: center; gap: 13px; min-width: 0; }
  .mark {
    width: 42px; height: 42px; border-radius: 13px;
    display: grid; place-items: center;
    background: linear-gradient(145deg, #182a4d, #10182c);
    border: 1px solid rgba(122, 183, 255, .25);
    box-shadow: inset 0 1px rgba(255,255,255,.08), 0 5px 20px rgba(0,0,0,.25);
  }
  .mark svg { width: 25px; height: 25px; }
  h1 { margin: 0; font-size: 20px; letter-spacing: .12em; line-height: 1.1; font-weight: 850; }
  .subtitle { margin-top: 5px; font-size: 10px; letter-spacing: .2em; color: #7f91b2; font-weight: 700; }
  .stats { display: flex; gap: 9px; align-items: center; }
  .stat {
    min-width: 92px; height: 49px; padding: 7px 13px;
    display: flex; flex-direction: column; justify-content: center;
    border-radius: 11px;
    background: linear-gradient(180deg, rgba(24,35,63,.92), rgba(14,22,41,.92));
    border: 1px solid rgba(127, 157, 207, .15);
    box-shadow: 0 4px 15px rgba(0,0,0,.14);
  }
  .stat span { font-size: 9px; color: #8494b3; font-weight: 800; letter-spacing: .15em; }
  .stat strong { margin-top: 2px; font-size: 15px; letter-spacing: .06em; font-variant-numeric: tabular-nums; }
  .stat.power { min-width: 112px; }
  .stat.power strong { color: #75eaff; font-size: 12px; }

  .board-wrap {
    position: relative;
    border-radius: 17px;
    padding: 7px;
    background: linear-gradient(135deg, rgba(112,168,238,.36), rgba(49,73,119,.12) 38%, rgba(105,93,204,.25));
    box-shadow: 0 22px 60px rgba(0,0,0,.38), 0 0 45px rgba(43,95,177,.1);
  }
  .board-inner {
    overflow: hidden;
    border-radius: 11px;
    background: #090f20;
    border: 1px solid rgba(148,187,255,.2);
    position: relative;
  }
  canvas {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1000 / 520;
    touch-action: none;
    cursor: crosshair;
  }
  .bottomline {
    display: flex; justify-content: space-between; align-items: center;
    gap: 16px; padding: 15px 5px 0;
    color: #8291ad; font-size: 11px; letter-spacing: .035em;
  }
  .keys { display: flex; gap: 7px; align-items: center; }
  kbd {
    color: #c4d1e8; background: #151e32; border: 1px solid #2b3853;
    border-bottom-width: 2px; padding: 3px 7px; border-radius: 5px;
    font: 700 10px ui-sans-serif, system-ui, sans-serif;
  }
  .tip { text-align: right; }
  .tip b { color: #cbd8ee; font-weight: 650; }
  @media (max-width: 620px) {
    body { padding: 13px 12px 15px; }
    .topbar { height: auto; min-height: 61px; margin-bottom: 12px; gap: 8px; align-items: flex-start; }
    .mark { width: 36px; height: 36px; border-radius: 11px; }
    h1 { font-size: 16px; }
    .subtitle { font-size: 8px; letter-spacing: .14em; }
    .stats { gap: 5px; }
    .stat { min-width: 55px; height: 43px; padding: 5px 7px; border-radius: 9px; }
    .stat span { font-size: 7px; letter-spacing: .09em; }
    .stat strong { font-size: 12px; }
    .stat.power { min-width: 68px; }
    .stat.power strong { font-size: 10px; }
    canvas { height: 225px; }
    .board-wrap { padding: 5px; border-radius: 13px; }
    .board-inner { border-radius: 9px; }
    .bottomline { padding: 12px 2px 0; font-size: 9px; }
    .tip { max-width: 145px; }
  }
</style>
</head>
<body>
<main class="app">
  <header class="topbar">
    <div class="brand">
      <div class="mark" aria-hidden="true">
        <svg viewBox="0 0 28 28" fill="none">
          <path d="M5 8.5h18M8 5v7M14 5v7M20 5v7" stroke="#67E7FF" stroke-width="2" stroke-linecap="round"/>
          <path d="M6 19.5h16" stroke="#9A85FF" stroke-width="3" stroke-linecap="round"/>
          <circle cx="14" cy="15" r="2.3" fill="#FFF" stroke="#74E9FF" stroke-width="1.4"/>
        </svg>
      </div>
      <div>
        <h1>BREAKOUT</h1>
        <div class="subtitle">NEON ARCADE&nbsp; / &nbsp;LEVEL 01</div>
      </div>
    </div>
    <div class="stats" aria-label="Game statistics">
      <div class="stat"><span>SCORE</span><strong id="score">00000</strong></div>
      <div class="stat"><span>LIVES</span><strong id="lives">● ● ●</strong></div>
      <div class="stat power"><span>POWER-UP</span><strong id="power">—</strong></div>
    </div>
  </header>

  <section class="board-wrap" aria-label="Breakout game board">
    <div class="board-inner">
      <canvas id="game" width="1000" height="520" aria-label="Breakout game. Move the paddle with your mouse or left and right arrow keys."></canvas>
    </div>
  </section>

  <footer class="bottomline">
    <div class="keys"><kbd>←</kbd><kbd>→</kbd><span>move paddle</span><span style="margin-left:8px">·</span><span>mouse works too</span></div>
    <div class="tip"><b>Catch falling capsules</b> for a wider paddle or multi-ball.</div>
  </footer>
</main>
<script>
(() => {
  "use strict";

  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const W = 1000, H = 520;
  const scoreEl = document.getElementById("score");
  const livesEl = document.getElementById("lives");
  const powerEl = document.getElementById("power");

  let dpr = Math.min(window.devicePixelRatio || 1, 2);
  function sizeCanvas() {
    dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = W * dpr;
    canvas.height = H * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  sizeCanvas();
  window.addEventListener("resize", sizeCanvas);

  const palette = ["#ff638d", "#ff9c56", "#ffd15e", "#64e6b3", "#57cfff", "#9b8cff"];
  const stars = Array.from({length: 72}, (_, i) => ({
    x: (i * 173.7 + 19) % W,
    y: (i * 91.3 + 37) % H,
    r: .55 + (i % 4) * .22,
    a: .12 + (i % 5) * .055
  }));

  let bricks = [], balls = [], drops = [], particles = [];
  let score = 0, lives = 3, state = "playing";
  let paddle = { x: W / 2, y: 478, w: 112, h: 12 };
  let manual = false, mouseX = W / 2, leftDown = false, rightDown = false;
  let wideTimer = 0, elapsed = 0, last = performance.now(), accumulator = 0;
  let restartButton = { x: 390, y: 316, w: 220, h: 48 };

  function makeBricks() {
    const tough = new Set(["1-1", "1-6", "2-3", "2-7", "3-0", "3-5", "4-2", "4-8"]);
    const powers = {
      "0-1": "wide", "2-7": "multi", "4-2": "wide", "5-6": "multi"
    };
    const bw = 96, bh = 23, gapX = 8, gapY = 7;
    const startX = (W - (9 * bw + 8 * gapX)) / 2;
    for (let row = 0; row < 6; row++) {
      for (let col = 0; col < 9; col++) {
        const key = `${row}-${col}`;
        bricks.push({
          x: startX + col * (bw + gapX),
          y: 82 + row * (bh + gapY),
          w: bw, h: bh, hp: tough.has(key) ? 2 : 1,
          maxHp: tough.has(key) ? 2 : 1,
          color: palette[row], power: powers[key] || null
        });
      }
    }
  }

  function makeBall(x = W / 2, y = 451, vx = 155, vy = -300) {
    return { x, y, vx, vy, r: 6.5, px: x, py: y };
  }

  function resetGame() {
    bricks = []; drops = []; particles = [];
    makeBricks();
    score = 0; lives = 3; wideTimer = 0;
    paddle = { x: W / 2, y: 478, w: 112, h: 12 };
    balls = [makeBall()];
    state = "playing";
    manual = false;
    leftDown = rightDown = false;
    updateHUD();
  }

  function updateHUD() {
    scoreEl.textContent = String(score).padStart(5, "0");
    livesEl.textContent = lives > 0 ? Array.from({length: 3}, (_, i) => i < lives ? "●" : "○").join(" ") : "○ ○ ○";
    powerEl.textContent = wideTimer > 0 ? `WIDE ${Math.ceil(wideTimer)}s` : "—";
  }

  function burst(x, y, color, count = 16) {
    for (let i = 0; i < count; i++) {
      const angle = Math.random() * Math.PI * 2;
      const speed = 45 + Math.random() * 190;
      particles.push({
        x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed,
        life: .38 + Math.random() * .55, max: .93,
        size: 1.3 + Math.random() * 3, color
      });
    }
  }

  function addDrop(type, x, y) {
    drops.push({ type, x, y, vy: 128, phase: Math.random() * 6 });
  }

  function applyPower(type) {
    if (type === "wide") {
      wideTimer = 10;
      paddle.w = 174;
    } else {
      const source = balls[0] || makeBall();
      const speed = Math.hypot(source.vx, source.vy);
      for (const angle of [-.36, .36]) {
        const base = Math.atan2(source.vx, -source.vy) + angle;
        balls.push(makeBall(source.x, source.y, Math.sin(base) * speed, -Math.cos(base) * speed));
      }
    }
    updateHUD();
  }

  function hitBrick(ball, brick) {
    const nearestX = Math.max(brick.x, Math.min(ball.x, brick.x + brick.w));
    const nearestY = Math.max(brick.y, Math.min(ball.y, brick.y + brick.h));
    const dx = ball.x - nearestX, dy = ball.y - nearestY;
    if (dx * dx + dy * dy > ball.r * ball.r) return false;

    const wasAbove = ball.py + ball.r <= brick.y + 2;
    const wasBelow = ball.py - ball.r >= brick.y + brick.h - 2;
    const wasLeft = ball.px + ball.r <= brick.x + 2;
    const wasRight = ball.px - ball.r >= brick.x + brick.w - 2;
    if (wasAbove || wasBelow) ball.vy *= -1;
    else if (wasLeft || wasRight) ball.vx *= -1;
    else {
      const overlapX = ball.r + Math.min(Math.abs(ball.x - brick.x), Math.abs(ball.x - (brick.x + brick.w)));
      const overlapY = ball.r + Math.min(Math.abs(ball.y - brick.y), Math.abs(ball.y - (brick.y + brick.h)));
      if (overlapX < overlapY) ball.vx *= -1;
      else ball.vy *= -1;
    }

    // Nudge the ball clear so it cannot repeatedly register against the same face.
    ball.x += Math.sign(ball.vx) * 1.2;
    ball.y += Math.sign(ball.vy) * 1.2;

    brick.hp--;
    if (brick.hp <= 0) {
      score += 100;
      burst(brick.x + brick.w / 2, brick.y + brick.h / 2, brick.color, 19);
      if (brick.power) addDrop(brick.power, brick.x + brick.w / 2, brick.y + brick.h / 2);
      bricks.splice(bricks.indexOf(brick), 1);
      if (!bricks.length) state = "victory";
    } else {
      score += 25;
      burst(ball.x, ball.y, "#fff0a5", 7);
    }
    updateHUD();
    return true;
  }

  function movePaddle(dt) {
    const targetWidth = wideTimer > 0 ? 174 : 112;
    paddle.w += (targetWidth - paddle.w) * Math.min(1, dt * 8);
    let target = paddle.x;
    if (manual) {
      if (leftDown) target -= 650 * dt;
      if (rightDown) target += 650 * dt;
      if (!leftDown && !rightDown) target = mouseX;
    } else {
      let candidate = null;
      for (const b of balls) {
        if (b.vy > 0 && (!candidate || b.y > candidate.y)) candidate = b;
      }
      target = candidate ? candidate.x : W / 2;
    }
    const half = paddle.w / 2;
    target = Math.max(half + 12, Math.min(W - half - 12, target));
    const maxMove = (manual ? 900 : 1100) * dt;
    paddle.x += Math.max(-maxMove, Math.min(maxMove, target - paddle.x));
  }

  function update(dt) {
    if (state !== "playing") return;
    elapsed += dt;
    if (wideTimer > 0) {
      wideTimer = Math.max(0, wideTimer - dt);
      if (wideTimer === 0) paddle.w = 112;
    }
    movePaddle(dt);

    for (const b of balls) {
      b.px = b.x; b.py = b.y;
      b.x += b.vx * dt; b.y += b.vy * dt;

      if (b.x - b.r < 14) { b.x = 14 + b.r; b.vx = Math.abs(b.vx); }
      if (b.x + b.r > W - 14) { b.x = W - 14 - b.r; b.vx = -Math.abs(b.vx); }
      if (b.y - b.r < 56) { b.y = 56 + b.r; b.vy = Math.abs(b.vy); }

      if (b.vy > 0 && b.py + b.r <= paddle.y + 3 &&
          b.y + b.r >= paddle.y && b.x + b.r >= paddle.x - paddle.w / 2 &&
          b.x - b.r <= paddle.x + paddle.w / 2) {
        const offset = Math.max(-1, Math.min(1, (b.x - paddle.x) / (paddle.w / 2)));
        const angle = offset * 1.13;
        const speed = Math.min(440, Math.max(310, Math.hypot(b.vx, b.vy) * 1.015));
        b.vx = Math.sin(angle) * speed;
        b.vy = -Math.cos(angle) * speed;
        b.y = paddle.y - b.r - .5;
        burst(b.x, paddle.y, "#72eaff", 4);
      }

      for (let i = 0; i < bricks.length; i++) {
        if (hitBrick(b, bricks[i])) break;
      }
    }

    balls = balls.filter(b => b.y - b.r < H + 5);
    if (!balls.length && state === "playing") {
      lives--;
      if (lives <= 0) state = "gameover";
      else balls = [makeBall(paddle.x, paddle.y - 26, 155 * (Math.random() < .5 ? -1 : 1), -300)];
      updateHUD();
    }

    for (const d of drops) {
      d.y += d.vy * dt;
      d.phase += dt * 5;
      if (d.y + 11 >= paddle.y && d.y - 11 <= paddle.y + paddle.h &&
          d.x >= paddle.x - paddle.w / 2 - 8 && d.x <= paddle.x + paddle.w / 2 + 8) {
        applyPower(d.type);
        d.caught = true;
        burst(d.x, paddle.y, d.type === "wide" ? "#66eaff" : "#b795ff", 12);
      }
    }
    drops = drops.filter(d => !d.caught && d.y < H + 20);

    for (const p of particles) {
      p.x += p.vx * dt; p.y += p.vy * dt;
      p.vx *= Math.pow(.18, dt);
      p.vy *= Math.pow(.18, dt);
      p.life -= dt;
    }
    particles = particles.filter(p => p.life > 0);
    updateHUD();
  }

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

  function drawBackground() {
    const grad = ctx.createLinearGradient(0, 0, 0, H);
    grad.addColorStop(0, "#0d1830");
    grad.addColorStop(.55, "#0a1123");
    grad.addColorStop(1, "#080d1b");
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, W, H);

    const glow = ctx.createRadialGradient(500, 220, 10, 500, 220, 490);
    glow.addColorStop(0, "rgba(54,92,163,.15)");
    glow.addColorStop(1, "rgba(5,10,23,0)");
    ctx.fillStyle = glow;
    ctx.fillRect(0, 0, W, H);

    ctx.strokeStyle = "rgba(104,145,206,.045)";
    ctx.lineWidth = 1;
    for (let x = 20; x < W; x += 40) {
      ctx.beginPath(); ctx.moveTo(x, 56); ctx.lineTo(x, H - 15); ctx.stroke();
    }
    for (let y = 80; y < H; y += 40) {
      ctx.beginPath(); ctx.moveTo(14, y); ctx.lineTo(W - 14, y); ctx.stroke();
    }
    for (const s of stars) {
      ctx.globalAlpha = s.a;
      ctx.fillStyle = "#d5e7ff";
      ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.fill();
    }
    ctx.globalAlpha = 1;

    // Board edge lighting
    const edge = ctx.createLinearGradient(15, 0, W - 15, 0);
    edge.addColorStop(0, "rgba(77,142,225,.25)");
    edge.addColorStop(.5, "rgba(104,207,255,.12)");
    edge.addColorStop(1, "rgba(142,112,255,.23)");
    ctx.strokeStyle = edge; ctx.lineWidth = 1;
    roundRect(13.5, 55.5, W - 27, H - 70, 12); ctx.stroke();
  }

  function drawBricks() {
    for (const b of bricks) {
      ctx.save();
      ctx.shadowColor = b.color;
      ctx.shadowBlur = 10;
      roundRect(b.x, b.y, b.w, b.h, 5);
      const g = ctx.createLinearGradient(0, b.y, 0, b.y + b.h);
      g.addColorStop(0, lighten(b.color, 19));
      g.addColorStop(.16, b.color);
      g.addColorStop(1, darken(b.color, 28));
      ctx.fillStyle = g; ctx.fill();
      ctx.shadowBlur = 0;

      ctx.fillStyle = "rgba(255,255,255,.20)";
      roundRect(b.x + 4, b.y + 3, b.w - 8, 2, 1); ctx.fill();
      ctx.strokeStyle = "rgba(255,255,255,.27)";
      ctx.lineWidth = 1;
      roundRect(b.x + .5, b.y + .5, b.w - 1, b.h - 1, 5); ctx.stroke();

      if (b.maxHp === 2 && b.hp === 2) {
        ctx.fillStyle = "rgba(12,18,39,.42)";
        roundRect(b.x + b.w - 22, b.y + 5, 15, 13, 4); ctx.fill();
        ctx.fillStyle = "rgba(255,255,255,.9)";
        ctx.font = "800 8px ui-sans-serif,system-ui";
        ctx.textAlign = "center"; ctx.textBaseline = "middle";
        ctx.fillText("II", b.x + b.w - 14.5, b.y + 11.5);
      } else if (b.maxHp === 2) {
        ctx.strokeStyle = "rgba(23,25,50,.85)";
        ctx.lineWidth = 1.5;
        ctx.beginPath();
        ctx.moveTo(b.x + b.w * .35, b.y + 3);
        ctx.lineTo(b.x + b.w * .43, b.y + 10);
        ctx.lineTo(b.x + b.w * .37, b.y + 15);
        ctx.lineTo(b.x + b.w * .51, b.y + 21);
        ctx.moveTo(b.x + b.w * .43, b.y + 10);
        ctx.lineTo(b.x + b.w * .58, b.y + 7);
        ctx.stroke();
      }
      if (b.power) {
        ctx.fillStyle = "rgba(9,17,36,.64)";
        roundRect(b.x + 5, b.y + 4, 19, 15, 5); ctx.fill();
        drawPowerIcon(b.power, b.x + 14.5, b.y + 11.5, 6.2, true);
      }
      ctx.restore();
    }
  }

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

  function drawPowerIcon(type, x, y, size, tiny = false) {
    ctx.save();
    ctx.translate(x, y);
    ctx.strokeStyle = type === "wide" ? "#74edff" : "#d3a5ff";
    ctx.fillStyle = ctx.strokeStyle;
    ctx.lineWidth = tiny ? 1.5 : 2;
    ctx.lineCap = "round"; ctx.lineJoin = "round";
    if (type === "wide") {
      ctx.beginPath();
      ctx.moveTo(-size, 0); ctx.lineTo(size, 0);
      ctx.moveTo(-size, 0); ctx.lineTo(-size * .45, -size * .55);
      ctx.moveTo(-size, 0); ctx.lineTo(-size * .45, size * .55);
      ctx.moveTo(size, 0); ctx.lineTo(size * .45, -size * .55);
      ctx.moveTo(size, 0); ctx.lineTo(size * .45, size * .55);
      ctx.stroke();
    } else {
      for (const ox of [-size * .55, 0, size * .55]) {
        ctx.beginPath(); ctx.arc(ox, 0, size * .28, 0, Math.PI * 2); ctx.fill();
      }
    }
    ctx.restore();
  }

  function drawPaddleAndBalls() {
    const x = paddle.x - paddle.w / 2;
    ctx.save();
    ctx.shadowColor = wideTimer > 0 ? "#5ce9ff" : "#8b7cff";
    ctx.shadowBlur = 23;
    const g = ctx.createLinearGradient(x, paddle.y, x, paddle.y + paddle.h);
    g.addColorStop(0, "#e7fbff");
    g.addColorStop(.28, wideTimer > 0 ? "#55e8ff" : "#8aa9ff");
    g.addColorStop(1, wideTimer > 0 ? "#148bb9" : "#6356d8");
    roundRect(x, paddle.y, paddle.w, paddle.h, 6);
    ctx.fillStyle = g; ctx.fill();
    ctx.shadowBlur = 0;
    ctx.fillStyle = "rgba(255,255,255,.78)";
    roundRect(x + 12, paddle.y + 2, paddle.w - 24, 2, 1); ctx.fill();
    ctx.restore();

    for (const b of balls) {
      ctx.save();
      ctx.shadowColor = "#70e9ff"; ctx.shadowBlur = 19;
      const ballGrad = ctx.createRadialGradient(b.x - 2, b.y - 2, 1, b.x, b.y, b.r * 1.5);
      ballGrad.addColorStop(0, "#ffffff");
      ballGrad.addColorStop(.48, "#c8f9ff");
      ballGrad.addColorStop(1, "#55c6ff");
      ctx.fillStyle = ballGrad;
      ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2); ctx.fill();
      ctx.restore();
    }
  }

  function drawDrops() {
    for (const d of drops) {
      const color = d.type === "wide" ? "#53e6ff" : "#c294ff";
      const bob = Math.sin(d.phase) * 2;
      ctx.save();
      ctx.shadowColor = color; ctx.shadowBlur = 15;
      const g = ctx.createLinearGradient(d.x - 13, d.y - 11 + bob, d.x + 13, d.y + 11 + bob);
      g.addColorStop(0, "rgba(255,255,255,.98)");
      g.addColorStop(.22, color);
      g.addColorStop(1, d.type === "wide" ? "#168bb8" : "#7751c2");
      roundRect(d.x - 14, d.y - 11 + bob, 28, 22, 8);
      ctx.fillStyle = g; ctx.fill();
      ctx.strokeStyle = "rgba(255,255,255,.65)"; ctx.lineWidth = 1;
      roundRect(d.x - 13.5, d.y - 10.5 + bob, 27, 21, 7); ctx.stroke();
      ctx.shadowBlur = 0;
      drawPowerIcon(d.type, d.x, d.y + bob, 8, false);
      ctx.restore();
    }
  }

  function drawParticles() {
    for (const p of particles) {
      ctx.globalAlpha = Math.max(0, p.life / p.max);
      ctx.fillStyle = p.color;
      ctx.beginPath(); ctx.arc(p.x, p.y, p.size * Math.min(1, p.life * 2), 0, Math.PI * 2); ctx.fill();
    }
    ctx.globalAlpha = 1;
  }

  function drawTopLabels() {
    ctx.fillStyle = "#8ea3c8";
    ctx.font = "800 10px ui-sans-serif,system-ui";
    ctx.textBaseline = "middle";
    ctx.textAlign = "left";
    ctx.fillText("◆  ARCADE CIRCUIT", 30, 31);
    ctx.textAlign = "right";
    ctx.fillText("LEVEL 01  /  54 BRICKS", W - 30, 31);

    ctx.save();
    ctx.textAlign = "center";
    ctx.font = "800 9px ui-sans-serif,system-ui";
    const label = manual ? "PLAYER CONTROL   ·   MOVE WITH MOUSE OR ← →" : "DEMO MODE   ·   CLICK OR PRESS A KEY TO START";
    const tw = ctx.measureText(label).width + 28;
    roundRect(W / 2 - tw / 2, 19, tw, 24, 12);
    ctx.fillStyle = "rgba(20,34,61,.92)"; ctx.fill();
    ctx.strokeStyle = manual ? "rgba(103,232,255,.35)" : "rgba(139,158,201,.25)"; ctx.stroke();
    ctx.fillStyle = manual ? "#8defff" : "#b4c2dc";
    ctx.textBaseline = "middle"; ctx.fillText(label, W / 2, 31);
    ctx.restore();

    if (wideTimer > 0) {
      ctx.textAlign = "left"; ctx.textBaseline = "middle";
      ctx.font = "700 10px ui-sans-serif,system-ui";
      ctx.fillStyle = "#6beaff";
      ctx.fillText(`WIDE PADDLE  ${Math.ceil(wideTimer)}s`, 30, 66);
      ctx.fillStyle = "rgba(104,225,255,.22)";
      roundRect(30, 74, 110, 3, 2); ctx.fill();
      ctx.fillStyle = "#65eaff";
      roundRect(30, 74, 110 * wideTimer / 10, 3, 2); ctx.fill();
    }
  }

  function drawEndScreen() {
    if (state === "playing") return;
    ctx.fillStyle = "rgba(5,9,20,.72)";
    ctx.fillRect(14, 56, W - 28, H - 71);
    const boxX = 310, boxY = 180, boxW = 380, boxH = 205;
    ctx.save();
    ctx.shadowColor = state === "victory" ? "#5ce8c0" : "#ff668b";
    ctx.shadowBlur = 32;
    roundRect(boxX, boxY, boxW, boxH, 18);
    const grad = ctx.createLinearGradient(boxX, boxY, boxX + boxW, boxY + boxH);
    grad.addColorStop(0, "rgba(23,37,67,.98)");
    grad.addColorStop(1, "rgba(15,22,43,.98)");
    ctx.fillStyle = grad; ctx.fill();
    ctx.shadowBlur = 0;
    ctx.strokeStyle = state === "victory" ? "rgba(97,239,198,.55)" : "rgba(255,106,143,.5)";
    ctx.lineWidth = 1.2; ctx.stroke();
    ctx.textAlign = "center"; ctx.textBaseline = "middle";
    ctx.fillStyle = state === "victory" ? "#75f2c7" : "#ff7898";
    ctx.font = "900 11px ui-sans-serif,system-ui";
    ctx.fillText(state === "victory" ? "CIRCUIT CLEARED" : "OUT OF LIVES", W / 2, 218);
    ctx.fillStyle = "#f3f6ff";
    ctx.font = "900 30px ui-sans-serif,system-ui";
    ctx.fillText(state === "victory" ? "NICE WORK!" : "GAME OVER", W / 2, 258);
    ctx.fillStyle = "#a6b5d1";
    ctx.font = "600 12px ui-sans-serif,system-ui";
    ctx.fillText(`FINAL SCORE  ${String(score).padStart(5, "0")}`, W / 2, 286);
    restartButton = { x: W / 2 - 110, y: 316, w: 220, h: 48 };
    roundRect(restartButton.x, restartButton.y, restartButton.w, restartButton.h, 12);
    const bg = ctx.createLinearGradient(0, 316, 0, 364);
    bg.addColorStop(0, "#66e5f5"); bg.addColorStop(1, "#478bdc");
    ctx.fillStyle = bg; ctx.fill();
    ctx.fillStyle = "#08152d"; ctx.font = "900 12px ui-sans-serif,system-ui";
    ctx.fillText("PLAY AGAIN   ↻", W / 2, 340);
    ctx.restore();
  }

  function render() {
    ctx.clearRect(0, 0, W, H);
    drawBackground();
    drawTopLabels();
    drawBricks();
    drawDrops();
    drawParticles();
    drawPaddleAndBalls();
    drawEndScreen();
  }

  function loop(now) {
    const dt = Math.min(.04, Math.max(0, (now - last) / 1000));
    last = now;
    accumulator += dt;
    const step = 1 / 120;
    while (accumulator >= step) {
      update(step);
      accumulator -= step;
    }
    render();
    requestAnimationFrame(loop);
  }

  function pointerPosition(e) {
    const rect = canvas.getBoundingClientRect();
    return {
      x: (e.clientX - rect.left) / rect.width * W,
      y: (e.clientY - rect.top) / rect.height * H
    };
  }
  function startManual() {
    if (state === "playing") manual = true;
  }
  canvas.addEventListener("pointermove", e => {
    if (e.pointerType === "mouse" || e.buttons || e.pointerType === "touch") {
      const p = pointerPosition(e);
      mouseX = Math.max(paddle.w / 2 + 12, Math.min(W - paddle.w / 2 - 12, p.x));
      startManual();
    }
  });
  canvas.addEventListener("pointerdown", e => {
    const p = pointerPosition(e);
    if (state !== "playing") {
      if (p.x >= restartButton.x && p.x <= restartButton.x + restartButton.w &&
          p.y >= restartButton.y && p.y <= restartButton.y + restartButton.h) resetGame();
      return;
    }
    startManual();
    mouseX = Math.max(paddle.w / 2 + 12, Math.min(W - paddle.w / 2 - 12, p.x));
    canvas.setPointerCapture?.(e.pointerId);
  });

  window.addEventListener("keydown", e => {
    if (["ArrowLeft", "ArrowRight", " "].includes(e.key)) e.preventDefault();
    if (state !== "playing" && (e.key === "Enter" || e.key.toLowerCase() === "r" || e.key === " ")) {
      resetGame(); return;
    }
    startManual();
    if (e.key === "ArrowLeft") leftDown = true;
    if (e.key === "ArrowRight") rightDown = true;
  });
  window.addEventListener("keyup", e => {
    if (e.key === "ArrowLeft") leftDown = false;
    if (e.key === "ArrowRight") rightDown = false;
  });
  window.addEventListener("blur", () => { leftDown = rightDown = false; });

  resetGame();
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>
Reasoning trace (3,760 tokens)
**Designing interactive demo**

I’m working on producing a visually polished, self-playing demo that's interactive. The design calls for a responsive canvas at 1280x800 pixels. I’m considering styling the UI around the canvas area. The demo will automatically run but also display a "Click or press a key to start" message. It’ll be cool to have AI take control initially, with the option for the user to start manual mode whenever they wish. I'll ensure the canvas has an appealing neon arcade look!**Calculating canvas design**

I'm working on rendering the canvas for a game with a width of 100%, maximizing to 1160 pixels. My logical dimensions are set at 1000x620, keeping it responsive. With a total page height of 800 pixels, I’m calculating various components to ensure everything fits nicely, like the header and footer. I'm considering a canvas height of 520 pixels to keep good proportions and a visually appealing starfield background. I need to ensure the outer frame complements the aesthetics!**Analyzing canvas dimensions**

I'm sorting out the dimensions for a canvas. The total height with the footer and margins adds up to about 780. With a main width of 351 at a 375 pixel width, I’m noticing the height of 183 is quite small, making the game appear too tiny. I think adjusting the mobile aspect ratio and scaling should help, but I have to be cautious about non-uniform scaling issues. Keeping the canvas responsive is key!**Adjusting mobile canvas settings**

I'm thinking about using CSS for the mobile canvas with an aspect ratio of 1.15 and dynamically altering the logical world dimensions based on the actual CSS. For desktops, the world dimensions are 1000 by 520, but for mobile, I'm considering a CSS ratio of about 0.9. It seems I could keep the mobile ratio at 1.65, resulting in around 212 pixels, which should work. I also need to use separate scale transformations for X and Y to make sure everything fits well!**Evaluating game layout**

I'm exploring how to design the game's layout, focusing on a mobile screenshot aspect ratio of 1.923. I wonder if using CSS media queries with a 1.3 aspect ratio could help with scaling content more uniformly. It seems I need to ensure the layout remains visible and functional on mobile, especially with initial gameplay prompts and overlays. I want to avoid obscuring the game itself, suggesting simple placements for important notices without interfering with the player's view.**Designing game controls**

I want to establish an initial prompt reading "Click or press a key to start" for the self-playing feature. I think placing a banner at the top center beneath the bricks, starting around y=30, could work well. The overlay should ensure the AI runs until the user clicks to take manual control. It feels essential to clearly show how to switch to manual paddle control with intuitive inputs and have a restart button that’s easy to interact with, ensuring an engaging experience.**Refining AI paddle mechanics**

I’m thinking the AI has to control the paddle effectively, following the ball's x-axis, ensuring it always catches unless something unexpected happens. I want to have at least 54 bricks, creating a challenging path for the ball. In the demo, I plan to keep the bricks visible for the first few seconds. The initial ball can start at x500 y450 and move up-right. I’ll use visual elements like rounded rectangles for the bricks, with details like shadows and power-up icons to enhance the gameplay experience.**Considering game design updates**

I'm thinking about the game’s physics, especially with the paddle and ball collisions. There are elements like updating the status in the DOM and ensuring accessibility. I’m also figuring out how to manage things like power-ups that drop from bricks and how velocities work in relation to gravity.

I need to refine the UI too, including details like headers, logos, and an overlay for a game over modal. A restart button is also essential! I want everything to feel polished and functional.**Refining game mechanics**

I'm thinking about how the game ends when lives reach zero and how victory is determined when all bricks are cleared. Restarting the game might involve an automatic demo mode, and I need to consider how clicks work for restarting. 

I'll ensure the canvas properly maps mouse movements and prevents default actions when using keyboard controls. The overlay should prompt the user to start the game, and once they move the mouse, that should switch from demo mode to manual control. Getting the pointer events right is essential for a smooth experience.**Clarifying interaction mechanics**

I’m considering how to manage the pointer events, especially with the loading situation—would a `pointermove` still register? If it does, I want that to set `manual=true` unless we're in terminal mode. Users should be able to start the game by clicking or pressing a key, but I need to ensure mouse movements also trigger manual control. 

For paddle mechanics, I’m thinking about a simplistic prediction model with a maximum speed of 660 px/s. The paddle will be smoothly positioned, and other physics settings are set, including variable speeds and positions.**Evaluating visual distortion**

I'm thinking about how the balls appear elliptical and that the bricks look slightly taller. The gameplay coordinates are consistent, even with the event map ratios. Although the visual scales are rendered vertically, the physics coordinates are raster-based. Since the CSS canvas has an internal raster ratio, stretching the y-axis can distort the geometry. However, the interactions still correspond to normalized coordinates. Hmm, could this create an undesirable distortion in appearance? I need to keep that in mind!**Predicting ball movement**

I’m thinking about how I can use AI to track multiple balls and focus on the lowest ball's y position for prediction. I’ll use the target x of that ball, and at the walls, I could simply follow the current x. I need to consider that the ball falls with some speed too. It sounds like a good demo to work on, where I can make the AI move smoothly. That’ll be interesting to see!