Models 44

North Mini Code (free)

by CohereReasoningClean
Open in new window

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

How it went

Total time
2 min 1 s
Cost
$0
Output tokens
19,411
Spent thinking
16,415 tokens
Speed
161 tok/s
Console errors
0
First token after
229 ms
First visible text after
1 min 35 s
Prompt tokens
495
Code size
12.0 KB
Finish reason
stop
Served by
Cohere
Model id
cohere/north-mini-code:free
Reasoning sent
model default
Max output
32,000 tokens
Generated
Nov 9, 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 · 464 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Breakout Demo</title>
<style>
  body { margin:0; padding:0; background:#000; overflow:hidden; font-family:Arial,sans-serif; }
  #gameContainer { position:relative; width:100%; height:0; padding-bottom:56.25%; /* 16:9 */ }
  #gameCanvas { position:absolute; top:0; left:0; width:100%; height:100%; image-rendering:pixelated; }
  #overlay {
    position:absolute; inset:0;
    background:rgba(0,0,0,0.7); color:#fff;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    z-index:10; pointer-events:auto;
  }
  #startMessage { font-size:2em; text-align:center; }
  #restartBtn {
    margin-top:20px; padding:10px 20px; font-size:1.5em; cursor:pointer;
    display:none;
  }
</style>
</head>
<body>
<div id="gameContainer">
  <canvas id="gameCanvas" tabindex="0"></canvas>
</div>
<div id="overlay">
  <div id="startMessage">Click or press a key to start</div>
  <button id="restartBtn">Restart</button>
</div>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
const startMessage = document.getElementById('startMessage');
const restartBtn = document.getElementById('restartBtn');

// Fixed logical resolution (will be scaled via CSS)
canvas.width = 800;
canvas.height = 600;

// Game state
let gameState = 'MENU';      // MENU, PLAYING, GAMEOVER, VICTORY
let gameStarted = false;
let aiMode = false;           // true while simple AI controls paddle
let score = 0;
let lives = 3;
let particles = [];
let powerUps = [];

// Paddle
let paddle = {
  x: 0,
  y: canvas.height - 30,      // top of paddle
  width: 100,
  height: 15,
  defaultWidth: 100,
  extraWidth: 50,
  speed: 8,
  widePaddleActive: false,
  widePaddleStart: 0
};

// Balls
let balls = [];

// Bricks
let bricks = [];

// Helper: spawn particles
function spawnParticles(x, y, color, count = 8) {
  for (let i = 0; i < count; i++) {
    let angle = (Math.PI * 2 * i) / count + (Math.random() - 0.5) * 0.5;
    let speed = 2 + Math.random() * 3;
    particles.push({
      x, y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 20,
      maxLife: 20,
      radius: 2 + Math.random() * 2,
      color
    });
  }
}

// Draw power-up icon inside a brick
function drawPowerUpIcon(cx, cy, type) {
  ctx.save();
  ctx.beginPath();
  if (type === 'widePaddle') {
    ctx.fillStyle = '#ff0';
    ctx.fillRect(cx - 5, cy - 5, 10, 10);
  } else if (type === 'multiBall') {
    ctx.fillStyle = '#f0f';
    ctx.moveTo(cx, cy - 6);
    ctx.lineTo(cx, cy + 6);
    ctx.moveTo(cx - 6, cy);
    ctx.lineTo(cx + 6, cy);
    ctx.fill();
  }
  ctx.restore();
}

// Generate bricks (6 rows)
function generateBricks() {
  const rows = 6;
  const cols = 8;
  const w = 70;
  const h = 20;
  const gap = 5;
  const top = 30;
  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      const x = 20 + col * (w + gap);
      const y = top + row * (h + gap);
      // 30% chance of double‑health brick
      const maxHealth = Math.random() < 0.3 ? 2 : 1;
      let hasPowerUp = false;
      let powerUpType = null;
      if (maxHealth === 1 && Math.random() < 0.2) {
        hasPowerUp = true;
        powerUpType = Math.random() < 0.5 ? 'widePaddle' : 'multiBall';
      } else if (maxHealth === 2 && Math.random() < 0.1) {
        hasPowerUp = true;
        powerUpType = Math.random() < ramethyl() < 0.5 ? 'widePaddle' : 'multiBall';
      }
      bricks.push({
        x, y,
        width: w,
        height: h,
        maxHealth,
        health: maxHealth,
        hasPowerUp,
        powerUpType,
        color: maxHealth === 2 ? '#3498db' : '#e74c3c',
        cracked: false
      });
    }
  }
}

// Create a ball at the paddle
function createBall() {
  balls.push({
    x: paddle.x + paddle.width / 2,
    y: paddle.y - 10,
    radius: 10,
    speed: 5,
    dx: (Math.random() - 0.5) * 4,
    dy: -5
  });
}

// Reset whole game
function resetGame() {
  bricks = [];
  generateBricks();
  balls = [];
  createBall();
  paddle.x = (canvas.width - paddle.width) / 2;
  paddle.y = canvas.height - paddle.height - 20;
  paddle.widePaddleActive = false;
  particles = [];
  powerUps = [];
  score = 0;
  lives = 3;
  gameState = 'PLAYING';
  aiMode = true; // simple AI starts
}

// Update paddle when AI is active
function updatePaddleAI() {
  if (!aiMode || balls.length === 0) return;
  const b = balls[0];
  let targetX = b.x - paddle.width / 2;
  if (targetX < paddle.x) {
    paddle.x = Math.max(paddle.x - paddle.speed, targetX);
  } else if (targetX > paddle.x) {
    paddle.x = Math.min(paddle.x + paddle.speed, targetX);
  }
  paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, paddle.x));
}

// Clamp paddle X and set directly (manual control)
function setPaddleX(x) {
  paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, x));
}

// Apply power‑up effect
function applyPowerUp(pu) {
  if (pu.type === 'widePaddle') {
    paddle.widePaddleActive = true;
    paddle.widePaddleStart = performance.now();
    paddle.width = paddle.defaultWidth + paddle.extraWidth;
  } else if (pu.type === 'multiBall') {
    // spawn two extra balls
    for (let i = 0; i < 2; i++) {
      balls.push({
        x: paddle.x + paddle.width / 2,
        y: paddle.y - 10,
        radius: 10,
        speed: 5,
        dx: (Math.random() - 0.5) * 4,
        dy: -5
      });
    }
  }
  // power‑up consumed
}

// Draw everything
function draw() {
  ctx.fillStyle = '#000';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // Bricks
  for (const b of bricks) {
    ctx.fillStyle = b.color;
    ctx.fillRect(b.x, b.y, b.width, b.height);
    if (b.cracked) {
      ctx.strokeStyle = '#555';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(b.x, b.y);
      ctx.lineTo(b.x + b.width, b.y + b.height);
      ctx.moveTo(b.x + b.width, b.y);
      ctx.lineTo(b.x, b.y + b.height);
      ctx.stroke();
    }
    if (b.hasPowerUp) {
      drawPowerUpIcon(b.x + b.width / 2, b.y + b.height / 2, b.powerUpType);
    }
  }

  // Particles
  for (const p of particles) {
    ctx.globalAlpha = p.life / p.maxLife;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.globalAlpha = 1;
  }

  // Power‑ups
  for (const pu of powerUps) {
    ctx.fillStyle = pu.type === 'widePaddle' ? '#0f0' : '#ff0';
    ctx.beginPath();
    ctx.arc(pu.x, pu.y, pu.radius, 0, Math.PI * 2);
    ctx.fill();
  }

  // Paddle
  ctx.fillStyle = '#fff';
  ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);

  // Balls
  for (const b of balls) {
    ctx.fillStyle = '#0ff';
    ctx.beginPath();
    ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
    ctx.fill();
  }

  // UI
  ctx.fillStyle = '#fff';
  ctx.font = '20px Arial';
  ctx.fillText(`Score: ${score}`, 20, 30);
  ctx.fillText(`Lives: ${lives}`, 20, 60);
}

// Game loop
function gameLoop(timestamp) {
  // Wide‑paddle timer
  if (paddle.widePaddleActive) {
    if (timestamp - paddle.widePaddleStart >= 10000) {
      paddle.widePaddleActive = false;
      paddle.width = paddle.defaultWidth;
    }
  }

  // Update AI paddle
  updatePaddleAI();

  // Move balls
  for (const b of balls) {
    b.x += b.dx;
    b.y += b.dy;
  }

  // Update particles
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.x += p.vx;
    p.y += p.vy;
    p.vy += 0.2; // gravity
    p.life--;
    if (p.life <= 0) particles.splice(i, 1);
  }

  // Update power‑ups
  for (let i = powerUps.length - 1; i >= 0; i++) {
    const pu = powerUps[i];
    pu.y += pu.vy;
    if (pu.vx) pu.x += pu.vx;
    // Remove if off‑screen
    if (pu.y - pu.radius > canvas.height) {
      powerUps.splice(i, 1);
    }
    // Paddle collision
    if (pu.y + pu.radius >= paddle.y &&
        pu.x >= paddle.x && pu.x <= paddle.x + paddle.width) {
      applyPowerUp(pu);
      powerUps.splice(i, 1);
    }
  }

  // Collisions
  for (let i = balls.length - 1; i >= 0; i++) {
    const b = balls[i];

    // Walls
    if (b.x - b.radius < 0 || b.x + b.radius > canvas.width) b.dx = -b.dx;
    if (b.y - b.radius < 0) b.dy = -b.dy;

    // Paddle
    if (b.y + b.radius >= paddle.y && b.y - b.radius <= paddle.y + paddle.height) {
      if (b.x >= paddle.x && b.x <= paddle.x + paddle.width) {
        const offset = (b.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2);
        const limited = Math.max(-1, Math.min(1, offset));
        const angle = limited * Math.PI / 3; // max 60°
        const speed = b.speed;
        b.dx = Math.sin(angle) * speed;
        b.dy = -Math.cos(angle) * speed;
      }
    }

    // Bricks
    for (let j = bricks.length - 1; j >= 0; j--) {
      const br = bricks[j];
      if (br.health <= 0) continue;
      const closestX = Math.max(br.x, Math.min(b.x, br.x + br.width));
      const closestY = Math.max(br.y, Math.min(b.y, br.y + br.height));
      const dx = b.x - closestX;
      const dy = b.y - closestY;
      if (dx * dx + dy * dy < b.radius * b.radius) {
        // reflect
        if (b.x < br.x || b.x > br.x + br.width) b.dx = -b.dx;
        if (b.y < br.y || b.y > br.y + br.height) b.dy = -b.dy;
        // brick damage
        br.health--;
        if (br.health <= 0) {
          score += br.maxHealth === 2 ? 20 : 10;
          spawnParticles(br.x + br.width / 2, br.y + br.height / 2, br.color, 8);
          if (br.hasPowerUp) {
            powerUps.push({
              x: br.x + br.width / 2,
              y: br.y + br.height,
              radius: 6,
              vy: 2,
              vx: (Math.random() - 0.5) * 1,
              type: br.powerUpType
            });
          }
          bricks.splice(j, 1);
        } else if (br.health < br.maxHealth) {
          br.cracked = true;
        }
      }
    }

    // Ball out of bottom
    if (b.y + b.radius > canvas.height) {
      lives--;
      balls.splice(i, 1);
      if (balls.length === 0) {
        if (lives > 0) createBall();
      }
      if (lives <= 0) {
        gameState = 'GAMEOVER';
        startMessage.textContent = `Game Over\nScore: ${score}`;
        restartBtn.style.display = 'inline-block';
        overlay.style.display = 'flex';
      }
    }
  }

  // Victory?
  if (bricks.length === 0 && gameState === 'PLAYING') {
    gameState = 'VICTORY';
    startMessage.textContent = `Victory!\nScore: ${score}`;
    restartBtn.style.display = 'inline-block';
    overlay.style.display = 'flex';
  }

  draw();
  requestAnimationFrame(gameLoop);
}

// UI actions
function startGame() {
  if (gameState !== 'MENU' || gameStarted) return;
  overlay.style.display = 'none';
  resetGame();
  gameStarted = true;
  requestAnimationFrame(gameLoop);
}

function showGameOver() {
  gameState = 'GAMEOVER';
  startMessage.textContent = `Game Over\nScore: ${score}`;
  restartBtn.style.display = 'inline-block';
  overlay.style.display = 'flex';
}

function showVictory() {
  gameState = 'VICTORY';
  startMessage.textContent = `Victory!\nScore: ${score}`;
  restartBtn.style.display = 'inline-block';
  overlay.style.display = 'flex';
}

function restartGame() {
  gameStarted = false;
  aiMode = false;
  overlay.style.display = 'none';
  restartBtn.style.display = 'none';
  startMessage.textContent = 'Click or press a key to start';
  resetGame();
}

// Listeners
overlay.addEventListener('click', startGame);
restartBtn.addEventListener('click', restartGame);

document.addEventListener('keydown', (e) => {
  if (gameState === 'MENU' && !gameStarted) {
    startGame();
  } else if (gameState === 'PLAYING') {
    if (e.key === 'ArrowLeft') {
      setPaddleX(paddle.x - paddle.speed);
      aiMode = false;
    } else if (e.key === 'ArrowRight') {
      setPaddleX(paddle.x + paddle.speed);
      aiMode = false;
    }
  }
});

canvas.addEventListener('mousemove', (e) => {
  if (gameState !== 'PLAYING' || !gameStarted) return;
  const rect = canvas.getBoundingClientRect();
  const mx = e.clientX - rect.left;
  setPaddleX(mx - paddle.width / 2);
  aiMode = false;
});

// Initial overlay text
startMessage.textContent = 'Click or press a key to start';
</script>
</body>
</html>
Reasoning trace (16,415 tokens)
We need to generate a self-contained HTML file with the breakout game, using vanilla JS and a canvas. Must be polished. The constraints: show a screenshot after 3 seconds, game must start automatically with a simple AI playing behind a "Click or press a key to start" message. The iframe receives keyboard events only after user clicks it. So we need to display that message and then start game upon click or key press. It also must have mouse control for paddle and left/right arrow keys.

The game features:

- Paddle: mouse and arrow keys.
- Bounce angle depends on where ball hits paddle (center = straight up, edges = sharp).
- At least 6 rows of bricks, mixing normal bricks (one hit) and tougher bricks needing two hits and visibly crack after first.
- Some bricks carry a power-up icon; when broken they drop it, and catching it with paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls).
- Ball must never pass through bricks or paddle.
- Particles when a brick breaks.
- 3 lives, score displayed. Game over and victory screens with restart option.

- Must be self-contained, no external resources except allowed CDN URLs; we can embed all CSS, JS in <style> and <script> inside HTML.

- Must adapt to viewport 1280×800, gracefully to 375px.

- Game must start in a self-playing demo mode (simple AI) behind "Click or press a key to start". So after user interacts, start game automatically, presumably with an AI controlling maybe the paddle? Or the simple AI could control the ball? Actually the requirement: "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." So the AI will play: maybe AI controls paddle automatically for the first few seconds to show gameplay, but after the user clicks, the game starts and the player can then control using mouse or keys. But to judge the demo, we need the game to be playing automatically initially. Actually the requirement says the game must start in 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. That likely means that after the user clicks the canvas (or presses a key), the game starts with a simple AI that automatically plays, maybe controlling the paddle, until the user can take over? Or the AI could simulate the player's moves? Usually self-playing demo means it just plays automatically, you don't need to control. But the requirement also says paddle controlled with mouse and left/right arrow keys. So after the initial demo, the user can take over. So maybe initially the AI plays, but the controls still work after start. But we can implement that after clicking, the game begins, and the paddle is controlled either by the AI (maybe moves randomly or towards ball) for the first few seconds, then we let the user control after first input? The spec says: "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." This suggests that the demo mode is the initial state where the AI plays automatically. Then, after that, the user can start controlling? Or perhaps we can start the game with a simple AI that plays (e.g., automatic ball and paddle movement) and when the user interacts (click or key), the AI pauses and the user can control. But we need to show the game playing in the screenshot after ~3 seconds. So the screenshot should capture the gameplay, which is the AI playing. So we need to show gameplay that is visible from the start, no user input needed to see the game. But there is a "Click or press a key to start" message overlay. So initially we show that overlay. Then after a user clicks or keypress, we hide overlay and start the AI playing. The AI might automatically control the paddle and ball. The ball may also move automatically (like simple physics). But the paddle also needs to be controlled with mouse and arrow keys. However, we can have the AI automatically move the paddle to keep the ball in play for the demo. After user interacts later (like second click), we could allow manual control, but not required for the demo; the auto-play is enough. The requirement doesn't specify that the user must be able to control after the demo; it only says paddle controlled with mouse and left/right arrow keys. So we should implement that in general; but the demo will start with AI playing, but the controls are still active. The user could still interact, but not needed for the screenshot.

We need to handle particles: when a brick breaks, show particle effects.

We need to manage power-ups: some bricks have icon; when broken, drop power-up; catching it extends paddle width for 10 seconds or spawns multi-ball (3 balls). Need to manage multiple balls? Possibly multi-ball spawns additional balls that follow? Implementation: maybe we start with 1 ball; when power-up is caught, we either increase paddle width (for 10 sec) or spawn extra balls (maybe 2 more). The extra balls could all bounce with similar physics. Or we could treat "multi-ball" as spawning 2 extra balls that travel outward.

We need at least 6 rows of bricks. Mix of normal and tougher bricks (2-hit) that crack visibly after first hit. So we need a visual representation for cracked bricks: maybe show a smaller inner brick shape or overlay. They need 2 hits; after first hit, they are partially broken (cracked). Possibly we can have a "health" property for each brick; if health >1, on hit reduce to 1 and show cracked state. On second hit, break with particles and drop power-up perhaps.

Some bricks carry power-up icon: need to represent visually. Could be a small icon drawn inside the brick, e.g., a star shape or a small circle. When broken, drop a power-up item that falls down. The paddle can catch it if its bottom intersects with paddle.

Ball physics: must never pass through bricks or paddle. So we need proper collision detection and response with correct angle based on paddle hit position.

Game over after 3 lives lost (ball goes below paddle). Display lives and score. Victory screen when all bricks destroyed. Restart option: a button to restart.

Need to adapt to viewport: responsive canvas that scales within container.

We must be self-contained, everything inline.

We need to generate exactly one fenced code block with the HTML file.

We cannot use any external libraries.

We'll structure HTML: <html>, <head> with style, <body> with canvas and overlay message.

In JS, we'll define game loop with requestAnimationFrame, state management.

We need to ensure it works in Chrome, no console errors.

We need to think about the design: we can have canvas sized to container (or fixed size like 800x600). Use CSS to make canvas responsive: maybe width: 100%; height: auto; but need to preserve aspect ratio.

We'll create a container div with id="gameContainer", inside canvas with id="gameCanvas". We'll overlay a start screen div with message and maybe a "Press to start" button.

We need to handle mouse and keyboard events: on mouse move, update paddleX; on keydown for left/right arrow, update paddleX with some speed. We'll need to clamp paddleX within canvas bounds.

Ball physics: ball has x, y, dx, dy. On each frame, update ball position. Collision detection:

- With left/right walls: reverse dx.

- With top wall: reverse dy.

- With paddle: if ball's y+radius >= paddleY and ball's x between paddleX and paddleX + paddleWidth, compute angle based on relative hit position. Use formula: hitOffset = (ball.x - (paddleX + paddleWidth/2)) / (paddleWidth/2); angle = hitOffset * maxAngle (like 60 degrees). Then set ball dx, dy accordingly: dy = -abs(speed * sin(angle)), dx = speed * cos(angle) maybe. Or compute new velocity vector.

- With bricks: detect collisions, handle both normal and double hit. For double hit: first hit reduces health and shows cracked (draw inner empty). On second hit, break (like normal brick). For normal bricks: break.

Also detect ball hitting bottom: if ball.y + radius > canvas.height (paddle y?), then lose a life. If lives <=0, show game over.

When bricks broken: spawn particles: we can have small line segments or circles moving outward with velocity. For simplicity, we can create a particle object with x, y, vx, vy, life, color. Each frame reduce life and draw. When brick broken, spawn multiple particles (e.g., 5-10). Use colors matching bricks.

Power-ups: some bricks will have a power-up icon. When broken, drop a power-up item that falls down with gravity (maybe dx=0, dy+=gravity). The item can be represented as a small sprite (like a star). When paddle catches it (if item y+radius >= paddleY and item x within paddle bounds), apply effect: either extend paddle width for 10 seconds, or spawn multiple balls (like additional balls that move). Also need to remove the power-up item.

We also need to keep track of time for power-ups: e.g., after paddle width increase, revert after 10 seconds.

Victory: when all bricks are destroyed, show victory screen with restart button.

Restart: resets all state.

We need to use requestAnimationFrame loop. We'll also need to handle UI overlay: start message. When user clicks/taps or presses a key, we hide start overlay and start game loop.

Because the AI will start playing automatically, we need to simulate movement without player input. But the code will start game after user interaction, and then the game loop runs. The paddle could be controlled by AI (maybe moving to follow ball). We could set AI that updates paddleX based on ball position at start. That might be simple: just set paddleX = canvas.width/2 (center). Or we could simulate ball movement in demo mode by also moving paddle automatically: we can set paddleX to follow ball's x position using some speed.

We need to ensure the demo runs automatically: we can set a timeout after user interaction to start the game, maybe with a "press to start" overlay displayed initially. When user clicks or presses a key, we hide overlay, then start loop. That will start ball and paddle AI. The AI could move paddle to track ball: paddleX = clamp(ball.x - paddleWidth/2, 0, canvas.width - paddleWidth). This is simple. So the demo will play with AI controlling paddle automatically. This satisfies the requirement: simple AI plays.

We also need to ensure the user can control paddle after that using mouse/keyboard events. So we need event listeners for mouse move and keydown that update a flag for manual control, maybe only if game is not in "AI demo" mode? Actually we can let both AI and manual control coexist: after user interacts, we could toggle manual control, but maybe it's simpler: after the start overlay is hidden, we set a variable `aiMode = true` initially. In the game loop, if aiMode and not user controlled, we move paddle based on AI (track ball). When user moves mouse or presses arrow keys, we set aiMode = false and follow input. That way both features work.

Alternatively, we can just have AI always controlling, but then manual controls wouldn't work. But requirement says paddle controlled with mouse and arrow keys. So we must support them. So we can have a manual control mode, where input updates paddleX. We can also have AI mode for the initial demo, but still allow manual override. The simplest: when start overlay is removed, we set a flag `gameStarted = true`. Then we begin the loop. In the loop, we always update paddle based on either AI (if manual input not recent) or based on input. But we need to detect input.

Simplify: we don't need a separate AI mode. We can just start the game with ball moving, and paddle initially placed at center. The ball will bounce off walls and bricks; without paddle movement, it might eventually fall off. So we need paddle to move to keep ball in play. We can implement an AI that automatically moves the paddle to intercept the ball, overriding any manual input for the first few seconds. Then after a certain time, we allow manual control. Or we could always allow manual control, but we need the game to be playable automatically without user input to capture screenshot. So we can have the AI control initially, and after some time (or after user input), we switch to manual. But for the screenshot after ~3 seconds, AI will have been controlling for at least 3 seconds, so the game will be in progress.

Thus steps:

1. Display start overlay with "Click or press a key to start". Show this overlay initially.

2. On any user interaction (click, keydown) once, we hide overlay (set display none). Then start game loop with `gameStarted = true`, set `aiMode = true` initially.

3. In game loop, update ball physics.

4. Update paddle: if aiMode, set paddleX = clamp(ball.x - paddleWidth/2, 0, canvas.width - paddleWidth). Optionally add a slight smoothing.

5. If manual input occurs (mouse move or arrow key), we set aiMode = false (so manual control takes over). Maybe also track that the user has taken control.

6. After some time (maybe after 5 seconds or after first manual input), we could keep aiMode false after user input. But we might also want to let the user control the paddle even after some time. That's fine.

7. Manage lives, score, bricks, power-ups.

Now, design specifics: we need to define brick layout: at least 6 rows. We'll have maybe 8 columns. Mix normal and tougher bricks: we can assign health 1 for normal, health 2 for tougher. Some bricks carry power-ups: maybe 20% of bricks have a power-up icon. We'll represent icon as a small star drawn inside brick when drawing bricks.

We need to render cracked bricks: for health = 2 but after first hit, we show a partially broken brick. We could draw a "cracked" version: draw the brick, then draw a small crack line across center or show inner empty region. Simpler: for health=2 and currentHealth=1 (i.e., cracked), we draw the brick with a lighter color (e.g., gray) to indicate it's weakened. Or we could draw a small X across brick to indicate cracked. The requirement says "visibly crack after the first one". So we need a visual crack. We can draw a small line or a broken pattern. A simple way: draw the brick, then draw a diagonal line across it (like X) using a contrasting color. Or we could draw an inner rectangle offset or a pattern of small dots.

I'll implement: for a brick with health 2 and health current = 1 (cracked), we draw a brick with base color, then draw a "cracked" overlay: e.g., draw two crossing lines (like an X) with a darker color. Or we could fill with a pattern using canvas fillText with some characters? Not ideal.

Alternatively, we can display a partially broken image by drawing the brick, then drawing a smaller inner rectangle missing part: maybe draw a rectangle inset by 4px from each side, leaving a border. That could show a crack effect. But may not look like crack.

Better: use canvas drawing of a "cracks" effect: fill with brick color, then draw some diagonal lines.

We'll implement simple: draw brick background color. If brick.health == 2 and brick.currentHealth == 1: draw two diagonal lines across the brick (like an X) using a darker color to indicate cracked.

For normal bricks (health=1), just draw.

Power-up icon: we can draw a small star or circle in the center of the brick. Let's draw a yellow star shape inside for power-up bricks. Use a simple star: can draw using path.

Particle effect: For each brick break, spawn N particles (maybe 8). Each particle has position at brick center, velocity random direction outward with some speed, color same as brick. They have a lifespan (e.g., 30 frames). In each frame, update position based on velocity, apply gravity (downward) maybe, reduce life. Draw as small circles.

Power-ups: we have two types: "widePaddle" and "multiBall". Represent power-up item as a small icon (e.g., a diamond shape) that falls from the brick break position with gravity. The item moves down with a speed and maybe slight horizontal drift. When it hits paddle, apply effect. For widePaddle: increase paddle width by e.g., 50 pixels for 10 seconds, then revert. For multiBall: spawn additional balls (e.g., 2 more) that behave like normal balls (maybe same direction as main ball). Should we limit number of balls? Not required.

We need to handle multiple balls: have an array of ball objects. Each ball has its own physics. When multi-ball power-up is collected, we can add new balls to the array. The main ball stays. For simplicity, we can add 2 extra balls that start at paddle center (or near paddle) with slightly offset velocities.

We need to track score: +10 per normal brick, +20 for tougher? Or maybe same.

Game over: If any ball's bottom goes below paddle Y + paddleHeight (i.e., paddle Y is at bottom of canvas), then lose a life. All balls removed? Typically one ball per life. For multiple balls scenario, if any ball goes out, maybe all balls reset? We'll just treat each ball independently: if any ball goes below paddle, decrement lives and reset that ball(s) to starting position (center near paddle). For simplicity, when lives <= 0, show game over.

Victory: if all bricks destroyed (bricks array empty) then show victory screen.

Restart: a button displayed on game over or victory that resets all game state and hides overlay.

The overlay start message: We'll have a semi-transparent overlay div with id="startOverlay" containing text "Click or press a key to start". It will be visible initially. We'll hide it on first user interaction.

We also need a restart screen overlay for game over/victory. Possibly same overlay but with different text and a button.

We need to adapt layout: We'll set canvas size to maybe 800x600, but container width should be responsive: use CSS to set canvas max width to 80% of viewport, maintain aspect ratio.

Implementation details:

HTML structure:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Breakout Game</title>
<style>
  body { margin:0; padding:0; background:#000; overflow:hidden; }
  #gameContainer { position:relative; width:100%; height:100vh; display:flex; align-items:center; justify-content:center; }
  #gameCanvas { max-width:100%; max-height:100vh; imageResize:none; }
  #overlay { position:absolute; inset:0; background:rgba(0,0,0,0.7); color:#fff; font-family:Arial,sans-serif; display:flex; flex-direction:column; align-items:center; justify-content:center; z-index:10; pointer-events:auto; }
  #overlay button { margin-top:20px; padding:10px 20px; font-size:1.2em; cursor:pointer; }
</style>
</head>
<body>
<div id="gameContainer">
  <canvas id="gameCanvas"></canvas>
</div>
<div id="overlay">
  <div id="startMessage">Click or press a key to start</div>
  <button id="restartBtn" style="display:none;">Restart</button>
</div>
<script>
... JS code ...
</script>
</body>
</html>
```

We need to style the overlay to be pointer-events auto, so clicks go to it. We need the start message to be shown initially, restart button hidden.

We also need to hide overlay on start: after user interaction, hide startMessage, maybe show restart button? Actually restart button is only shown on game over/victory. So we can set restart button display none initially.

In JS, we'll set up event listeners:

- window.addEventListener('click', startGame);
- window.addEventListener('keydown', startGame);

But we need to avoid conflict with click on canvas if overlay covers it; the overlay will be above canvas. So clicking overlay triggers startGame.

When startGame is triggered once, we hide startMessage and maybe show a "Press any key to start" but we hide overlay entirely? Or we could set overlay display none and let the canvas receive focus for keyboard events. But the spec says: "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." So we need to ensure keyboard events are only captured after user clicks. However, we are inside a sandboxed iframe. Not sure about specifics. But we can set focus on canvas after overlay removed, or rely on global keydown listeners.

We'll add event listeners on document for 'keydown' and 'mousemove', but we need to ensure they are captured after overlay is removed. We'll set a flag `gameInitialized` to false initially; after start, set to true; then we attach listeners or just keep them and check the flag.

Simplify: attach listeners globally, but only process input when `gameActive` (meaning after start). So keydown and mousemove events will update paddleX regardless of start? Actually before start, we may not want to control paddle yet. So we can check if `gameStarted` flag is true. However, the start message overlay will be above canvas, and clicking overlay should hide overlay. The keydown and mouse events might be captured by overlay; but we can handle start overlay click and keydown as start triggers, and after that we remove overlay and let events bubble to canvas. However, global listeners are fine.

Implementation details:

Define constants:

- canvas width and height: maybe get from canvas element or fixed. We'll get ctx = canvas.getContext('2d'), set canvas.width = 800, canvas.height = 600. But we need to make it responsive: set canvas style width/height in CSS to something like 100% and let JS scale drawing using devicePixelRatio.

Simplest: we set canvas width and height to fixed values (800x600). Then we use CSS to scale it: canvas { width: 100%; height: auto; max-height: 100vh; } But that may cause pixelation. But for demonstration, it's okay.

Alternatively, we can set canvas size based on window size: canvas.width = Math.min(window.innerWidth, 800) ??? But it's easier to use fixed 800x600 and let container be responsive.

We'll also need to handle resize: we can adjust canvas resolution using devicePixelRatio to preserve crispness.

We need to get ctx = canvas.getContext('2d'); then maybe set ctx.scale factor.

Let's design fixed resolution with high DPI: Use `const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr);` But we need to do this once at start. However, if window resizes, need to adjust. Could ignore for simplicity; canvas may be static 800x600. The requirement: page must look right in 1280×800 viewport and adapt gracefully down to 375px. So we need to make the canvas responsive.

We can define CSS:

#gameCanvas { width: 100%; height: auto; max-height: 100vh; image-rendering: crisp-edges; }

But we also want to maintain aspect ratio: we could use object-fit: contain. So:

#gameCanvas { width: 100%; height: 100%; object-fit: contain; }

But we also want to adapt to smaller width: maybe set max-width: 100%; max-height: 100vh; So the canvas will shrink accordingly.

We can also use position absolute to center.

Better: Use container with position:relative, canvas absolutely positioned with object-fit: contain.

Implementation: In CSS:

#gameContainer { position:relative; width:100%; height:0; padding-bottom:56.25%; /* 16:9 aspect ratio */ }

#gameCanvas { position:absolute; top:0; left:0; width:100%; height:100%; image-rendering: pixelated; }

This ensures aspect ratio.

Now for the JS, we need to define game state variables.

Game State:

- paddle: x, width, height, y (fixed at bottom). Paddle height maybe 15. Width default 100. Max width after power-up maybe 150. Also timer for power-up.

- balls: array of ball objects. Each ball: x, y, radius, dx, dy, active? Maybe we always have active balls. We start with one ball at center near top? Typically ball spawns from paddle? In breakout, ball starts from paddle. So we can start ball at paddle center with upward velocity.

- bricks: array of brick objects. Each brick: x, y, width, height, health, maxHealth (1 or 2), hasPowerUp (bool), broken (bool). We'll generate bricks in rows: for row in 0..5 (6 rows), for col in 0..7 (8 columns). We need to assign health and power-up randomly. Eg: chance 30% for health=2, 10% for power-up (if health=1?). Or both can co-exist. For simplicity: for each brick, randomly decide: if Math.random() < 0.3 -> health=2 else health=1. If health==1 and Math.random() < 0.2 -> hasPowerUp = true. Or for health=2 also can have power-up.

We'll also need to draw bricks: fill with colors: normal bricks maybe red, tougher maybe blue. Or use gradient.

- particles: array of particle objects. Each particle: x, y, vx, vy, life (initial 30), maxLife, radius, color.

- powerUps: array of power-up objects. Each power-up: x, y, radius, vy (downward speed), type ("widePaddle" or "multiBall"), active.

- score: integer.

- lives: integer, default 3.

- gameState: 'start', 'playing', 'gameOver', 'victory'.

- gameStarted: boolean indicates that start overlay is removed and game loop started.

- aiMode: boolean indicates if AI is controlling paddle (for demo). Initially true; on any manual input (mouse move or keydown) we set aiMode = false.

- powerUpTimers: we need to track expiration for wide paddle.

- start time for wide paddle.

- multiBallCount: maybe number of extra balls currently present.

- audio? Not needed.

Game Loop:

function gameLoop(timestamp) {
  if (!gameStarted) return; // but we will call only after start

  // clear canvas with black background
  ctx.fillStyle = '#000';
  ctx.fillRect(0,0,canvas.width, canvas.height);

  // update ball positions
  updateBalls();

  // update particles
  updateParticles();

  // update power-ups
  updatePowerUps();

  // check collisions: paddle, bricks, walls
  checkCollisions();

  // draw everything
  draw();

  // schedule next frame
  requestAnimationFrame(gameLoop);
}

We need to define functions:

- resetGame(): resets everything: lives, score, bricks, balls, particles, powerUps, paddle width, etc.

- startGame(): hides start overlay, resets game, sets gameStarted = true, maybe set aiMode = true. Also show canvas.

- updateBalls(): for each ball, move: ball.x += ball.dx; ball.y += ball.dy; apply gravity? Not needed; just simple physics.

- checkCollisions(): loops through balls.

Ball-to-wall collisions:

If ball.x - radius < 0 -> dx = -dx.

If ball.x + radius > canvas.width -> dx = -dx.

If ball.y - radius < 0 -> dy = -dy.

But top wall is y < radius? Actually top is y < radius, bounce.

Ball-to-paddle: check if ball.y + radius >= paddleY (paddle top?) Actually paddle is at bottom. Usually paddle Y is near bottom: paddleY = canvas.height - paddleHeight - 5. So paddle is above bottom. The ball collides with paddle if ball.y + radius >= paddleY and ball.x within paddleX range and ball.dy > 0 (moving downwards). Then reflect: dy = -ball.speedY (or absolute), compute new dx based on hit offset.

Compute offset = (ball.x - (paddleX + paddleWidth/2)) / (paddleWidth/2). Clamp offset between -1 and 1. Then angle = offset * maxPaddleAngle (e.g., Math.PI/3). Then new dx = Math.sin(angle) * speed; new dy = -Math.abs(speed * Math.cos(angle)). Or using speed magnitude.

Simplify: ball speed constant: speed = 5 maybe. Compute angle.

If we want to vary angle, we can compute new dx = speed * Math.sin(angle), new dy = -speed * Math.cos(angle). But sign ensures upward direction.

We also need to apply bounce for each ball.

Ball-to-bricks collisions: For each brick that is not broken, check if ball's rectangle overlaps brick. Compute collision direction. If overlapping, determine which side hit (top, bottom, left, right). For simplicity, we can use distance to edges: if ball's center is within brick's horizontal bounds and ball bottom is within brick top to bottom (i.e., ball.y < brick.y + brick.h && ball.y > brick.y). Then treat as top collision: reverse dy. If ball top > brick.y + brick.h and ball.y < brick.y + brick.h (i.e., bottom collision). But we can use a simple circle-rectangle collision detection.

Simplify: Use circle to rectangle: find nearest point on rectangle to ball center, compute distance. If distance < radius, collision. Determine which side was hit based on ball center relative to rectangle. Then reflect velocity accordingly: if hit from top, reverse dy; if from bottom, reverse dy; if from left/right, reverse dx.

If collision detected:

- Decrease brick.health by 1. If health becomes 0, brick is broken: set broken flag, spawn particles, maybe drop power-up (if brick.hasPowerUp). Score increase accordingly (maybe 10 for normal, 20 for tougher). Add to score.

- If health >0 (i.e., after first hit of tougher brick), set brick.currentHealth = health (maybe display cracked). So we need to track brick.health and brick.currentHealth? Actually brick.health is maxHealth? Let's define brick = {x, y, w, h, maxHealth, health, hasPowerUp, cracked?}. On hit, decrement health; if health <=0 -> break; else if health < maxHealth, show cracked. We'll set health decreasing.

If brick has power-up and it breaks (i.e., health <=0 after hit), we create a power-up object at brick's center (x, y + h/2) with type chosen randomly from two types maybe 50/50. Or we could have each power-up brick have a predetermined type; for simplicity, when brick has power-up, we randomly assign type at creation; then on break we spawn that type.

- If brick.cracked (i.e., health == 1 after first hit) we display cracked overlay but not break. Also, maybe a second hit can cause break, but we also may drop power-up? Usually only break triggers power-up. So only on break (health <=0) we drop.

- For drawing bricks: if brick.cracked (i.e., health == 1 but maxHealth == 2) we draw a "cracked" effect. We'll draw brick base color, then draw a diagonal line across it.

Drawing bricks: We'll define colors: normal: '#e74c3c' maybe; tougher: '#3498db'. For cracked, maybe lighten color or overlay.

Drawing power-up icon: for bricks that have power-up, we draw a small icon inside, e.g., a star shape using canvas path. Let's define function drawPowerUpIcon(b) that draws a small yellow star centered in brick.

When brick breaks, we need to spawn particles. We'll define function spawnParticles(x, y, count, color). We'll push new particle objects.

Particle update: each particle v: x+=vx, y+=vy, life--. Apply gravity maybe: vy += gravity (0.2). Remove if life <= 0. Draw as circles.

Power-up update: move downwards: y += vy (maybe initial -2? Actually falling downwards, positive vy). Maybe also random horizontal drift: x += vx (maybe small). If power-up y exceeds canvas height, remove. If power-up collides with paddle: check if ball's bottom? Actually power-up collides with paddle if its y + radius >= paddleY and its x within paddle bounds. Then apply effect: if type 'widePaddle', set paddleWidth = defaultWidth + widthIncrease (maybe 50) and start widePaddleTimer = now + 10 seconds. During that time, paddle width remains increased. After timer expires, revert width to default.

If type 'multiBall', we spawn extra balls. For simplicity, we can spawn 2 extra balls that start at paddle center with same dx/dy as main ball (or maybe with slight offset). Add them to balls array.

We need to track paddle width increase timer: record startTime of widePaddle. In game loop, if widePaddleActive, compute elapsed = timestamp - widePaddleStart; if elapsed >= 10000 ms, revert paddle width.

We also need to handle multiple balls: they all share same physics.

Collision detection with paddle: if ball's bottom is colliding with paddle, also check if ball's y + radius >= paddleY and ball's y - radius <= paddleY + paddleHeight (i.e., ball near paddle). Then reflect.

Now game over condition: If any ball's bottom (ball.y + radius) > canvas.height (i.e., below canvas), then decrement lives and reset ball(s). For simplicity, if any ball goes out, we consider all balls lost and reset to start: remove all balls and create a new ball at paddle. But we might want to keep other balls if there are multiple balls. But for simplicity, we can remove all balls if any one goes out. Then decrement lives; if lives <=0, show game over overlay (display restart button). Also reset game (score, bricks, etc.) if restart.

Victory: If bricks.length == 0, show victory overlay.

We need to define startGame() function that resets and starts loop.

We also need to handle user input:

- Mouse move: update paddleX = e.clientX - canvasXOffset? Since canvas is scaled and positioned, we need to convert mouse coordinates to canvas coordinate system. Use `const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top;` Then set paddleX = mx - paddleWidth/2; clamp within [0, canvas.width - paddleWidth].

- Keyboard left/right: on keydown, if key is 'ArrowLeft' or 'ArrowRight', adjust paddleX by speed (e.g., 10) and clamp.

But note: we only want to capture keyboard events after user clicks. The overlay likely blocks click events for canvas initially. But we can attach listeners globally, and for start overlay we can also listen to keydown for space or Enter to start. But the spec says "Click or press a key to start". So we can treat any keydown as start. However, the iframe only receives keyboard events after user clicks it. So we need to ensure that we only start after a click on canvas (or overlay). Possibly we need to set focus on canvas after overlay removed. We'll implement start on click of overlay (startMessage) or any keydown when overlay visible. We'll use `document.addEventListener('keydown', startGame)` but we need to ensure that the overlay is displayed and capture keydown. This is fine.

But the spec says that the iframe only receives keyboard events after the user clicks it. That suggests that we need to set up a "click to focus" behavior. So initially, we could set the canvas to not receive keyboard events until clicked. In HTML, you can use `tabindex="0"` on canvas to make it focusable and receive key events. But by default, canvas does not receive keyboard events unless focusable. So we can set canvas tabindex=0. But also we can attach listeners to document.

Simplify: We'll attach listeners to document for keydown and mouse events, but check if overlay is visible; if yes, then startGame. After start, we hide overlay and let input control paddle.

Thus startGame will be called once.

After startGame, we set `gameState = 'playing'`, `gameStarted = true`, `aiMode = true`. Also call resetGame() to initialize bricks, etc.

We'll also schedule requestAnimationFrame.

Game Over / Victory Overlay:

- When game over or victory, we hide the canvas? Or keep it but display overlay with appropriate message and restart button. The overlay initially had startMessage and restartBtn (hidden). We'll reuse the same overlay for both. We'll set startMessage text accordingly.

Implementation details:

We'll define functions:

- showGameOver() { overlay.style.display = 'flex'; startMessage.textContent = 'Game Over\nScore: ' + score; restartBtn.style.display = 'inline-block'; }

- showVictory() { similar with victory message.

- restartGame(): reset state, hide overlay (display none), show startMessage with "Click or press a key to start", hide restart button.

But we need to differentiate between start overlay and game over overlay. We'll reuse overlay but adjust content.

Implementation for overlay:

HTML:

<div id="overlay">
  <div id="startMessage">Click or press a key to start</div>
  <button id="restartBtn" style="display:none;">Restart</button>
</div>

CSS:

#overlay {
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:100%;
  background:rgba(0,0,0,0.7);
  color:#fff;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  z-index:10;
  pointer-events:auto;
}
#startMessage { font-size:2em; text-align:center; }
#restartBtn { margin-top:20px; padding:10px 20px; font-size:1.5em; }

We need to ensure the overlay is above canvas.

Now code structure:

We'll define everything inside `<script>`.

Define constants:

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
const startMessage = document.getElementById('startMessage');
const restartBtn = document.getElementById('restartBtn');

Set canvas size based on container? We'll use fixed resolution with device pixel ratio.

We can define a function resizeCanvas() that sets canvas width/height based on display size and dpr.

Because we are not handling window resize events, but we can just call it once.

Alternatively, we can set canvas width and height to window inner width/height or maintain aspect ratio.

Simplify: Use fixed 800x600 but set CSS width/height accordingly.

Let's do:

canvas.width = 800;
canvas.height = 600;

But for responsive, we can set style width and height to 100% etc. However, drawing will stay at 800x600, but container may be smaller, causing scaling. That's okay.

We'll define a scale factor for high DPI: const dpr = window.devicePixelRatio || 1; canvas.style.width = '800px'; canvas.style.height = '600px'; canvas.width = 800 * dpr; canvas.height = 600 * dpr; ctx.scale(dpr, dpr); (But then we have both style and actual pixel size; this will cause crisp edges if we set appropriate style). We'll do that.

Alternatively, we can compute based on container size: get container dimensions, but okay.

Now game objects:

Initialize gameState:

let gameState = 'MENU'; // 'MENU', 'PLAYING', 'GAMEOVER', 'VICTORY'

let gameStarted = false;
let aiMode = false; // after user input

Let paddle:

let paddle = {
  x: 0,
  y: 0,
  width: 100,
  height: 15,
  speed: 8,
  defaultWidth: 100,
  extraWidth: 50, // for power-up
  widePaddleActive: false,
  widePaddleStart: 0,
};

Set initial paddleY: canvas.height - paddle.height - 20. PaddleX start: (canvas.width - paddle.width) / 2.

Balls array:

let balls = [];

Define ball template:

let ball = {
  x: 0,
  y: 0,
  radius: 10,
  speed: 5,
  dx: 0,
  dy: 0,
};

Initialize a ball at start: position at paddleX + paddleWidth/2, y = paddleY - radius. dx = 0? Actually we need to give initial velocity upward, maybe with some random angle: dx = (Math.random() - 0.5) * 2; dy = -5.

Define function resetBall(ball) or create new ball.

Bricks:

Let bricks = [];

Define brick template:

let brick = {
  x: 0,
  y: 0,
  width: 70,
  height: 20,
  maxHealth: 1,
  health: 1,
  hasPowerUp: false,
  powerUpType: null, // 'widePaddle' or 'multiBall'
  cracked: false, // indicates cracked after first hit (if health==1 but maxHealth==2)
};

Generate bricks: loop for row in 0..5 (6 rows). For each row, we can set y position: top margin maybe 30 + row * (brick.height + 5). For each column in 0..7 (8 columns), set x = 20 + col * (brick.width + 5). For each brick, assign health and power-up:

let health = Math.random() < 0.3 ? 2 : 1; // 30% tougher

let hasPowerUp = false;
let powerUpType = null;
if (health === 1 && Math.random() < 0.2) {
  hasPowerUp = true;
  powerUpType = Math.random() < 0.5 ? 'widePaddle' : 'multiBall';
} else if (health === 2 && Math.random() < 0.1) {
  hasPowerUp = true;
  powerUpType = Math.random() < 0.5 ? 'widePaddle' : 'multiBall';
}
bricks.push({... brick template with values ...});

We'll also define a color per brick: normal color '#e74c3c' (red), tougher '#3498db' (blue).

Now particles:

particles = [];

Define particle template:

let particle = {
  x: 0, y: 0,
  vx: 0, vy: 0,
  life: 30,
  maxLife: 30,
  radius: 2,
  color: '#fff',
};

When brick breaks (health <=0), spawn e.g., 8 particles around center with random directions and speed ~2-4.

Spawn function:

function spawnParticles(x, y, color, count = 8) {
  for (let i = 0; i < count; i++) {
    let angle = (Math.PI * 2 * i) / count + (Math.random() - 0.5) * 0.5;
    let speed = 2 + Math.random() * 3;
    particles.push({
      x, y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 20,
      maxLife: 20,
      radius: 2 + Math.random() * 2,
      color,
    });
  }
}

Power-ups:

powerUps = [];

Define power-up template:

let powerUp = {
  x: 0, y: 0,
  radius: 6,
  vy: 2,
  type: 'widePaddle' or 'multiBall',
  active: true,
};

When brick with power-up breaks, we push a powerUp at brick's center (or above?) Usually power-up drops from brick. So we can set y = brick.y + brick.height/2.

Now Paddle movement:

function updatePaddleAI() {
  // if aiMode, move paddle towards ball's x position
  if (!aiMode) return; // manual control will override later
  let targetX = balls[0]?.x - paddle.width / 2;
  // smooth movement: move towards target
  if (targetX < paddle.x) {
    paddle.x = Math.max(paddle.x - paddle.speed, targetX);
  } else if (targetX > paddle.x) {
    paddle.x = Math.min(paddle.x + paddle.speed, targetX);
  }
  // clamp overall
  paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, paddle.x));
}

Manual control via mouse/keyboard:

function setPaddleX(x) {
  paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, x));
}

Mouse move listener:

canvas.addEventListener('mousemove', (e) => {
  if (!gameStarted) return;
  const rect = canvas.getBoundingClientRect();
  const mx = e.clientX - rect.left;
  // adjust for DPI scaling if needed: we have scaled ctx, but canvas coordinates are actual pixel.
  // Since we set canvas width/height and ctx.scale(dpr,dpr), coordinates are actual pixels.
  // So mx is screen pixel, but rect.left already accounts for scaling.
  setPaddleX(mx - paddle.width / 2);
  aiMode = false; // user took control
});

Keyboard left/right:

document.addEventListener('keydown', (e) => {
  if (!gameStarted) {
    // start game on any key
    startGame();
    return;
  }
  if (e.key === 'ArrowLeft') {
    setPaddleX(paddle.x - paddle.speed);
    aiMode = false;
  } else if (e.key === 'ArrowRight') {
    setPaddleX(paddle.x + paddle.speed);
    aiMode = false;
  }
});

Click to start (overlay):

overlay.addEventListener('click', startGame);
Also keydown when in menu triggers start.

Now startGame implementation:

function startGame() {
  // if overlay not yet shown, hide startMessage and show restart only after game over
  // Remove pointer events for overlay? Actually we want overlay to be visible until start.
  // We'll hide startMessage and set overlay display none after start.
  // But need to keep overlay for game over/victory later.
  // We'll set gameState = 'PLAYING' if not started.
  if (!gameStarted) {
    gameStarted = true;
    // Show overlay with start message? Actually we want to hide it.
    // We can hide startMessage text and restartBtn (which is hidden). But we need to keep overlay for later usage.
    // We'll set overlay.style.display = 'none'; or style to hide.
    // Actually we want the overlay to be hidden while playing. So set overlay.style.display = 'none';
    // Then call resetGame().
    overlay.style.display = 'none';
    resetGame();
    // Set aiMode true for demo
    aiMode = true;
    // start loop
    requestAnimationFrame(gameLoop);
  }
}

But we also need to ensure restartBtn click triggers restartGame, which resets game and shows start overlay again? Or shows start message? For restart after game over/victory, we probably want to reset and hide overlay (the overlay for game over) and show start overlay? But spec says "Game over and victory screens with a restart option." So after game over, we show overlay with game over message and restart button; clicking restart resets game and starts playing (maybe also hides overlay). So restartBtn click should call restartGame() which resets all and hides overlay.

Thus we need to define restartGame() that resets game state and hides overlay, shows startMessage as "Click or press a key to start"? Actually after restart, we should be back to playing. So we need to show start overlay? But we just want to start playing automatically after restart, i.e., start the game. So we can just reset game and start playing with gameStarted = true, aiMode = true. Also hide overlay (set display none). But if we hide overlay, we lose the game over/victory overlay. Actually after game over, overlay is shown with game over message and restart button. So we need to differentiate overlay content based on state. The overlay element is used for both start and game over/victory. We can have two sets of messages inside: startMessage and restartBtn. So we can show startMessage for start and game over/victory messages for those states. But the overlay will contain both; we can hide or show accordingly.

Simplify: Use overlay for all UI states. We'll have startMessage for start and game over/victory messages. The restartBtn is only visible when game over or victory. So:

- In start state (gameState == 'MENU' and !gameStarted), overlay displayed with startMessage "Click or press a key to start". restartBtn hidden.

- In game over/victory state, overlay displayed with startMessage "Game Over\nScore: XXX" (or "Victory! Score: XXX") and restartBtn visible.

- In playing state, overlay hidden.

Thus we need to adjust overlay content based on gameState.

Implement:

function showGameOver() {
  gameState = 'GAMEOVER';
  startMessage.textContent = `Game Over\nScore: ${score}`;
  restartBtn.style.display = 'inline-block';
  overlay.style.display = 'flex';
}
function showVictory() {
  gameState = 'VICTORY';
  startMessage.textContent = `Victory!\nScore: ${score}`;
  restartBtn.style.display = 'inline-block';
  overlay.style.display = 'flex';
}
function restartGame() {
  // Reset all state to initial
  gameState = 'MENU';
  gameStarted = false;
  aiMode = false;
  score = 0;
  lives = 3;
  // hide overlay (it will be shown by startGame on interaction)
  overlay.style.display = 'none';
  restartBtn.style.display = 'none';
  // Reset everything else (bricks, balls, particles, powerUps)
  resetGame();
}

But we need to ensure restartBtn click triggers restartGame.

So add:

restartBtn.addEventListener('click', restartGame);

Now resetGame() implementation:

function resetGame() {
  // Reset bricks
  bricks = [];
  generateBricks();
  // Reset balls
  balls = [];
  // Create one ball
  createBall();
  // Reset paddle
  paddle.x = (canvas.width - paddle.width) / 2;
  paddle.y = canvas.height - paddle.height - 20;
  paddle.widePaddleActive = false;
  // Reset particles and power-ups
  particles = [];
  powerUps = [];
  // Reset gameState? It will be set by caller.
}

// createBall

function createBall() {
  let ball = {
    x: paddle.x + paddle.width / 2,
    y: paddle.y - ball.radius,
    radius: 10,
    speed: 5,
    dx: (Math.random() - 0.5) * 4,
    dy: -5,
  };
  balls.push(ball);
}

// generateBricks

function generateBricks() {
  const rows = 6;
  const cols = 8;
  const brickWidth = 70;
  const brickHeight = 20;
  const gap = 5;
  const topOffset = 30;
  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      const x = 20 + col * (brickWidth + gap);
      const y = topOffset + row * (brickHeight + gap);
      let maxHealth = Math.random() < 0.3 ? 2 : 1;
      let hasPowerUp = false;
      let powerUpType = null;
      if (maxHealth === 1 && Math.random() < 0.2) {
        hasPowerUp = true;
        powerUpType = Math.random() < 0.5 ? 'widePaddle' : 'multiBall';
      } else if (maxHealth === 2 && Math.random() < 0.1) {
        hasPowerUp = true;
        powerUpType = Math.random() < 0.5 ? 'widePaddle' : 'multiBall';
      }
      bricks.push({
        x, y,
        width: brickWidth,
        height: brickHeight,
        maxHealth,
        health: maxHealth,
        hasPowerUp,
        powerUpType,
        color: maxHealth === 2 ? '#3498db' : '#e74c3c',
      });
    }
  }
}

Now update logic:

In gameLoop, after updating ball positions, we will call functions.

Define updateBalls():

function updateBalls() {
  for (let i = balls.length - 1; i >= 0; i--) {
    let b = balls[i];
    b.x += b.dx;
    b.y += b.dy;
    // apply gravity? Not needed.
  }
}

Define updateParticles():

function updateParticles() {
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i];
    p.x += p.vx;
    p.y += p.vy;
    p.vy += 0.2; // gravity
    p.life--;
    if (p.life <= 0) {
      particles.splice(i, 1);
    }
  }
}

Define updatePowerUps():

function updatePowerUps() {
  for (let i = powerUps.length - 1; i >= 0; i--) {
    let pu = powerUps[i];
    pu.y += pu.vy;
    // optional horizontal drift
    pu.x += pu.vx ?? 0;
    // if out of bounds, remove
    if (pu.y - pu.radius > canvas.height) {
      powerUps.splice(i, 1);
    }
  }
}

Define checkCollisions():

function checkCollisions() {
  // loop over balls
  for (let i = balls.length - 1; i >= 0; i--) {
    let b = balls[i];
    // Walls
    if (b.x - b.radius < 0 || b.x + b.radius > canvas.width) {
      b.dx = -b.dx;
    }
    if (b.y - b.radius < 0) {
      b.dy = -b.dy;
    }
    // Paddle
    if (b.y + b.radius >= paddle.y && b.y - b.radius < paddle.y && b.dx > 0) {
      // Actually need to check horizontal overlap
      if (b.x >= paddle.x && b.x <= paddle.x + paddle.width) {
        // compute angle based on hit offset
        let offset = (b.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2);
        offset = Math.max(-1, Math.min(1, offset));
        let angle = offset * Math.PI / 3; // max angle 60 degrees
        let speed = Math.hypot(b.dx, b.dy);
        b.dx = Math.sin(angle) * speed;
        b.dy = -Math.cos(angle) * speed;
        // ensure ball moves upward
      }
    }

    // Bricks collisions
    for (let j = bricks.length - 1; j >= 0; j--) {
      let br = bricks[j];
      if (br.health <= 0) continue;
      // Circle-rectangle collision detection
      let closestX = Math.max(br.x, Math.min(b.x, br.x + br.width));
      let closestY = Math.max(br.y, Math.min(b.y, br.y + br.height));
      let dx = b.x - closestX;
      let dy = b.y - closestY;
      let distSq = dx*dx + dy*dy;
      if (distSq < b.radius * b.radius) {
        // collision
        // Determine which side
        let fromLeft = b.x < br.x;
        let fromRight = b.x > br.x + br.width;
        let fromTop = b.y < br.y;
        let fromBottom = b.y > br.y + br.height;

        // Reflect velocity based on which side
        if (fromLeft || fromRight) {
          b.dx = -b.dx;
        }
        if (fromTop || fromBottom) {
          b.dy = -b.dy;
        }

        // Handle brick damage
        br.health--;
        if (br.health <= 0) {
          // Brick is broken
          // Score
          score += br.maxHealth === 2 ? 20 : 10;
          // Particles
          spawnParticles(br.x + br.width/2, br.y + br.height/2, br.color, 8);
          // Power-up drop
          if (br.hasPowerUp) {
            powerUps.push({
              x: br.x + br.width/2,
              y: br.y + br.height,
              radius: 6,
              vy: 2,
              vx: (Math.random() - 0.5) * 1,
              type: br.powerUpType,
            });
          }
          // Remove brick
          bricks.splice(j, 1);
        } else if (br.health < br.maxHealth) {
          // Brick is cracked (first hit for double health)
          // Visual cracked overlay: maybe just mark as cracked
          br.cracked = true;
        }
      }
    }

    // Ball out of bottom
    if (b.y + b.radius > canvas.height) {
      // ball lost
      lives--;
      // Remove ball
      balls.splice(i, 1);
      if (balls.length === 0) {
        // All balls removed
        // create new ball if lives>0
        if (lives > 0) {
          createBall();
        }
      } else {
        // If multiple balls, we may still have balls left
        // but maybe reset all? We'll just remove the lost ball.
        // create a new ball if none left.
        if (balls.length === 0) {
          createBall();
        }
      }
      // If lives == 0, game over
      if (lives <= 0) {
        showGameOver();
      }
    }
  }

  // After processing bricks, check victory condition
  if (bricks.length === 0) {
    showVictory();
  }
}

But we need to adjust ball collision with paddle more precisely: current check is simplistic and may not detect collisions correctly. We need a proper detection: ball's bottom intersects paddle top.

Define paddle top Y = paddle.y (since paddle y is top). Actually earlier we set paddle.y = canvas.height - paddle.height - margin. So paddle top Y is that coordinate.

Ball collides with paddle if ball.y + radius >= paddle.y && ball.y - radius <= paddle.y + paddle.height (i.e., ball near paddle). And horizontal overlap: ball.x - radius <= paddle.x + paddle.width && ball.x + radius >= paddle.x.

Then reflect.

Also we need to handle power-up collisions with paddle: similar check but for power-up objects: if pu.y + pu.radius >= paddle.y && pu.x between paddle.x and paddle.x + paddle.width, then apply effect.

Now apply power-up effect:

function applyPowerUp(powerUp) {
  if (powerUp.type === 'widePaddle') {
    paddle.widePaddleActive = true;
    paddle.widePaddleStart = performance.now();
    // increase width
    paddle.width = paddle.defaultWidth + paddle.extraWidth;
  } else if (powerUp.type === 'multiBall') {
    // spawn extra balls (e.g., 2 extra)
    for (let i = 0; i < 2; i++) {
      let b = {
        x: paddle.x + paddle.width / 2,
        y: paddle.y - b.radius,
        radius: 10,
        speed: 5,
        dx: (Math.random() - 0.5) * 4,
        dy: -5,
      };
      balls.push(b);
    }
  }
  // Remove power-up after catch
}

But we need to handle wide paddle timer: in gameLoop, after each frame, if paddle.widePaddleActive, compute elapsed = timestamp - paddle.widePaddleStart; if elapsed >= 10000 ms, revert width.

So we need to capture timestamp in gameLoop. The gameLoop receives timestamp parameter (from requestAnimationFrame). We'll store a global variable `lastFrameTime` or use timestamp directly.

Now draw function:

function draw() {
  // Clear canvas
  ctx.fillStyle = '#000';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // Draw bricks
  for (let br of bricks) {
    // Base brick color
    ctx.fillStyle = br.color;
    ctx.fillRect(br.x, br.y, br.width, br.height);
    // If cracked (first hit for double health)
    if (br.cracked) {
      ctx.strokeStyle = '#555';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(br.x, br.y);
      ctx.lineTo(br.x + br.width, br.y + br.height);
      ctx.moveTo(br.x + br.width, br.y);
      ctx.lineTo(br.x, br.y + br.height);
      ctx.stroke();
    }
    // Draw power-up icon if any
    if (br.hasPowerUp) {
      drawPowerUpIcon(br.x + br.width/2, br.y + br.height/2, br.powerUpType);
    }
  }

  // Draw particles
  for (let p of particles) {
    ctx.globalAlpha = p.life / p.maxLife;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.globalAlpha = 1;
  }

  // Draw power-ups
  for (let pu of powerUps) {
    ctx.fillStyle = pu.type === 'widePaddle' ? '#0f0' : '#ff0';
    ctx.beginPath();
    ctx.arc(pu.x, pu.y, pu.radius, 0, Math.PI * 2);
    ctx.fill();
    // optional text icon
  }

  // Draw paddle
  ctx.fillStyle = '#fff';
  ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);

  // Draw balls
  for (let b of balls) {
    ctx.fillStyle = '#0ff';
    ctx.beginPath();
    ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
    ctx.fill();
  }

  // Draw UI: score, lives
  ctx.fillStyle = '#fff';
  ctx.font = '20px Arial';
  ctx.fillText(`Score: ${score}`, 20, 30);
  ctx.fillText(`Lives: ${lives}`, 20, 60);
}

drawPowerUpIcon:

function drawPowerUpIcon(cx, cy, type) {
  ctx.save();
  ctx.beginPath();
  if (type === 'widePaddle') {
    // draw a small rectangle
    ctx.fillStyle = '#ff0';
    ctx.fillRect(cx - 5, cy - 5, 10, 10);
  } else if (type === 'multiBall') {
    // draw a star shape
    ctx.fillStyle = '#f0f';
    ctx.moveTo(cx, cy - 6);
    ctx.lineTo(cx, cy + 6);
    ctx.moveTo(cx - 6, cy);
    ctx.lineTo(cx + 6, cy);
    ctx.fill();
  }
  ctx.restore();
}

Now handle power-up collision with paddle:

In gameLoop after updating powerUps, we can check:

for each powerUp:
if powerUp.y + powerUp.radius >= paddle.y && powerUp.x >= paddle.x && powerUp.x <= paddle.x + paddle.width:
applyPowerUp(powerUp);
remove powerUp.

Now paddle AI update:

function updatePaddleAI() {
  if (!aiMode || balls.length === 0) return;
  // track ball position
  let b = balls[ estimate? Actually we can target the first ball.
  let targetX = balls[0].x - paddle.width / 2;
  if (targetX < paddle.x) {
    paddle.x = Math.max(paddle.x - paddle.speed, targetX);
  } else if (targetX > paddle.x) {
    paddle.x = Math.min(paddle.x + paddle.speed, targetX);
  }
  // clamp
  paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, paddle.x));
}

We should call this in gameLoop after movement updates.

Now handle widePaddle timer:

In gameLoop, after draw perhaps before next frame, we check:

if (paddle.widePaddleActive) {
  let elapsed = performance.now() - paddle.widePaddleStart;
  if (elapsed >= 10000) {
    paddle.widePaddleActive = false;
    paddle.width = paddle.defaultWidth;
  }
}

Now we need to ensure that paddle width doesn't exceed canvas width. In start, default width maybe 100, extraWidth 50, total 150. That's fine.

Now we need to ensure paddle AI doesn't conflict with manual control: if user moves paddle, we set aiMode = false. So we need to set aiMode = false on mouse move or keydown. Already we set aiMode = false in those event listeners.

Now we need to ensure that startGame is called on click of overlay and on any keydown when overlay visible. But we need to differentiate between keydown for paddle control vs start. So we need a flag `menuActive` or check gameState.

We can define a function `handleStartOrInput(e)`. In start state (gameState == 'MENU' and !gameStarted), keydown triggers startGame. In playing state, keydown moves paddle.

We also need to handle mouse move for paddle only when playing.

Thus:

document.addEventListener('keydown', (e) => {
  if (gameState === 'MENU' && !gameStarted) {
    // start
    startGame();
  } else if (gameState === 'PLAYING') {
    // arrow keys
    if (e.key === 'ArrowLeft') {
      setPaddleX(paddle.x - paddle.speed);
      aiMode = false;
    } else if (e.key === 'ArrowRight') {
      setPaddleX(paddle.x + paddle.speed);
      aiMode = false;
    }
  }
});

Similarly, for mouse move:

canvas.addEventListener('mousemove', (e) => {
  if (gameState !== 'PLAYING' || !gameStarted) return;
  const rect = canvas.getBoundingClientRect();
  const mx = e.clientX - rect.left;
  setPaddleX(mx - paddle.width / 2);
  aiMode = false;
});

Now we also need to handle start overlay click: overlay.addEventListener('click', startGame). But we must differentiate: if overlay is visible and user clicks overlay (which covers canvas), it triggers startGame. But overlay covers everything. But we have overlay with pointer-events auto. So clicking overlay triggers startGame. That's fine.

Potential issues: startGame may be called multiple times if user clicks overlay multiple times. We can guard using a flag.

Now define startGame:

function startGame(e) {
  // Only start if not already started and overlay is currently shown
  if (gameState !== 'MENU' || gameStarted) return;

  // Hide overlay
  overlay.style.display = 'none';
  // Reset game
  resetGame();
  gameStarted = true;
  gameState = 'PLAYING';
  // Start loop
  requestAnimationFrame(gameLoop);
}

But we also need to show overlay when game over/victory.

Now define showGameOver and showVictory as described.

Also define restartGame to reset state.

But also, after startGame is called, we need to show the overlay? Actually after game over/victory, we show overlay with messages. So we need to hide overlay when playing.

Now we need to ensure that the canvas receives focus for keyboard events? In some browsers, canvas does not receive keyboard events unless focusable. We can add tabindex="0" to canvas. But the overlay is on top, and we use global keydown listeners for start and paddle control. So that's fine.

Now we must ensure that the game runs and does not produce console errors. Let's go through code to check syntax errors and logic.

Let's write out the full HTML file with all the code.

We must include everything inside <script> tags. Ensure we have proper indentation.

Potential missing imports: none.

One more detail: We must ensure that we handle devicePixelRatio scaling correctly. For simplicity, we might ignore DPI scaling and just use canvas.width = 800; canvas.height = 600; and set CSS to match.

But for crisp rendering, we might want to set DPR.

We'll implement a resize function that runs on load and maybe on window resize.

function resizeCanvas() {
  const rect = canvas.getBoundingClientRect();
  const dpr = window.devicePixelRatio || 1;
  canvas.width = rect.width * dpr;
  canvas.height = rect.height * dpr;
  ctx.setTransform(1, 0, 0, 1, 0, 0); // reset
  ctx.scale(dpr, dpr);
  // But we need to keep scale factor for drawing.
}

But we also need to adjust paddle positions accordingly? Since we use fixed coordinates, but scaling will map them correctly.

Simplify: Use CSS to set canvas size based on viewport, but we will still use canvas.width = 800; canvas.height = 600; and CSS style width/height: 100%; The DPR scaling might cause crispness issues.

Given the constraints, the demo does not need ultra-crisp. So we can skip DPR scaling for simplicity.

Thus we can set canvas.width = 800; canvas.height = 600; and CSS:

#gameCanvas {
  width: 100%;
  height: auto;
  max-height: 100vh;
  image-rendering: pixelated;
}

The container ensures aspect ratio.

Now we need to define container to maintain aspect ratio: we can use padding-bottom 56.25% for 16:9.

We'll include CSS in <style>.

Let's write final HTML.

But we must ensure everything is in one code block as per instructions: output exactly one fenced code block with the HTML file. We'll include all code inside <html> tags.

We'll ensure no stray console errors: use correct syntax. Check for any missing variables like `b.radius` used but not defined? We defined ball objects with radius property. In createBall we set `b.radius`. But in spawn of extra balls for multiBall, we used `b.radius` incorrectly. We'll fix.

Also need to define `performance.now` for timers: use `Date.now()` or `performance.now()`. We'll use `performance.now()`.

Now let's write the final code.

We'll be careful with variable scopes and functions.

Let's assemble.

One more consideration: the AI mode: we set `aiMode = true` initially. But after startGame, we set aiMode = true. Then in gameLoop, after updating paddle positions, we call updatePaddleAI(). But updatePaddleAI will only run if aiMode is true. However, we also need to call updatePaddleAI after movement? Actually we need to call it each frame to move paddle towards ball.

But we also need to ensure that manual control (mouse/keyboard) will set aiMode false, and then updatePaddleAI will skip.

Thus we will call updatePaddleAI after each loop.

Now, we need to ensure `updateP(solve` is called.

Now check for any references to undefined functions: drawPowerUpIcon defined; spawnParticles defined; createBall defined; resetGame defined; generateBricks defined; updateParticles, updatePowerUps, checkCollisions, draw.

We also need to ensure that we referenced variable `paddle.defaultWidth` etc.

We need to define `paddle.defaultWidth` as initial width. We'll set in paddle init.

Also, when widePaddleActive is true, we need to revert after 10 seconds. We'll use performance.now() to track start.

Now, one more thing: In checkCollisions for paddle, we need to calculate offset correctly. We also need to compute speed to preserve magnitude. But for simplicity, we can just set ball.dy = -ball.speed and ball.dx = offset * ball.speed * some factor.

Simplify: Compute angle based on offset, and set velocity magnitude to ball.speed.

Implementation:

let speed = ball.speed;
let angle = offset * Math.PI / 3; // -60° to +60°
ball.dx = Math.sin(angle) * speed;
ball.dy = -Math.cos(angle) * speed;

Make sure ball speed is set to some value.

Now we also need to define `ball.speed` in ball objects; we set speed constant.

Now the code is fairly complete.

Let's write out the final HTML.