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 36 s
Cost
$0.015
Output tokens
25,702
Spent thinking
8,439 tokens
Speed
1327.9 tok/s
Console errors
0
First token after
2 min 17 s
First visible text after
2 min 36 s
Prompt tokens
25,714
Code size
26.9 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
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 · 728 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NEON BREAKER — Arcade</title>
<style>
  :root {
    color-scheme: dark;
    --bg: #090d19;
    --panel: #11182a;
    --muted: #8993ad;
    --text: #f3f6ff;
    --cyan: #54e6ff;
    --line: rgba(157, 181, 235, .13);
  }
  * { box-sizing: border-box; }
  html, body { margin: 0; min-height: 100%; }
  body {
    min-height: 100vh;
    padding: 17px 24px 14px;
    background:
      radial-gradient(ellipse at 50% -20%, rgba(41, 74, 148, .25), transparent 52%),
      radial-gradient(ellipse at 12% 85%, rgba(31, 73, 107, .12), transparent 34%),
      var(--bg);
    color: var(--text);
    font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  }
  .app { width: min(1100px, 100%); margin: 0 auto; }
  .topbar {
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 12px;
  }
  .brand { display: flex; align-items: center; gap: 12px; }
  .brand-mark {
    width: 38px; height: 38px; border-radius: 12px;
    display: grid; place-items: center;
    color: #07111f; font-size: 18px; font-weight: 900;
    background: linear-gradient(145deg, #79f4ff, #31aefa 67%, #9373ff);
    box-shadow: 0 0 26px rgba(69, 207, 255, .22);
  }
  .brand-name { font-size: 16px; letter-spacing: .16em; font-weight: 850; }
  .brand-sub { margin-top: 2px; color: #77839f; font-size: 9px; letter-spacing: .2em; }
  .top-right { display: flex; align-items: center; gap: 9px; }
  .pill {
    padding: 7px 11px; border-radius: 999px;
    border: 1px solid var(--line); background: rgba(20, 28, 48, .72);
    color: #aeb9d4; font-size: 10px; letter-spacing: .09em; white-space: nowrap;
  }
  .live-dot {
    display: inline-block; width: 6px; height: 6px; margin-right: 6px; border-radius: 50%;
    background: #5df0ba; box-shadow: 0 0 9px #5df0ba;
    vertical-align: 1px;
  }
  .hud {
    display: grid; grid-template-columns: 1fr 1fr 1fr;
    gap: 10px; margin-bottom: 12px;
  }
  .hud-card {
    min-height: 62px; padding: 10px 16px;
    border: 1px solid var(--line); border-radius: 13px;
    background: linear-gradient(135deg, rgba(20, 28, 48, .92), rgba(15, 21, 37, .8));
    display: flex; align-items: center; justify-content: space-between;
  }
  .hud-label { color: #8490ac; font-size: 9px; font-weight: 750; letter-spacing: .18em; }
  .hud-value { margin-top: 3px; font-variant-numeric: tabular-nums; font-size: 20px; font-weight: 800; }
  .score-value { color: #eef7ff; }
  .lives { display: flex; align-items: center; gap: 7px; }
  .life {
    width: 23px; height: 8px; border-radius: 6px;
    background: linear-gradient(90deg, #45cfff, #93f6ff);
    box-shadow: 0 0 12px rgba(69, 207, 255, .22);
  }
  .life.lost { opacity: .19; box-shadow: none; }
  .effect { color: #b7c2dc; font-size: 12px; text-align: right; }
  .effect strong { color: #b98bff; font-size: 13px; }
  .stage {
    position: relative;
    overflow: hidden;
    border: 1px solid rgba(140, 173, 236, .2);
    border-radius: 16px;
    background: #0a1020;
    box-shadow: 0 18px 60px rgba(0, 0, 0, .31), 0 0 40px rgba(53, 113, 206, .06);
  }
  canvas { display: block; width: 100%; height: clamp(320px, 42vw, 520px); }
  .demo-prompt {
    position: absolute; z-index: 2; top: 13px; left: 50%; transform: translateX(-50%);
    display: flex; align-items: center; gap: 8px;
    border: 1px solid rgba(126, 177, 255, .22);
    background: rgba(11, 17, 33, .86); backdrop-filter: blur(8px);
    border-radius: 999px; padding: 8px 14px;
    color: #d9e4ff; font-size: 10px; font-weight: 700; letter-spacing: .13em;
    white-space: nowrap; pointer-events: none;
    transition: opacity .25s, transform .25s;
  }
  .demo-prompt .pulse {
    width: 7px; height: 7px; border-radius: 50%; background: #62e6ff;
    box-shadow: 0 0 10px #62e6ff; animation: pulse 1.4s infinite;
  }
  @keyframes pulse { 50% { opacity: .3; transform: scale(.75); } }
  .demo-prompt.hidden { opacity: 0; transform: translate(-50%, -8px); }
  .end-screen {
    position: absolute; inset: 0; z-index: 3; display: none;
    align-items: center; justify-content: center;
    background: rgba(5, 9, 19, .72); backdrop-filter: blur(5px);
  }
  .end-screen.show { display: flex; }
  .end-card {
    min-width: min(310px, calc(100% - 36px)); padding: 25px 28px;
    border: 1px solid rgba(125, 184, 255, .25); border-radius: 18px;
    background: linear-gradient(145deg, #17213a, #10172a);
    text-align: center; box-shadow: 0 18px 60px #0008;
  }
  .end-kicker { color: #69eaff; font-size: 9px; font-weight: 800; letter-spacing: .24em; }
  .end-title { margin: 8px 0 5px; font-size: 27px; font-weight: 900; letter-spacing: .04em; }
  .end-copy { color: #9aa7c2; font-size: 12px; }
  .restart {
    margin-top: 18px; padding: 10px 20px; border: 0; border-radius: 9px;
    background: linear-gradient(100deg, #5deaff, #6eabff);
    color: #07111f; font: inherit; font-size: 11px; font-weight: 900; letter-spacing: .12em;
    cursor: pointer; box-shadow: 0 5px 18px #4edcff35;
  }
  .restart:hover { filter: brightness(1.12); transform: translateY(-1px); }
  .footer {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 11px 3px 0; color: #74809c; font-size: 10px;
  }
  .controls { display: flex; align-items: center; gap: 8px; }
  .key {
    min-width: 23px; padding: 4px 6px; text-align: center; border-radius: 5px;
    border: 1px solid rgba(155, 177, 222, .2); background: rgba(24, 33, 54, .78);
    color: #b9c5df; font-size: 9px; font-weight: 750;
  }
  .power-legend { display: flex; gap: 15px; align-items: center; }
  .legend-item { display: flex; gap: 6px; align-items: center; white-space: nowrap; }
  .legend-icon {
    width: 18px; height: 18px; border-radius: 6px; display: grid; place-items: center;
    color: #07111f; font-size: 10px; font-weight: 900;
  }
  .wide-icon { background: #bd87ff; }
  .multi-icon { background: #ffb45e; }
  @media (max-width: 600px) {
    body { padding: 10px 10px 12px; }
    .topbar { height: 42px; margin-bottom: 9px; }
    .brand-mark { width: 33px; height: 33px; }
    .brand-name { font-size: 13px; }
    .brand-sub { font-size: 8px; }
    .top-right .pill { padding: 6px 8px; font-size: 8px; }
    .hud { gap: 6px; margin-bottom: 9px; }
    .hud-card { min-height: 56px; padding: 8px 9px; border-radius: 10px; }
    .hud-label { font-size: 8px; letter-spacing: .12em; }
    .hud-value { font-size: 17px; }
    .life { width: 16px; height: 7px; }
    .effect { font-size: 10px; }
    .demo-prompt { top: 9px; padding: 7px 10px; font-size: 8px; }
    .footer { align-items: flex-start; font-size: 9px; }
    .controls { gap: 5px; }
    .power-legend { gap: 8px; }
    .legend-item { gap: 4px; }
    .legend-icon { width: 16px; height: 16px; }
  }
  @media (max-width: 390px) {
    .top-right .pill:first-child { display: none; }
    .footer { flex-wrap: wrap; }
  }
</style>
</head>
<body>
<main class="app">
  <header class="topbar">
    <div class="brand">
      <div class="brand-mark">✦</div>
      <div>
        <div class="brand-name">NEON BREAKER</div>
        <div class="brand-sub">ARCADE SYSTEMS · SECTOR 01</div>
      </div>
    </div>
    <div class="top-right">
      <div class="pill"><span class="live-dot"></span>DEMO RUNNING</div>
      <div class="pill">LEVEL <strong style="color:#f3f6ff">01</strong> &nbsp;·&nbsp; NEON GRID</div>
    </div>
  </header>

  <section class="hud" aria-label="Game status">
    <div class="hud-card">
      <div>
        <div class="hud-label">SCORE</div>
        <div class="hud-value score-value" id="score">000000</div>
      </div>
      <div style="color:#65dcff;font-size:17px">✧</div>
    </div>
    <div class="hud-card">
      <div>
        <div class="hud-label">LIVES</div>
        <div class="hud-value" id="lives" style="display:flex;align-items:center;gap:7px"></div>
      </div>
      <div style="color:#8290ae;font-size:9px;letter-spacing:.12em">3 MAX</div>
    </div>
    <div class="hud-card">
      <div>
        <div class="hud-label">ACTIVE BOOST</div>
        <div class="hud-value effect" id="effect">—</div>
      </div>
      <div style="color:#b98bff;font-size:17px">⌁</div>
    </div>
  </section>

  <section class="stage">
    <canvas id="game" aria-label="Breakout game"></canvas>
    <div class="demo-prompt" id="prompt"><span class="pulse"></span>DEMO IN PROGRESS <span style="color:#71809e">·</span> CLICK OR PRESS A KEY TO PLAY</div>
    <div class="end-screen" id="endScreen">
      <div class="end-card">
        <div class="end-kicker" id="endKicker">RUN COMPLETE</div>
        <div class="end-title" id="endTitle">GAME OVER</div>
        <div class="end-copy" id="endCopy">Your score: 000000</div>
        <button class="restart" id="restart">PLAY AGAIN&nbsp; ↗</button>
      </div>
    </div>
  </section>

  <footer class="footer">
    <div class="controls">
      <span>MOVE</span><span class="key">←</span><span class="key">→</span><span>OR MOUSE</span>
    </div>
    <div class="power-legend">
      <span class="legend-item"><span class="legend-icon wide-icon">↔</span>WIDE PADDLE</span>
      <span class="legend-item"><span class="legend-icon multi-icon">×3</span>MULTI-BALL</span>
    </div>
    <div>BREAK EVERY BRICK TO WIN</div>
  </footer>
</main>

<script>
(() => {
  "use strict";
  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const scoreEl = document.getElementById("score");
  const livesEl = document.getElementById("lives");
  const effectEl = document.getElementById("effect");
  const promptEl = document.getElementById("prompt");
  const endScreen = document.getElementById("endScreen");
  const endTitle = document.getElementById("endTitle");
  const endKicker = document.getElementById("endKicker");
  const endCopy = document.getElementById("endCopy");
  const restartBtn = document.getElementById("restart");

  const W = 1000;
  const COLORS = ["#48d7ed", "#559cff", "#a37aff", "#f078c7", "#ffad62", "#6fe0ae"];
  let H = 470, scaleX = 1, scaleY = 1;
  let bricks = [], balls = [], drops = [], particles = [], stars = [];
  let paddle = { x: 500, y: 0, w: 112, h: 12 };
  let score = 0, lives = 3, remaining = 0;
  let mode = "demo", state = "playing", keys = { left: false, right: false };
  let wideUntil = 0, elapsed = 0, lastTime = 0, serveFlash = 0;

  function resize() {
    const rect = canvas.getBoundingClientRect();
    if (!rect.width || !rect.height) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = Math.round(rect.width * dpr);
    canvas.height = Math.round(rect.height * dpr);
    scaleX = canvas.width / W;
    H = rect.height / rect.width * W;
    scaleY = canvas.height / H;
    ctx.setTransform(scaleX, 0, 0, scaleY, 0, 0);
    paddle.y = H - 39;
    if (!stars.length) {
      for (let i = 0; i < 76; i++) stars.push({
        x: Math.random() * W, y: Math.random() * 1000,
        r: .45 + Math.random() * 1.15, a: .13 + Math.random() * .36,
        phase: Math.random() * 6.28
      });
    }
  }
  window.addEventListener("resize", resize);

  function makeBricks() {
    bricks = [];
    const cols = 10, rows = 6, bw = 84, bh = 24, gapX = 8, gapY = 10;
    const startX = (W - (cols * bw + (cols - 1) * gapX)) / 2;
    for (let r = 0; r < rows; r++) {
      for (let c = 0; c < cols; c++) {
        const tough = ((r * 7 + c * 3) % 5 === 0) || (r === 2 && c % 3 === 1);
        let power = null;
        if ((r === 1 && c === 1) || (r === 2 && c === 7)) power = "wide";
        if ((r === 4 && c === 4) || (r === 5 && c === 8)) power = "multi";
        bricks.push({
          x: startX + c * (bw + gapX), y: 66 + r * (bh + gapY),
          w: bw, h: bh, hp: tough ? 2 : 1, maxHp: tough ? 2 : 1,
          color: COLORS[r], power, alive: true, seed: r * 10 + c
        });
      }
    }
    remaining = bricks.length;
  }

  function launchBall(x = W * .5, y = H - 62, vx = 112, vy = -383) {
    const speed = Math.hypot(vx, vy);
    balls.push({ x, y, px: x, py: y, vx, vy, r: 6.5, speed });
  }

  function resetGame() {
    score = 0; lives = 3; elapsed = 0; wideUntil = 0;
    drops = []; particles = []; balls = [];
    paddle.w = 112; paddle.x = W / 2;
    state = "playing"; mode = "manual";
    makeBricks(); launchBall();
    endScreen.classList.remove("show");
    promptEl.classList.add("hidden");
    updateHud();
  }

  function updateHud() {
    scoreEl.textContent = String(score).padStart(6, "0");
    livesEl.innerHTML = "";
    for (let i = 0; i < 3; i++) {
      const bar = document.createElement("span");
      bar.className = "life" + (i >= lives ? " lost" : "");
      livesEl.appendChild(bar);
    }
    if (elapsed < wideUntil) {
      effectEl.innerHTML = `<strong>↔ WIDE</strong> &nbsp;${Math.ceil(wideUntil - elapsed)}s`;
    } else {
      effectEl.textContent = "—";
    }
  }

  function finish(won) {
    if (state !== "playing") return;
    state = won ? "won" : "over";
    endKicker.textContent = won ? "SECTOR CLEARED" : "RUN COMPLETE";
    endTitle.textContent = won ? "VICTORY!" : "GAME OVER";
    endCopy.textContent = `Your score: ${String(score).padStart(6, "0")}`;
    endScreen.classList.add("show");
  }

  function addParticles(x, y, color, count = 12) {
    for (let i = 0; i < count; i++) {
      const a = Math.random() * Math.PI * 2;
      const speed = 45 + Math.random() * 190;
      particles.push({
        x, y, vx: Math.cos(a) * speed, vy: Math.sin(a) * speed - 35,
        life: .35 + Math.random() * .45, max: .8,
        size: 1.5 + Math.random() * 3, color
      });
    }
  }

  function createDrop(brick) {
    if (!brick.power) return;
    drops.push({ x: brick.x + brick.w / 2, y: brick.y + brick.h / 2, type: brick.power, vy: 108, spin: 0 });
  }

  function hitBrick(ball, brick) {
    const left = brick.x, right = brick.x + brick.w;
    const top = brick.y, bottom = brick.y + brick.h;
    const closestX = Math.max(left, Math.min(ball.x, right));
    const closestY = Math.max(top, Math.min(ball.y, bottom));
    const dx = ball.x - closestX, dy = ball.y - closestY;
    if (dx * dx + dy * dy > ball.r * ball.r) return false;

    const prevX = ball.px, prevY = ball.py;
    if (prevY + ball.r <= top + 2) {
      ball.y = top - ball.r - .2; ball.vy = -Math.abs(ball.vy);
    } else if (prevY - ball.r >= bottom - 2) {
      ball.y = bottom + ball.r + .2; ball.vy = Math.abs(ball.vy);
    } else if (prevX + ball.r <= left + 2) {
      ball.x = left - ball.r - .2; ball.vx = -Math.abs(ball.vx);
    } else if (prevX - ball.r >= right - 2) {
      ball.x = right + ball.r + .2; ball.vx = Math.abs(ball.vx);
    } else if (Math.abs(dx) > Math.abs(dy)) {
      ball.x = closestX + Math.sign(dx || ball.vx) * (ball.r + .2);
      ball.vx *= -1;
    } else {
      ball.y = closestY + Math.sign(dy || ball.vy) * (ball.r + .2);
      ball.vy *= -1;
    }

    brick.hp--;
    if (brick.hp <= 0) {
      brick.alive = false;
      remaining--;
      score += brick.maxHp === 2 ? 25 : 15;
      addParticles(brick.x + brick.w / 2, brick.y + brick.h / 2, brick.color, 16);
      createDrop(brick);
      if (remaining === 0) finish(true);
    } else {
      score += 5;
      addParticles(ball.x, ball.y, brick.color, 5);
    }
    updateHud();
    return true;
  }

  function reflectPaddle(ball) {
    const offset = Math.max(-1, Math.min(1, (ball.x - paddle.x) / (paddle.w / 2)));
    const angle = offset * 1.13;
    ball.speed = Math.min(520, ball.speed * 1.012);
    ball.vx = Math.sin(angle) * ball.speed;
    ball.vy = -Math.cos(angle) * ball.speed;
    ball.y = paddle.y - ball.r - .2;
    addParticles(ball.x, paddle.y, "#64eaff", 4);
  }

  function catchPower(drop) {
    if (drop.type === "wide") {
      wideUntil = elapsed + 10;
      paddle.w = 176;
    } else {
      const source = balls[0] || { x: paddle.x, y: paddle.y - 20, vx: 100, vy: -360, speed: 375 };
      const original = { ...source };
      balls.length = 0;
      balls.push(original);
      if (balls.length < 3) {
        const a = Math.atan2(original.vy, original.vx);
        for (const turn of [-.38, .38]) {
          const angle = a + turn;
          balls.push({
            x: original.x, y: original.y, px: original.x, py: original.y,
            vx: Math.cos(angle) * original.speed, vy: Math.sin(angle) * original.speed,
            speed: original.speed, r: original.r
          });
        }
      }
    }
    addParticles(drop.x, drop.y, drop.type === "wide" ? "#bd87ff" : "#ffb45e", 12);
    updateHud();
  }

  function update(dt) {
    if (state !== "playing") {
      updateParticles(dt);
      return;
    }
    elapsed += dt;
    if (wideUntil && elapsed >= wideUntil) {
      wideUntil = 0; paddle.w = 112; updateHud();
    }
    paddle.y = H - 39;
    paddle.w = elapsed < wideUntil ? 176 : 112;

    if (mode === "demo") {
      let target = W / 2;
      let chosen = null;
      for (const b of balls) {
        if (b.vy > 0 && (!chosen || b.y > chosen.y)) chosen = b;
      }
      if (chosen) {
        const t = Math.max(0, (paddle.y - chosen.y) / chosen.vy);
        let projected = chosen.x + chosen.vx * t;
        const span = W - 16;
        projected = ((projected - 8) % (span * 2) + span * 2) % (span * 2);
        if (projected > span) projected = span * 2 - projected;
        target = projected + 8;
      }
      const delta = target - paddle.x;
      paddle.x += Math.max(-720 * dt, Math.min(720 * dt, delta));
    } else {
      if (keys.left) paddle.x -= 570 * dt;
      if (keys.right) paddle.x += 570 * dt;
    }
    paddle.x = Math.max(paddle.w / 2 + 8, Math.min(W - paddle.w / 2 - 8, paddle.x));

    for (let i = balls.length - 1; i >= 0; i--) {
      const ball = balls[i];
      ball.px = ball.x; ball.py = ball.y;
      ball.x += ball.vx * dt; ball.y += ball.vy * dt;

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

      if (ball.vy > 0 && ball.y + ball.r >= paddle.y &&
          ball.y - ball.r <= paddle.y + paddle.h &&
          ball.x + ball.r >= paddle.x - paddle.w / 2 &&
          ball.x - ball.r <= paddle.x + paddle.w / 2) {
        reflectPaddle(ball);
      } else if (ball.vy < 0) {
        for (const brick of bricks) {
          if (brick.alive && hitBrick(ball, brick)) break;
        }
      }

      if (ball.y - ball.r > H + 10) balls.splice(i, 1);
    }

    if (!balls.length && state === "playing") {
      lives--;
      updateHud();
      if (lives <= 0) finish(false);
      else {
        paddle.x = W / 2;
        launchBall(W / 2, H - 62, (Math.random() * 120 - 60), -390);
        serveFlash = .45;
      }
    }

    for (let i = drops.length - 1; i >= 0; i--) {
      const d = drops[i];
      d.y += d.vy * dt; d.spin += dt * 2.8;
      if (d.y + 12 >= paddle.y && d.y - 12 <= paddle.y + paddle.h &&
          d.x >= paddle.x - paddle.w / 2 - 7 && d.x <= paddle.x + paddle.w / 2 + 7) {
        catchPower(d); drops.splice(i, 1);
      } else if (d.y > H + 18) drops.splice(i, 1);
    }
    updateParticles(dt);
  }

  function updateParticles(dt) {
    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;
      p.vx *= Math.pow(.22, dt); p.vy = p.vy * Math.pow(.22, dt) + 80 * dt;
      if (p.life <= 0) particles.splice(i, 1);
    }
  }

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

  function drawBackground() {
    const bg = ctx.createLinearGradient(0, 0, 0, H);
    bg.addColorStop(0, "#0b1327"); bg.addColorStop(.55, "#0a1122"); bg.addColorStop(1, "#0b1020");
    ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H);

    const glow = ctx.createRadialGradient(W / 2, 165, 10, W / 2, 165, 580);
    glow.addColorStop(0, "rgba(42,90,153,.13)"); glow.addColorStop(1, "rgba(15,24,45,0)");
    ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H);

    ctx.save();
    for (const s of stars) {
      if (s.y > H) continue;
      ctx.globalAlpha = s.a * (.72 + .28 * Math.sin(elapsed * 1.4 + s.phase));
      ctx.fillStyle = "#b9d9ff";
      ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.fill();
    }
    ctx.globalAlpha = 1;
    ctx.strokeStyle = "rgba(104,145,212,.045)";
    ctx.lineWidth = 1;
    for (let x = 20; x < W; x += 40) {
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
    }
    ctx.restore();

    ctx.strokeStyle = "rgba(103, 157, 231, .17)";
    ctx.lineWidth = 1;
    ctx.beginPath(); ctx.moveTo(15, H - 16); ctx.lineTo(W - 15, H - 16); ctx.stroke();
  }

  function drawBrick(b) {
    if (!b.alive) return;
    ctx.save();
    ctx.shadowColor = b.color; ctx.shadowBlur = b.hp === 1 ? 9 : 5;
    roundedRect(b.x, b.y, b.w, b.h, 5);
    const grad = ctx.createLinearGradient(b.x, b.y, b.x, b.y + b.h);
    grad.addColorStop(0, lighten(b.color, b.hp === 1 ? 1.18 : .83));
    grad.addColorStop(1, darken(b.color, .68));
    ctx.fillStyle = grad; ctx.fill();
    ctx.shadowBlur = 0;
    ctx.strokeStyle = b.hp === 1 ? b.color : "rgba(197,218,255,.28)";
    ctx.lineWidth = 1; ctx.stroke();

    roundedRect(b.x + 4, b.y + 3, b.w - 8, 3, 2);
    ctx.fillStyle = "rgba(255,255,255,.16)"; ctx.fill();

    if (b.maxHp === 2 && b.hp === 1) {
      ctx.strokeStyle = "rgba(18,20,45,.78)"; ctx.lineWidth = 1.5;
      ctx.beginPath();
      const offset = (b.seed * 7) % 22;
      ctx.moveTo(b.x + 20 + offset, b.y + 5);
      ctx.lineTo(b.x + 38 + offset / 2, b.y + 12);
      ctx.lineTo(b.x + 31 + offset / 2, b.y + 20);
      ctx.moveTo(b.x + 38 + offset / 2, b.y + 12);
      ctx.lineTo(b.x + 52 + offset / 2, b.y + 6);
      ctx.stroke();
    }
    if (b.power) {
      const isWide = b.power === "wide";
      const cx = b.x + b.w - 10, cy = b.y + 10;
      ctx.fillStyle = isWide ? "#e5c8ff" : "#ffe0ad";
      ctx.beginPath(); ctx.arc(cx, cy, 7, 0, Math.PI * 2); ctx.fill();
      ctx.fillStyle = "#172039"; ctx.font = "bold 9px system-ui";
      ctx.textAlign = "center"; ctx.textBaseline = "middle";
      ctx.fillText(isWide ? "↔" : "×", cx, cy + .3);
    }
    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|0},${g|0},${b|0})`;
  }
  function darken(hex, amount) {
    const n = parseInt(hex.slice(1), 16);
    return `rgb(${((n >> 16) & 255)*amount|0},${((n >> 8) & 255)*amount|0},${(n & 255)*amount|0})`;
  }

  function drawPaddle() {
    const x = paddle.x - paddle.w / 2;
    ctx.save();
    ctx.shadowColor = "#58dcff"; ctx.shadowBlur = 20;
    roundedRect(x, paddle.y, paddle.w, paddle.h, 7);
    const g = ctx.createLinearGradient(x, paddle.y, x, paddle.y + paddle.h);
    g.addColorStop(0, "#c4fbff"); g.addColorStop(.36, "#63e7ff"); g.addColorStop(1, "#378cff");
    ctx.fillStyle = g; ctx.fill();
    ctx.shadowBlur = 0;
    roundedRect(x + 9, paddle.y + 3, paddle.w - 18, 2, 1);
    ctx.fillStyle = "rgba(255,255,255,.53)"; ctx.fill();
    ctx.restore();
  }

  function drawBall(ball) {
    ctx.save();
    const tail = ctx.createLinearGradient(ball.x, ball.y, ball.x - ball.vx * .065, ball.y - ball.vy * .065);
    tail.addColorStop(0, "rgba(116,242,255,.45)"); tail.addColorStop(1, "rgba(116,242,255,0)");
    ctx.strokeStyle = tail; ctx.lineWidth = ball.r * 1.6; ctx.lineCap = "round";
    ctx.beginPath(); ctx.moveTo(ball.x, ball.y);
    ctx.lineTo(ball.x - ball.vx * .06, ball.y - ball.vy * .06); ctx.stroke();
    ctx.shadowColor = "#a9f9ff"; ctx.shadowBlur = 15;
    const g = ctx.createRadialGradient(ball.x - 2, ball.y - 2, 1, ball.x, ball.y, ball.r);
    g.addColorStop(0, "#fff"); g.addColorStop(.45, "#c8fbff"); g.addColorStop(1, "#5edfff");
    ctx.fillStyle = g; ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2); ctx.fill();
    ctx.restore();
  }

  function drawDrop(d) {
    const wide = d.type === "wide";
    const color = wide ? "#c294ff" : "#ffb45e";
    ctx.save();
    ctx.translate(d.x, d.y); ctx.rotate(Math.sin(d.spin) * .12);
    ctx.shadowColor = color; ctx.shadowBlur = 15;
    roundedRect(-12, -12, 24, 24, 8);
    const g = ctx.createLinearGradient(-12, -12, 12, 12);
    g.addColorStop(0, wide ? "#efdbff" : "#ffe1b4"); g.addColorStop(1, color);
    ctx.fillStyle = g; ctx.fill();
    ctx.shadowBlur = 0; ctx.fillStyle = "#18213b";
    ctx.font = "bold 15px system-ui"; ctx.textAlign = "center"; ctx.textBaseline = "middle";
    ctx.fillText(wide ? "↔" : "×3", 0, 0);
    ctx.restore();
  }

  function draw() {
    ctx.setTransform(scaleX, 0, 0, scaleY, 0, 0);
    drawBackground();
    for (const b of bricks) drawBrick(b);

    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, 0, Math.PI * 2); ctx.fill();
    }
    ctx.globalAlpha = 1;
    for (const d of drops) drawDrop(d);
    drawPaddle();
    for (const ball of balls) drawBall(ball);

    if (serveFlash > 0) {
      serveFlash -= 1 / 60;
      ctx.globalAlpha = Math.min(1, serveFlash * 2);
      ctx.fillStyle = "#a8b8d8"; ctx.font = "700 10px system-ui";
      ctx.textAlign = "center"; ctx.fillText("BALL READY", W / 2, H - 75);
      ctx.globalAlpha = 1;
    }
    ctx.fillStyle = "rgba(175,198,236,.48)";
    ctx.font = "700 9px system-ui"; ctx.textAlign = "left";
    ctx.fillText(`${remaining} BRICKS REMAINING`, 21, H - 23);
    ctx.textAlign = "right";
    ctx.fillText(mode === "demo" ? "AUTO PILOT ACTIVE" : "PLAYER CONTROL", W - 21, H - 23);
  }

  function frame(now) {
    if (!lastTime) lastTime = now;
    const dt = Math.min(.025, (now - lastTime) / 1000);
    lastTime = now;
    update(dt);
    draw();
    requestAnimationFrame(frame);
  }

  function engage() {
    if (state !== "playing") return;
    mode = "manual";
    promptEl.classList.add("hidden");
  }

  function movePointer(event) {
    if (mode !== "manual" || state !== "playing") return;
    const rect = canvas.getBoundingClientRect();
    paddle.x = (event.clientX - rect.left) / rect.width * W;
    paddle.x = Math.max(paddle.w / 2 + 8, Math.min(W - paddle.w / 2 - 8, paddle.x));
  }
  canvas.addEventListener("pointerdown", event => {
    engage();
    movePointer(event);
  });
  canvas.addEventListener("pointermove", movePointer);

  window.addEventListener("keydown", event => {
    if (["ArrowLeft", "ArrowRight", " ", "ArrowUp", "ArrowDown"].includes(event.key)) event.preventDefault();
    if (state !== "playing") return;
    engage();
    if (event.key === "ArrowLeft" || event.key.toLowerCase() === "a") keys.left = true;
    if (event.key === "ArrowRight" || event.key.toLowerCase() === "d") keys.right = true;
  });
  window.addEventListener("keyup", event => {
    if (event.key === "ArrowLeft" || event.key.toLowerCase() === "a") keys.left = false;
    if (event.key === "ArrowRight" || event.key.toLowerCase() === "d") keys.right = false;
  });
  restartBtn.addEventListener("click", resetGame);

  resize();
  makeBricks();
  launchBall();
  updateHud();
  requestAnimationFrame(frame);
})();
</script>
</body>
</html>
Reasoning trace (8,439 tokens)
**Designing a polished game**

I need to produce a polished game that auto-starts with an AI demo behind a "Click or press a key to start" message. The gameplay should transition to player mode after the demo, allowing for human controls. I want the game to be responsive, with a canvas size of 1280x800 and a visually refined UI. The background will be dark navy, with a HUD that updates lives and displays power-up icons. I want to ensure the design is lively while maintaining gameplay continuity in demo mode.**Refining collision detection**

I'm considering paddle collision mechanics to ensure the ball can't pass through bricks or the paddle. I need a fixed step with modest speed, ensuring collisions are handled correctly with circle-rect checks. At 360px/s and 60fps movement translates to 6px, with ball radius at 7 and bricks at 24 thickness, I should avoid tunneling. I need to ensure collision detection is efficient and only handles one brick per tick. In demo mode, the AI paddle will follow the ball's x position, but I need to ensure gameplay is engaging for users.**Designing collision detection**

I'm considering using an expanded AABB collision radius to test if a previous position crosses during x movement. Since the time step (dt) is small, it should ensure robust hit detection. I'm thinking of a grid with 6 to 10 columns and a total of 60 bricks. For toughness, mixing normal bricks in rows 0-1 sounds good, and I want to keep the visuals colorful with a random yet deterministic pattern for brick placements.**Considering responsive scaling**

I'm trying to make sense of how CSS scales nonuniformly. On mobile, using a scale of .375 for x and .65 for y results in distorted bricks. I’m thinking it would be better to have a responsive canvas that scales dynamically based on its CSS size.

So, maybe I can set canvas dimensions based on client dimensions. I want to ensure the aspects remain uniform and adapt accordingly to the world width. Alright, it’s a matter of adjusting the canvas height and calculating the world dimensions accurately.**Adjusting mobile scaling**

I'm exploring the world scaling on mobile, where height is set to 960, but the width scales down to .375, making the height 360. The world scale becomes .375 as well. I’ve added some vertical space, which seems fine. 

However, there are width constraints that might limit object sizes to about 3px. I could maintain a minimum CSS height of 340 by adjusting internal world dimensions based on view width. It feels complex, but maybe I could clamp the canvas height between 320px and 520px for a better aspect ratio.**Implementing canvas resizing**

I'm defining the world width as 1000, with the ball scaling at .375 for a radius of 10, which means it appears as 3.75px. The brick height is set at 24, translating to 9px, which is okay. 

To tackle resizing the canvas, I need to use CSS sizing and device pixel ratio (DPR) dimensions. I’ll set the render transform to scale horizontally based on the rectangle width and vertically according to the canvas height. I’ll also define the world height relative to the canvas to ensure accurate positioning.