Models 44

GPT-5 Nano

by OpenAIReasoningClean
Open in new window

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

How it went

Total time
3 min 32 s
Cost
$0.0084
Output tokens
20,876
Spent thinking
13,696 tokens
Speed
103.4 tok/s
Console errors
0
First token after
10.6 s
First visible text after
2 min 29 s
Prompt tokens
508
Code size
23.7 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-5-nano
Reasoning sent
{"effort":"high"}
Max output
128,000 tokens
Generated
Nov 12, 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 · 817 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>VanillaJS Breakout - Self-Playing Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
  :root {
    --bg: #0a0b18;
    --panel: rgba(0,0,0,.6);
    --white: #eaeaea;
    --green: #4cd964;
    --pink: #e06bd8;
    --orange: #f4a261;
    --blue: #4da3ff;
  }

  html, body {
    height: 100%;
    margin: 0;
    background: #111;
    color: #eee;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
    overflow: hidden;
  }

  #stage {
    height: 100vh;
    width: 100vw;
    display: grid;
    place-items: center;
  }

  /* The game stage has a fixed logical size, then scales to fit nicely on all viewports */
  .board {
    width: min(96vw, 1280px);
    aspect-ratio: 16 / 9;
    position: relative;
    display: grid;
    place-items: center;
  }

  canvas {
    width: 100%;
    height: 100%;
    display: block;
    background: #0b0e1a;
    border-radius: 12px;
    box-shadow: 0 10px 40px rgba(0,0,0,.5);
  }

  /* HUDs and overlays */
  .hud {
    position: absolute;
    top: 8px;
    left: 12px;
    color: #fff;
    font-family: ui-monospace,SFMono-Regular,Monospace;
    font-size: 14px;
    text-shadow: 0 1px 2px rgba(0,0,0,.6);
    user-select: none;
  }

  .overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    background: rgba(0,0,0,.4);
    color: #fff;
    padding: 20px;
    border-radius: 12px;
    backdrop-filter: blur(2px);
  }

  .overlay .card {
    background: var(--panel);
    padding: 28px 34px;
    border-radius: 12px;
    max-width: 720px;
    box-shadow: 0 20px 60px rgba(0,0,0,.45);
  }

  h1 { margin: 0 0 8px; font-size: 28px; }

  p { margin: 6px 0 14px; opacity: .95; }

  .startBtn, .restartBtn {
    padding: 10px 18px;
    font-size: 14px;
    border-radius: 8px;
    border: 0;
    cursor: pointer;
    background: #1e88e5;
    color: white;
    font-weight: bold;
  }

  .restartBtn { background: #28a745; }

  .overlay.win { background: rgba(0, 128, 0, .5); }
  .overlay.lose { background: rgba(128, 0, 0, .5); }
  .overlay .hint { font-size: 12px; opacity: .9; }

  /* Responsive helpers */
  @media (max-width: 700px) {
    .overlay .card { padding: 22px; }
  }

  /* Tiny icons inside bricks indicating power-ups */
  .PUIcon {
    position: absolute;
    width: 14px;
    height: 14px;
    border-radius: 3px;
    font-size: 11px;
    line-height: 14px;
    text-align: center;
    color: white;
    pointer-events: none;
  }

  /* Ensure no horizontal scroll when scaling */
  body { padding: 0; margin: 0; }
</style>
</head>
<body>
  <div id="stage">
    <div class="board" id="board">
      <canvas id="gameCanvas" width="960" height="540" aria-label="Breakout canvas"></canvas>

      <div class="hud" id="hud">Score: 0 | Lives: 3</div>

      <!-- Overlay: Start Demo (auto-playing) -->
      <div class="overlay" id="overlayStart" aria-label="Click to start">
        <div class="card">
          <h1>Breakout Demo</h1>
          <p>Self-playing demo starts now. After clicking, you can take control with the mouse or arrow keys. Your task is to beat the level before you run out of lives.</p>
          <p class="hint">Click anywhere or press a key to take the reins.</p>
          <button class="startBtn" id="startBtn">Start Demo</button>
        </div>
      </div>

      <!-- Victory / Game Over overlays -->
      <div class="overlay win" id="overlayWin" style="display:none" aria-label="You win">
        <div class="card">
          <h1>You Win!</h1>
          <p>Your score: <span id="finalScoreWin">0</span></p>
          <button class="restartBtn" id="restartBtnWin">Restart</button>
        </div>
      </div>

      <div class="overlay lose" id="overlayLose" style="display:none" aria-label="Game Over">
        <div class="card">
          <h1>Game Over</h1>
          <p>Your score: <span id="finalScoreLose">0</span></p>
          <button class="restartBtn" id="restartBtnLose">Restart</button>
        </div>
      </div>

      <!-- Optional tiny power-up indicator layer (not separate DOM, drawn in canvas) -->
    </div>
  </div>

<script>
(function(){
  // Game constants (logical resolution)
  const GAME_W = 960;
  const GAME_H = 540;

  // Canvas and rendering
  const canvas = document.getElementById('gameCanvas');
  const ctx = canvas.getContext('2d');
  // For crisp rendering on HiDPI, scale the canvas drawing surface to devicePixelRatio
  function resizeCanvas() {
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    // CSS size is the displayed size; internal buffer is set to CSS size * dpr for crispness
    const cssW = canvas.clientWidth;
    const cssH = canvas.clientHeight;
    canvas.width = Math.max(1, Math.floor(cssW * dpr));
    canvas.height = Math.max(1, Math.floor(cssH * dpr));
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // draw in CSS px units
  }

  // Stage sizing: keep visual content pleasant on various sizes
  window.addEventListener('resize', resizeCanvas);
  window.addEventListener('orientationchange', resizeCanvas);
  // Initial resize once DOM ready
  window.addEventListener('load', resizeCanvas);

  // Game state
  let score = 0;
  let lives = 3;
  let bricksRemaining = 0;
  let started = false;      // User has clicked to start or not
  let demoMode = true;        // Auto-pilot paddle moves while not started
  let gameOver = false;
  let gameWin = false;

  // Paddle
  const PADDLE = {
    w: 110,
    h: 14,
    x: (GAME_W - 110) / 2,
    y: GAME_H - 40,
    speed: 720, // px/s (for keyboard control)
    boosting: false
  };

  // Ball(s)
  const BALL_SPEED = 6.8; // base speed
  function createBall(x, y, vx, vy) {
    return { x, y, vx, vy, r: 6.0, color: '#fff', alive: true };
  }
  let balls = [];

  // Bricks
  const BRICK_ROWS = 6;
  let bricks = [];

  // Power-ups
  // powerUp types: 0 = widen paddle, 1 = multi-ball
  function createPowerUp(x, y, type) {
    return { x, y, vy: 90, size: 18, type, collected: false, color: type === 0 ? '#9b59b6' : '#f39c12' };
  }
  let activePowerUps = []; // falling items

  // Particles on brick break
  let particles = [];

  // Input
  const input = {
    mouseX: GAME_W / 2,
    left: false,
    right: false
  };

  // Overlay elements
  const overlayStart = document.getElementById('overlayStart');
  const overlayWin = document.getElementById('overlayWin');
  const overlayLose = document.getElementById('overlayLose');
  const hud = document.getElementById('hud');
  const finalScoreWin = document.getElementById('finalScoreWin');
  const finalScoreLose = document.getElementById('finalScoreLose');
  const startBtn = document.getElementById('startBtn');
  const restartBtnWin = document.getElementById('restartBtnWin');
  const restartBtnLose = document.getElementById('restartBtnLose');

  // Init game
  function initGame() {
    // Reset
    score = 0;
    lives = 3;
    started = false;
    demoMode = true;
    gameOver = false;
    gameWin = false;
    balls = [];
    activePowerUps = [];
    particles = [];

    // Paddle initial
    PADDLE.w = 110;
    PADDLE.x = (GAME_W - PADDLE.w) / 2;
    PADDLE.y = GAME_H - 40;

    // Create initial ball
    const b = createBall(GAME_W / 2, PADDLE.y - 18, Math.sin(0.0) * BALL_SPEED, -BALL_SPEED);
    balls.push(b);

    // Build bricks
    bricks = [];
    bricksRemaining = 0;
    buildBricks();

    // Overlay
    overlayWin.style.display = 'none';
    overlayLose.style.display = 'none';
    overlayStart.style.display = 'flex';
  }

  // Build bricks: 6 rows, mix normal and tougher bricks, some bricks carry power-ups
  function buildBricks() {
    bricks = [];
    bricksRemaining = 0;

    const cols = 12;
    const gap = 8;
    const leftMargin = 28;
    const brickW = Math.floor((GAME_W - leftMargin * 2 - gap * (cols - 1)) / cols);
    const brickH = 22;
    const topMargin = 60;
    const powerChance = 0.15; // percent bricks carrying power

    for (let r = 0; r < BRICK_ROWS; r++) {
      for (let c = 0; c < cols; c++) {
        const x = leftMargin + c * (brickW + gap);
        const y = topMargin + r * (brickH + 6);

        // Randomize brick toughness
        let hits = (Math.random() < 0.25) ? 2 : 1; // 25% tough bricks
        if (r < 2 && Math.random() < 0.3) hits = 2; // bias for upper rows

        // Color by toughness
        let color = hits === 1 ? '#2ecc71' : '#e67e22';
        let cracked = false;

        // Optional power-up
        let powerUpType = null;
        if (Math.random() < powerChance) {
          powerUpType = Math.random() < 0.5 ? 0 : 1;
        }

        bricks.push({
          x, y, w: brickW, h: brickH,
          hitsLeft: hits,
          color, cracked: false,
          powerUpType,
          destroyed: false
        });

        if (! (hits <= 0) ) bricksRemaining++;
      }
    }
  }

  // Draw helpers
  function drawRect(x, y, w, h, color) {
    ctx.fillStyle = color;
    ctx.fillRect(x, y, w, h);
  }

  function drawRoundedRect(x, y, w, h, r, color) {
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.arcTo(x + w, y, x + w, y + h, r);
    ctx.arcTo(x + w, y + h, x, y + h, r);
    ctx.arcTo(x, y + h, x, y, r);
    ctx.arcTo(x, y, x + w, y, r);
    ctx.closePath();
    ctx.fill();
  }

  function drawBall(ball) {
    // glow
    ctx.save();
    ctx.shadowColor = '#fff';
    ctx.shadowBlur = 12;
    ctx.fillStyle = ball.color;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  function clamp(n, a, b) { return Math.max(a, Math.min(n, b)); }

  function circleRectCollision(ball, rect) {
    const nearestX = clamp(ball.x, rect.x, rect.x + rect.w);
    const nearestY = clamp(ball.y, rect.y, rect.y + rect.h);
    const dx = ball.x - nearestX;
    const dy = ball.y - nearestY;
    return (dx * dx + dy * dy) <= (ball.r * ball.r);
  }

  // Paddle collision bounce angle
  function bounceFromPaddle(ball) {
    // hit position: -1 (left edge) .. 1 (right edge)
    const hitPos = (ball.x - (PADDLE.x + PADDLE.w * 0.5)) / (PADDLE.w * 0.5);
    const maxBounce = Math.PI / 3; // 60 degrees
    const angle = clamp(hitPos, -1, 1) * maxBounce;
    const speed = Math.hypot(ball.vx, ball.vy);
    ball.vx = Math.sin(angle) * speed;
    ball.vy = -Math.cos(angle) * speed;
  }

  // Particles for brick breaks
  function spawnParticles(x, y, color) {
    const count = 8 + Math.floor(Math.random() * 6);
    for (let i = 0; i < count; i++) {
      const a = Math.random() * Math.PI * 2;
      const s = 40 + Math.random() * 60;
      particles.push({
        x, y,
        vx: Math.cos(a) * s,
        vy: Math.sin(a) * s,
        life: 0.8 + Math.random() * 0.6,
        color
      });
    }
  }

  // Power-up collection
  function applyPowerUp(pow) {
    if (pow.type === 0) {
      // widen paddle for 10 seconds
      if (!PADDLE._boostEnd) {
        PADDLE._boostEnd = performance.now() + 10000;
      } else {
        // extend existing boost
        PADDLE._boostEnd = Math.max(PADDLE._boostEnd, performance.now() + 10000);
      }
      PADDLE.w = Math.min(260, PADDLE.w + 40);
      // Remove any friction in keyboard autoplace
    } else if (pow.type === 1) {
      // multi-ball: spawn 2 extra balls
      const currentBall = balls[0];
      if (currentBall) {
        const speed = Math.hypot(currentBall.vx, currentBall.vy);
        const baseAngle = Math.atan2(-currentBall.vy, currentBall.vx);
        // Two new balls with slight angle variations
        for (let k = -1; k <= 1; k += 2) {
          const a = baseAngle + k * 0.25;
          const nb = createBall(currentBall.x, currentBall.y, Math.cos(a) * speed, Math.sin(a) * speed);
          balls.push(nb);
        }
      }
    }
    // Remove power-up
    pow.collected = true;
  }

  // Bricks drawing
  function drawBricks() {
    for (const br of bricks) {
      if (br.destroyed) continue;
      // Color intensity by hitsLeft
      const base = br.color;
      const tint = br.hitsLeft === 2 ? base : '#9bd299';
      drawRect(br.x, br.y, br.w, br.h, tint);

      // If it's a tough brick and has been hit once, show crack
      if (br.hitsLeft === 1 && br.hitsLeft !== 0) {
        // crack overlay
        ctx.fillStyle = 'rgba(0,0,0,.15)';
        ctx.fillRect(br.x, br.y, br.w, br.h);
      }

      // Power-up icon indicator
      if (br.powerUpType != null && br.powerUpType !== undefined) {
        ctx.fillStyle = '#fff';
        ctx.font = 'bold 12px sans-serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        // small "PU" icon drawn on brick
        ctx.fillText('PU', br.x + br.w * 0.5, br.y + br.h * 0.5);
      }
    }
  }

  // Power-ups drawing
  function drawPowerUps() {
    for (const p of activePowerUps) {
      // fall down
      ctx.fillStyle = p.color;
      ctx.strokeStyle = '#000';
      ctx.lineWidth = 1;
      ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
      // simple icon
      ctx.fillStyle = '#000';
      ctx.font = 'bold 11px sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(p.type === 0 ? 'W' : '×', p.x, p.y);
    }
  }

  // Draw particles
  function drawParticles() {
    ctx.globalAlpha = 1;
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      ctx.fillStyle = p.color;
      ctx.fillRect(p.x, p.y, 2, 2);
      // update life handled in update loop
    }
    ctx.globalAlpha = 1;
  }

  // Canvas render
  function render() {
    // Clear
    ctx.clearRect(0, 0, GAME_W, GAME_H);

    // Background subtle gradient
    const grad = ctx.createLinearGradient(0, 0, 0, GAME_H);
    grad.addColorStop(0, '#0a0f1a');
    grad.addColorStop(1, '#0a0b1a');
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, GAME_W, GAME_H);

    // Bricks
    drawBricks();

    // Power-ups
    drawPowerUps();

    // Balls
    for (const b of balls) {
      if (b.alive) drawBall(b);
    }

    // Paddle (draw last so it stays on top)
    ctx.fillStyle = '#3498db';
    drawRoundedRect(PADDLE.x, PADDLE.y, PADDLE.w, PADDLE.h, 6, '#3498db');
    // Paddle border
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#1b6bcf';
    ctx.strokeRect(PADDLE.x, PADDLE.y, PADDLE.w, PADDLE.h);

    // Particles
    if (particles.length > 0) {
      // simple particles - give them slight gravity
      for (let i = particles.length - 1; i >= 0; i--) {
        const p = particles[i];
        p.x += p.vx * 1/60;
        p.y += p.vy * 1/60;
        p.vy += 60 * (1/60); // gravity-ish
        p.life -= 1/60;
        if (p.life <= 0) {
          particles.splice(i, 1);
        } else {
          ctx.fillStyle = p.color;
          ctx.fillRect(p.x, p.y, 2, 2);
        }
      }
    }

    // Boosted paddle hint (visual)
    if (PADDLE._boostEnd) {
      // show a glow
      const glow = (PADDLE._boostEnd - performance.now()) / 1000;
      ctx.strokeStyle = 'rgba(0,255,0,.6)';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(PADDLE.x + PADDLE.w/2, PADDLE.y + PADDLE.h/2, Math.min(PADDLE.w/2, 26) + 4, 0, Math.PI * 2);
      ctx.stroke();
      if (glow <= 0) {
        // boost ends
        PADDLE._boostEnd = undefined;
      }
    }

    // HUD: score and lives drawn outside (DOM updates)
  }

  // Update world (with fixed sub-steps to avoid tunneling)
  function update(dt) {
    // Keep score HUD updated
    hud.textContent = `Score: ${score} | Lives: ${lives}`;

    // Start/End conditions
    if (lives <= 0) {
      gameOver = true;
      overlayLose.style.display = 'flex';
      finalScoreLose.textContent = String(score);
      return;
    }

    if (bricksRemaining <= 0) {
      gameWin = true;
      overlayWin.style.display = 'flex';
      finalScoreWin.textContent = String(score);
      return;
    }

    // Paddle movement
    if (!started) {
      // Demo mode: AI follows the ball
      demoMode = true;
      const targetX = balls[0] ? balls[0].x - PADDLE.w * 0.5 : PADDLE.x;
      // Smooth follow
      const speed = 900; // px/s
      const diff = targetX - PADDLE.x;
      const maxStep = speed * dt;
      if (Math.abs(diff) > 1) {
        PADDLE.x += diff > 0 ? Math.min(diff, maxStep) : Math.max(diff, -maxStep);
        PADDLE.x = clamp(PADDLE.x, 0, GAME_W - PADDLE.w);
      }
    } else {
      // User control: mouse or keyboard
      const target = input.mouseX - PADDLE.w * 0.5;
      if (target !== PADDLE.x) {
        // simple easing
        const dx = target - PADDLE.x;
        const step = 900 * dt;
        if (Math.abs(dx) < step) PADDLE.x = target;
        else PADDLE.x += dx > 0 ? step : -step;
        PADDLE.x = clamp(PADDLE.x, 0, GAME_W - PADDLE.w);
      }

      // Keyboard optional (left/right)
      if (input.left) {
        PADDLE.x -= PADDLE.speed * dt;
        input.left = false;
      }
      if (input.right) {
        PADDLE.x += PADDLE.speed * dt;
        input.right = false;
      }
      // keep in bounds
      PADDLE.x = clamp(PADDLE.x, 0, GAME_W - PADDLE.w);
    }

    // If boost is active, check end time
    if (PADDLE._boostEnd && performance.now() >= PADDLE._boostEnd) {
      PADDLE._boostEnd = undefined;
      // shrink back to normal width
      PADDLE.w = Math.max(110, PADDLE.w - 40);
    }

    // Move balls with small sub-steps to prevent tunneling
    const subStep = 1/120; // 8.33 ms
    let t = dt;
    while (t > 0) {
      const step = Math.min(subStep, t);
      t -= step;

      // Update each ball
      for (let i = balls.length - 1; i >= 0; i--) {
        const b = balls[i];
        if (!b.alive) continue;

        const prevX = b.x;
        const prevY = b.y;

        // Move
        b.x += b.vx * step;
        b.y += b.vy * step;

        // Wall collisions (left/right/top)
        if (b.x - b.r < 0) {
          b.x = b.r;
          b.vx *= -1;
        } else if (b.x + b.r > GAME_W) {
          b.x = GAME_W - b.r;
          b.vx *= -1;
        }
        if (b.y - b.r < 0) {
          b.y = b.r;
          b.vy *= -1;
        }

        // Bottom (miss)
        if (b.y - b.r > GAME_H) {
          balls.splice(i, 1);
          // If no balls left, lose a life and respawn if possible
          if (balls.length === 0) {
            lives--;
            if (lives > 0) {
              // reset a single ball
              const nb = createBall(GAME_W / 2, PADDLE.y - 18, Math.sin(0) * BALL_SPEED, -BALL_SPEED);
              balls.push(nb);
            }
          }
          continue;
        }

        // Paddle collision
        if (circleRectCollision(b, { x: PADDLE.x, y: PADDLE.y, w: PADDLE.w, h: PADDLE.h })) {
          // align ball just above paddle
          b.y = PADDLE.y - b.r - 1;
          // compute reflection based on hit position
          const offset = (b.x - (PADDLE.x + PADDLE.w * 0.5)) / (PADDLE.w * 0.5);
          const angle = offset * (Math.PI / 3);
          const speed = Math.hypot(b.vx, b.vy);
          b.vx = Math.sin(angle) * speed;
          b.vy = -Math.cos(angle) * speed;
        }

        // Brick collisions
        // stop after first collision in this substep to avoid multiple bounces in a single step
        let collidedBrick = null;
        for (let j = 0; j < bricks.length; j++) {
          const br = bricks[j];
          if (br.destroyed) continue;
          if (circleRectCollision(b, br)) {
            // Determine side of collision by comparing distances to brick edges
            const dxLeft = Math.abs(b.x - br.x);
            const dxRight = Math.abs(b.x - (br.x + br.w));
            const dyTop = Math.abs(b.y - br.y);
            const dyBottom = Math.abs(b.y - (br.y + br.h));
            const deltaX = Math.min(dxLeft, dxRight);
            const deltaY = Math.min(dyTop, dyBottom);

            if (deltaX < deltaY) b.vx *= -1;
            else b.vy *= -1;

            // Brick hit
            br.hitsLeft--;
            if (br.hitsLeft <= 0) {
              br.destroyed = true;
              bricksRemaining--;
              score += (br.color === '#2ecc71' ? 100 : 150);
              // Drop power-up with small probability
              if (br.powerUpType != null && br.powerUpType !== undefined) {
                if (Math.random() < 0.65) {
                  const pu = createPowerUp(br.x + br.w/2, br.y + br.h/2, br.powerUpType);
                  activePowerUps.push(pu);
                }
              }
              // Particles
              spawnParticles(br.x + br.w/2, br.y + br.h/2, br.color);
            } else {
              // If not destroyed, visually indicate crack
              br.cracked = true;
              br.color = br.hitsLeft === 1 ? '#d35400' : br.color;
            }

            // Adjust ball position to avoid sticking into brick
            if (deltaX < deltaY) {
              // horizontal collision: separate along x
              if (b.x < br.x) b.x = br.x - b.r;
              else b.x = br.x + br.w + b.r;
            } else {
              // vertical collision: separate along y
              if (b.y < br.y) b.y = br.y - b.r;
              else b.y = br.y + br.h + b.r;
            }

            collidedBrick = br;
            break;
          }
        }
        // If a brick was destroyed this substep, create a few extra effects (optional)
        // (Handled above)
      }

      // Power-ups movement
      for (let k = activePowerUps.length - 1; k >= 0; k--) {
        const pu = activePowerUps[k];
        pu.y += pu.vy * step;
        // Collect if hits paddle
        if (circleRectCollision({ x: pu.x, y: pu.y, r: pu.size/2 }, { x: PADDLE.x, y: PADDLE.y, w: PADDLE.w, h: PADDLE.h })) {
          applyPowerUp(pu);
          activePowerUps.splice(k, 1);
        } else if (pu.y - pu.size/2 > GAME_H) {
          activePowerUps.splice(k, 1);
        }
      }

      // Update boost end (if boost expired handled in render/path)
    } // end sub-steps

  }

  // Game loop
  let lastT = performance.now();
  function loop(now) {
    const dt = Math.min(0.033, (now - lastT) / 1000); // cap delta to avoid jumps
    lastT = now;

    if (!gameOver && !gameWin) {
      update(dt);
      render();
    }

    requestAnimationFrame(loop);
  }

  // Mouse & keyboard input
  canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    // Convert to CSS px
    const px = (e.clientX - rect.left) * (canvas.width / rect.width); // maps to canvas internal coords
    input.mouseX = px / (canvas.width / canvas.clientWidth) * GAME_W; // more robust? We'll approximate directly:
    // Simpler mapping: keep mouseX in game-space
    const scaledX = e.clientX - rect.left;
    const scale = GAME_W / rect.width;
    input.mouseX = scaledX * scale;
  });

  window.addEventListener('keydown', (e) => {
    // Without starting, we don't rely on keyboard to avoid capturing before click
    if (!started) {
      // any key starts
      startGame();
      return;
    }
    if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') {
      input.left = true;
    } else if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') {
      input.right = true;
    }
  });

  // Start button
  startBtn.addEventListener('click', startGame);
  restartBtnWin.addEventListener('click', () => { initGame(); overlayWin.style.display = 'none'; startGame(true); });
  restartBtnLose.addEventListener('click', () => { initGame(); overlayLose.style.display = 'none'; startGame(true); });

  // Start the game flow
  function startGame(force = false) {
    if (started && !force) return;
    started = true;
    overlayStart.style.display = 'none';
    // If force is used after a reset, put the game in playing mode
    // The demo autopilots paddle until user moves mouse/or presses keyboard, which is captured above
  }

  // Initialize and start the loop
  initGame();
  // Give a short moment to render the initial frame
  requestAnimationFrame(loop);

  // Expose a tiny status for debugging in console (optional)
  window.__BREAKOUT_STATE__ = {
    score, lives, bricksRemaining, balls: balls.length
  };

})();
</script>
</body>
</html>
Reasoning trace (13,696 tokens)
**Calculating angles and velocities**

I'm exploring how hit position affects velocity and angle. A center hit means very little horizontal velocity, while edge hits lead to higher speeds. I see that the angle is related to the ratio of horizontal to vertical speed. If I want to create a dramatic angle, I can adjust the horizontal speed accordingly. For example, to achieve a 75-degree angle, I may need a smaller vertical speed. It’s intriguing how these adjustments change the resulting angles!**Adjusting bounce angles**

I’m considering the bounce angle requirement, which allows for angles between 45 to 60 degrees based on hit position. A center hit goes straight up, while a hit at the edge results in a sharper angle. To achieve a balance, I can set vertical velocity to 90% of ball speed and adjust horizontal velocity based on hit position. I want to ensure that I dynamically calculate these velocities to maintain a consistent speed and appropriate angle. It’s quite an interesting challenge!**Calculating bounce dynamics**

I'm working on adjusting the bounce angle based on where the ball hits, with center hits resulting in a straight bounce and edge hits yielding sharper angles. The bounce angle can be calculated from the maximum angle of around 75 degrees. I’ll use trigonometric functions to determine horizontal and vertical velocities based on this angle. If a center hit results in no horizontal velocity, the implementation seems plausible, allowing for a smooth and dynamic response when the ball bounces!**Implementing bounce mechanics and bricks**

I'm planning to implement the bounce mechanics based on where the ball hits the paddle, capping hit position between -1 and 1. The bounce angle will be determined using this hit position and a maximum angle of π/3. I'll calculate horizontal and vertical velocities while ensuring the overall speed remains constant. For edge cases like hitting bricks or walls, I want to account for these velocities carefully. Regarding the bricks, I’ll create a grid with at least six rows, featuring normal bricks and tougher ones that crack after one hit. This sounds promising!**Designing brick and collision mechanics**

I'm creating a Brick object that includes properties like position, size, and hits remaining, along with a boolean for whether it's cracked. Each brick can possibly drop a power-up when destroyed, which adds an exciting feature. I need to ensure the ball never passes through bricks or the paddle, so I’m planning to implement continuous collision detection. This involves dividing time into small steps for accurate collision detection and adjusting ball velocity based on the collision side. It’s a complex but interesting challenge!**Implementing collision and brick mechanics**

I'm focusing on implementing the collision detection for the ball and bricks. I'll determine from which side of the brick the ball comes from, and invert its velocity accordingly. To do this, I'll track the ball's previous position before updating its position during each substep. If the ball collides with a brick, I'll decide the axis of collision by comparing distances to each side. Also, I need to handle collisions with walls and the paddle, and manage brick destruction, power-ups, and life reductions if the ball falls off the screen. It's quite a lot to juggle!**Setting up gameplay mechanics**

I'll ensure the ball's speed is consistent while handling collisions with the paddle. If the ball hits from above, it bounces; if from below, it doesn't. Power-ups will drop from some bricks, influencing paddle width or adding more balls. I'll implement a basic particle effect when bricks break and maintain a score system. There will be at least 6 rows of bricks with a grid layout, some carrying power-ups. I need to set up game over and victory messages, along with an AI-controlled demo that auto-plays until the user clicks to start. It's quite a detailed setup!**Implementing gameplay features**

I’m setting up a timer to have the paddle follow the ball gradually while ensuring the UI shows a "Click to start" message. The autoplayer will run initially until the user clicks. Once started, I plan for user control to replace the autoplayer, which will stop. I’ll also create a responsive canvas design that fits different screen sizes without scrollbars. Device pixel ratio scaling will ensure crisp visuals, and I'll implement resizing functions to adjust canvas dimensions appropriately. It’s all coming together!**Implementing game mechanics**

I'm setting up a robust scaling system for a 1280x800 viewport and including a start message overlay to dim the canvas. The game will feature a three lives system and a victory screen. I’ll generate a grid of bricks with normal and tough variations, and implement power-ups that affect paddle width or create new balls. The game states will include intro, playing, game over, and victory states. I’ll also develop AI for paddle control during the demo and allow manual control afterward, ensuring a smooth gameplay experience!**Implementing game mechanics and features**

I’ll add a crack property to bricks, indicating their strength; 1 for normal and 2 for tough. Each brick will have a power-up type assigned randomly. Tough bricks will show a crack effect after being hit, changing colors appropriately. Scoring will depend on brick type, with normal bricks giving 100 points and tough ones 150. Power-ups will drop with a chance upon brick destruction, and I’ll create visual icons for them. The game will include lives, score display, and screens for game over or victory with a restart option. The autoplayer will guide the demo mode for three seconds before allowing user control!**Implementing game setup and drawing functions**

I’ll create a "Rect" object for bricks, containing properties like position, size, hit points, and power-up type. I’ll also implement functions for drawing elements such as balls, paddles, bricks, power-ups, and the HUD. Constants will define the game’s dimensions, and I’ll manage canvas resizing to ensure crisp graphics across devices by adjusting based on the device pixel ratio. The goal is to set up a seamless user experience while maintaining game integrity, employing techniques for scaling without causing blurriness. I'll also include overlays for start and game results.**Implementing ball mechanics and updating game state**

I'm setting up an array for "Balls" and configuring their speed to ensure proper autoplayer functionality. I’ll use a frame rate of 60 FPS and break down the update process into smaller steps to prevent the balls from passing through objects. Handling paddle movement, ball positioning, and collisions with walls and bricks is crucial. I’ll make sure to accurately check and respond to collisions, adjust ball direction, and manage the destruction of bricks, which might spawn particles and power-ups. Overall, my goal is to create a smooth and engaging gameplay experience.**Implementing collision detection and game structure**

I’m working on a robust system for detecting collisions between balls and bricks using functions that assess distance between circles and rectangles. I’ll also keep track of the remaining bricks to trigger a victory overlay when they’re all destroyed. There are some key UI elements to implement, like overlays for the start and win conditions, while ensuring the canvas is set up correctly in the DOM. Memory efficiency is important, so I’ll avoid duplicating elements like button IDs across overlays. Let’s get into coding this!**Implementing game mechanics and autoplayer**

I'm working on the autoplayer feature, which will start in demo mode. In this mode, the paddle will gradually approach the ball. I need to safely handle key events for manual control and ensure paddles don’t move out of bounds. For power-ups, I'll create a mechanism to spawn and manage them effectively. Each ball will have specific properties like speed and bounce, and I want to ensure the code runs error-free. Let’s get started on coding all of this!