Models 44

Mistral Small 4

by MistralReasoningClean
Open in new window

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

How it went

Total time
1 min 2 s
Cost
$0.0087
Output tokens
13,200
Spent thinking
7,273 tokens
Speed
213.1 tok/s
Console errors
0
First token after
542 ms
First visible text after
36.7 s
Prompt tokens
532
Code size
20.9 KB
Finish reason
stop
Served by
Mistral
Model id
mistralai/mistral-small-2603
Reasoning sent
{"effort":"high"}
Max output
209,715 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 · 761 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Breakout Game</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: 'Arial', sans-serif;
      background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
      color: white;
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      overflow: hidden;
      user-select: none;
    }

    #gameCanvas {
      background: rgba(0, 0, 0, 0.3);
      border-radius: 8px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
      display: block;
    }

    .game-container {
      position: relative;
    }

    .overlay {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      pointer-events: none;
    }

    .menu-text, .game-over-text, .victory-text {
      background: rgba(0, 0, 0, 0.8);
      padding: 20px 40px;
      border-radius: 10px;
      text-align: center;
      pointer-events: none;
      margin-bottom: 20px;
    }

    .menu-text h1, .game-over-text h1, .victory-text h1 {
      font-size: 48px;
      margin-bottom: 20px;
      text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
    }

    .menu-text p, .game-over-text p, .victory-text p {
      font-size: 20px;
      margin-bottom: 10px;
    }

    .controls-text {
      font-size: 18px;
      margin-top: 20px;
      opacity: 0.8;
    }

    @media (max-width: 768px) {
      .menu-text h1, .game-over-text h1, .victory-text h1 {
        font-size: 32px;
      }

      .menu-text p, .game-over-text p, .victory-text p {
        font-size: 16px;
      }
    }
  </style>
</head>
<body>
  <div class="game-container">
    <canvas id="gameCanvas"></canvas>
    <div class="overlay">
      <div class="menu-text" id="menuText">
        <h1>BREAKOUT</h1>
        <p>Click or press any key to start</p>
        <p class="controls-text">Use mouse or arrow keys to move paddle</p>
        <p class="controls-text">Catch power-ups (W = wide paddle, M = multi-ball)</p>
      </div>
      <div class="game-over-text" id="gameOverText" style="display: none;">
        <h1>GAME OVER</h1>
        <p id="finalScore">Final Score: 0</p>
        <p class="controls-text">Click or press any key to restart</p>
      </div>
      <div class="victory-text" id="victoryText" style="display: none;">
        <h1>VICTORY!</h1>
        <p id="victoryScore">Final Score: 0</p>
        <p class="controls-text">Click or press any key to play again</p>
      </div>
    </div>
  </div>

  <script>
    // Game constants
    const COLORS = {
      paddle: '#3498db',
      ball: '#e74c3c',
      brickNormal: '#2ecc71',
      brickTough: '#f39c12',
      brickPower: '#9b59b6',
      powerUpWide: '#3498db',
      powerUpMulti: '#e67e22',
      particle: '#ffffff'
    };

    // Game state
    const gameStates = {
      MENU: 'MENU',
      PLAYING: 'PLAYING',
      GAME_OVER: 'GAME_OVER',
      VICTORY: 'VICTORY'
    };

    let canvas, ctx;
    let gameState = gameStates.MENU;
    let score = 0;
    let lives = 3;
    let level = 1;
    let bricks = [];
    let balls = [];
    let powerUps = [];
    let particles = [];
    let paddle;
    let mouseX = 0;
    let keys = {};
    let powerUpActive = null;
    let powerUpTimer = 0;
    let lastTime = 0;
    let demoBall = null;

    // Initialize game
    function init() {
      canvas = document.getElementById('gameCanvas');
      ctx = canvas.getContext('2d');

      // Set canvas size
      resizeCanvas();

      // Event listeners
      window.addEventListener('resize', resizeCanvas);
      canvas.addEventListener('mousemove', handleMouseMove);
      canvas.addEventListener('click', startGame);
      window.addEventListener('keydown', handleKeyDown);
      window.addEventListener('keyup', handleKeyUp);

      // Create paddle
      paddle = new Paddle();

      // Create initial level
      createLevel();

      // Start game loop
      requestAnimationFrame(gameLoop);
    }

    // Main game loop
    function gameLoop(timestamp) {
      if (!lastTime) lastTime = timestamp;
      const deltaTime = (timestamp - lastTime) / 1000;
      lastTime = timestamp;

      // Clear canvas
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // Update game state
      update(deltaTime);

      // Render
      render();

      // Continue loop
      requestAnimationFrame(gameLoop);
    }

    // Update game state
    function update(deltaTime) {
      if (gameState === gameStates.PLAYING) {
        // Update paddle (player controlled)
        paddle.update(false);

        // Update balls
        for (let i = balls.length - 1; i >= 0; i--) {
          balls[i].update(deltaTime);
          checkWallCollisions(balls[i]);
          checkPaddleCollision(balls[i]);
          checkBrickCollisions(balls[i]);
        }

        // Remove inactive balls
        balls = balls.filter(ball => ball.active);

        // If no balls left, lose a life
        if (balls.length === 0) {
          lives--;
          if (lives <= 0) {
            gameState = gameStates.GAME_OVER;
            document.getElementById('finalScore').textContent = `Final Score: ${score}`;
          } else {
            // Reset paddle and add new ball
            paddle.reset();
            balls.push(new Ball());
          }
        }

        // Update power-ups
        for (let i = powerUps.length - 1; i >= 0; i--) {
          powerUps[i].update(deltaTime);
          // Check if caught by paddle
          if (powerUps[i].y + powerUps[i].radius >= paddle.y &&
              powerUps[i].x > paddle.x &&
              powerUps[i].x < paddle.x + paddle.width) {
            activatePowerUp(powerUps[i].type);
            powerUps.splice(i, 1);
          }
          // Remove if off screen
          if (powerUps[i].y > canvas.height) {
            powerUps.splice(i, 1);
          }
        }

        // Update particles
        for (let i = particles.length - 1; i >= 0; i--) {
          particles[i].update(deltaTime);
          if (particles[i].alpha <= 0) {
            particles.splice(i, 1);
          }
        }

        // Check if all bricks are destroyed
        if (bricks.length === 0) {
          // Level complete
          if (level < 3) {
            level++;
            createLevel();
          } else {
            gameState = gameStates.VICTORY;
            document.getElementById('victoryScore').textContent = `Final Score: ${score}`;
          }
        }

        // Update power-up timer
        if (powerUpActive) {
          powerUpTimer -= deltaTime;
          if (powerUpTimer <= 0) {
            deactivatePowerUp();
          }
        }
      } else if (gameState === gameStates.MENU) {
        // AI demo mode - move paddle automatically
        paddle.update(true);

        // Keep one ball in demo
        if (!demoBall || !demoBall.active) {
          demoBall = new Ball();
        }
        demoBall.update(deltaTime);
        checkWallCollisions(demoBall);
        checkPaddleCollision(demoBall);
        checkBrickCollisions(demoBall);

        // Remove dead balls in demo
        if (demoBall && !demoBall.active) {
          demoBall = null;
        }
      }
    }

    // Render game
    function render() {
      // Draw background
      drawBackground();

      // Draw bricks
      bricks.forEach(brick => brick.draw());

      // Draw balls
      balls.forEach(ball => ball.draw());

      // Draw paddle
      paddle.draw();

      // Draw power-ups
      powerUps.forEach(powerUp => powerUp.draw());

      // Draw particles
      particles.forEach(particle => particle.draw());

      // Draw UI
      drawUI();

      // Draw game state overlays
      if (gameState === gameStates.MENU) {
        document.getElementById('menuText').style.display = 'flex';
        document.getElementById('gameOverText').style.display = 'none';
        document.getElementById('victoryText').style.display = 'none';
      } else if (gameState === gameStates.GAME_OVER) {
        document.getElementById('menuText').style.display = 'none';
        document.getElementById('gameOverText').style.display = 'flex';
        document.getElementById('victoryText').style.display = 'none';
      } else if (gameState === gameStates.VICTORY) {
        document.getElementById('menuText').style.display = 'none';
        document.getElementById('gameOverText').style.display = 'none';
        document.getElementById('victoryText').style.display = 'flex';
      }
    }

    // Paddle class
    class Paddle {
      constructor() {
        this.width = 100;
        this.height = 15;
        this.x = (canvas.width - this.width) / 2;
        this.y = canvas.height - this.height - 20;
        this.speed = 8;
        this.color = COLORS.paddle;
        this.dx = 0;
        this.originalWidth = this.width;
      }

      update(aiControlled) {
        if (aiControlled) {
          // Simple AI: follow the ball
          if (demoBall) {
            const targetX = demoBall.x;
            if (targetX < this.x + this.width/2 - 20) {
              this.dx = -this.speed;
            } else if (targetX > this.x + this.width/2 + 20) {
              this.dx = this.speed;
            } else {
              this.dx = 0;
            }
          }
        } else {
          // Keyboard control
          if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
            this.dx = -this.speed;
          } else if (keys['ArrowRight'] || keys['d'] || keys['D']) {
            this.dx = this.speed;
          } else {
            this.dx = 0;
          }
        }

        // Move paddle
        this.x += this.dx;

        // Boundary check
        if (this.x < 0) this.x = 0;
        if (this.x + this.width > canvas.width) this.x = canvas.width - this.width;
      }

      draw() {
        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.roundRect(this.x, this.y, this.width, this.height, 5);
        ctx.fill();

        // Draw center indicator
        ctx.fillStyle = 'rgba(255,255,255,0.3)';
        ctx.fillRect(this.x + this.width/2 - 2, this.y - 5, 4, 10);
      }

      reset() {
        this.width = this.originalWidth;
        this.x = (canvas.width - this.width) / 2;
      }
    }

    // Ball class
    class Ball {
      constructor() {
        this.radius = 8;
        this.reset();
      }

      reset() {
        this.x = canvas.width / 2;
        this.y = canvas.height / 2;
        this.dx = 4 * (Math.random() > 0.5 ? 1 : -1);
        this.dy = -4;
        this.active = true;
      }

      update(deltaTime) {
        this.x += this.dx;
        this.y += this.dy;
      }

      draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = COLORS.ball;
        ctx.fill();
        ctx.closePath();
      }
    }

    // Brick class
    class Brick {
      constructor(x, y, type) {
        this.x = x;
        this.y = y;
        this.width = 75;
        this.height = 25;
        this.type = type; // 'normal', 'tough', 'power'
        this.hits = type === 'tough' ? 2 : 1;
        this.cracked = false;
        this.color = type === 'normal' ? COLORS.brickNormal :
                     type === 'tough' ? COLORS.brickTough : COLORS.brickPower;
        this.active = true;
      }

      draw() {
        if (!this.active) return;

        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.roundRect(this.x, this.y, this.width, this.height, 3);
        ctx.fill();

        // Draw crack for tough bricks that have been hit once
        if (this.type === 'tough' && this.cracked) {
          ctx.strokeStyle = 'rgba(0,0,0,0.3)';
          ctx.lineWidth = 2;
          ctx.beginPath();
          ctx.moveTo(this.x + 5, this.y + 5);
          ctx.lineTo(this.x + this.width - 5, this.y + this.height - 5);
          ctx.stroke();

          ctx.beginPath();
          ctx.moveTo(this.x + this.width - 5, this.y + 5);
          ctx.lineTo(this.x + 5, this.y + this.height - 5);
          ctx.stroke();
        }
      }

      hit() {
        this.hits--;
        if (this.hits <= 0) {
          this.active = false;
          // Create particles
          createParticles(this.x + this.width/2, this.y + this.height/2, this.color);
          // Chance to drop power-up
          if (this.type === 'power' || Math.random() < 0.2) {
            powerUps.push(new PowerUp(this.x + this.width/2, this.y));
          }
          return true;
        } else if (this.type === 'tough') {
          this.cracked = true;
        }
        return false;
      }
    }

    // PowerUp class
    class PowerUp {
      constructor(x, y) {
        this.x = x;
        this.y = y;
        this.radius = 10;
        this.type = Math.random() < 0.5 ? 'wide' : 'multi'; // 50% chance for each
        this.speed = 3;
        this.color = this.type === 'wide' ? COLORS.powerUpWide : COLORS.powerUpMulti;
        this.active = true;
      }

      update(deltaTime) {
        this.y += this.speed;
      }

      draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.closePath();

        // Draw icon
        ctx.fillStyle = 'white';
        ctx.font = 'bold 12px Arial';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        if (this.type === 'wide') {
          ctx.fillText('W', this.x, this.y);
        } else {
          ctx.fillText('M', this.x, this.y);
        }
      }
    }

    // Particle class
    class Particle {
      constructor(x, y, color) {
        this.x = x;
        this.y = y;
        this.radius = Math.random() * 3 + 1;
        this.dx = (Math.random() - 0.5) * 4;
        this.dy = (Math.random() - 0.5) * 4;
        this.alpha = 1;
        this.color = color;
        this.decay = Math.random() * 0.02 + 0.01;
      }

      update(deltaTime) {
        this.x += this.dx;
        this.y += this.dy;
        this.alpha -= this.decay * deltaTime * 60;
      }

      draw() {
        ctx.save();
        ctx.globalAlpha = this.alpha;
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.closePath();
        ctx.restore();
      }
    }

    // Helper functions
    function createParticles(x, y, color) {
      for (let i = 0; i < 30; i++) {
        particles.push(new Particle(x, y, color));
      }
    }

    function checkWallCollisions(ball) {
      // Top wall
      if (ball.y - ball.radius < 0) {
        ball.y = ball.radius;
        ball.dy = -ball.dy;
      }

      // Left wall
      if (ball.x - ball.radius < 0) {
        ball.x = ball.radius;
        ball.dx = -ball.dx;
      }

      // Right wall
      if (ball.x + ball.radius > canvas.width) {
        ball.x = canvas.width - ball.radius;
        ball.dx = -ball.dx;
      }
    }

    function checkPaddleCollision(ball) {
      if (ball.y + ball.radius > paddle.y &&
          ball.y - ball.radius < paddle.y + paddle.height &&
          ball.x > paddle.x &&
          ball.x < paddle.x + paddle.width) {

        // Calculate bounce angle based on where ball hits paddle
        const relativeIntersectX = (paddle.x + paddle.width/2) - ball.x;
        const normalizedRelativeIntersectX = relativeIntersectX / (paddle.width/2);
        const bounceAngle = normalizedRelativeIntersectX * (Math.PI / 3); // Max 60 degrees from vertical

        // Reflect ball with new angle
        const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
        ball.dx = speed * Math.sin(bounceAngle);
        ball.dy = -speed * Math.cos(bounceAngle);

        // Ensure ball moves upward
        if (ball.dy > 0) ball.dy = -ball.dy;
      }
    }

    function checkBrickCollisions(ball) {
      for (let i = bricks.length - 1; i >= 0; i--) {
        const brick = bricks[i];

        if (ball.x + ball.radius > brick.x &&
            ball.x - ball.radius < brick.x + brick.width &&
            ball.y + ball.radius > brick.y &&
            ball.y - ball.radius < brick.y + brick.height) {

          // Calculate which side was hit
          const dx = ball.x - (brick.x + brick.width/2);
          const dy = ball.y - (brick.y + brick.height/2);
          const angle = Math.atan2(dy, dx);

          // Reflect ball
          ball.dx = Math.sin(angle) * 5;
          ball.dy = Math.cos(angle) * 5;

          // Hit the brick
          if (brick.hit()) {
            score += brick.type === 'tough' ? 30 : brick.type === 'power' ? 50 : 10;
            bricks.splice(i, 1);
          }

          // Ensure ball moves away from brick
          if (ball.dx > 0) ball.dx = Math.abs(ball.dx);
          else ball.dx = -Math.abs(ball.dx);

          if (ball.dy > 0) ball.dy = Math.abs(ball.dy);
          else ball.dy = -Math.abs(ball.dy);

          break; // Only handle one collision per frame
        }
      }
    }

    function activatePowerUp(type) {
      if (type === 'wide') {
        paddle.width = paddle.originalWidth * 1.5;
        powerUpActive = 'wide';
        powerUpTimer = 10; // 10 seconds
      } else if (type === 'multi') {
        // Add 2 more balls
        for (let i = 0; i < 2; i++) {
          balls.push(new Ball());
          // Position new balls around the main ball
          balls[balls.length - 1].x = paddle.x + paddle.width/2;
          balls[balls.length - 1].y = paddle.y - 20;
          // Randomize direction slightly
          balls[balls.length - 1].dx = (Math.random() - 0.5) * 6;
          balls[balls.length - 1].dy = -4 - Math.random() * 2;
        }
        powerUpActive = 'multi';
        powerUpTimer = 10; // 10 seconds
      }
    }

    function deactivatePowerUp() {
      if (powerUpActive === 'wide') {
        paddle.width = paddle.originalWidth;
      }
      powerUpActive = null;
      powerUpTimer = 0;
    }

    function createLevel() {
      bricks = [];
      const rows = 6;
      const cols = Math.floor(canvas.width / 80);
      const brickWidth = canvas.width / cols - 2;
      const brickHeight = 25;

      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          // Skip some positions for power-up bricks
          if (Math.random() < 0.1 && r > 1) {
            bricks.push(new Brick(c * (brickWidth + 2), 50 + r * (brickHeight + 10), 'power'));
          } else if (Math.random() < 0.3) {
            bricks.push(new Brick(c * (brickWidth + 2), 50 + r * (brickHeight + 10), 'tough'));
          } else {
            bricks.push(new Brick(c * (brickWidth + 2), 50 + r * (brickHeight + 10), 'normal'));
          }
        }
      }
    }

    function handleMouseMove(e) {
      if (gameState === gameStates.PLAYING) {
        const rect = canvas.getBoundingClientRect();
        mouseX = e.clientX - rect.left;
        paddle.x = mouseX - paddle.width/2;

        // Boundary check
        if (paddle.x < 0) paddle.x = 0;
        if (paddle.x + paddle.width > canvas.width) paddle.x = canvas.width - paddle.width;
      }
    }

    function handleKeyDown(e) {
      keys[e.key] = true;

      // Start game on any key press in menu or game over/victory screens
      if ((gameState === gameStates.MENU || gameState === gameStates.GAME_OVER || gameState === gameStates.VICTORY) && e.key) {
        startGame();
      }
    }

    function handleKeyUp(e) {
      keys[e.key] = false;
    }

    function startGame() {
      if (gameState === gameStates.MENU || gameState === gameStates.GAME_OVER || gameState === gameStates.VICTORY) {
        gameState = gameStates.PLAYING;
        score = 0;
        lives = 3;
        level = 1;
        bricks = [];
        balls = [];
        powerUps = [];
        particles = [];
        demoBall = null;

        // Create paddle and level
        paddle.reset();
        createLevel();

        // Add initial ball
        balls.push(new Ball());
      }
    }

    function resizeCanvas() {
      const container = document.body;
      const size = Math.min(container.clientWidth, container.clientHeight * 1.6);
      canvas.width = size;
      canvas.height = size / 1.6;
    }

    function drawBackground() {
      // Gradient background
      const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
      gradient.addColorStop(0, '#1a1a2e');
      gradient.addColorStop(1, '#16213e');
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }

    function drawUI() {
      // Score
      ctx.fillStyle = 'white';
      ctx.font = 'bold 20px Arial';
      ctx.textAlign = 'left';
      ctx.fillText(`Score: ${score}`, 20, 30);

      // Lives
      ctx.fillText(`Lives: ${lives}`, 20, 60);

      // Level
      ctx.textAlign = 'right';
      ctx.fillText(`Level: ${level}`, canvas.width - 20, 30);

      // Power-up timer if active
      if (powerUpActive) {
        ctx.textAlign = 'center';
        const timerText = powerUpActive === 'wide' ? 'WIDE PADDLE' : 'MULTI-BALL';
        ctx.fillText(`${timerText} (${powerUpTimer.toFixed(1)}s)`, canvas.width/2, 30);
      }
    }

    // Start the game
    init();
  </script>
</body>
</html>
Reasoning trace (7,273 tokens)
I need to create a polished Breakout game in a single HTML file with canvas. Let me break down the requirements:

1. **Game mechanics**:
   - Paddle controlled by mouse and arrow keys
   - Bounce angle depends on where ball hits paddle (center = straight, edges = sharp angle)
   - One level with at least 6 rows of bricks
   - Mix of normal bricks (1 hit) and tough bricks (2 hits, cracks after first hit)
   - Power-ups: wider paddle (10s) and multi-ball (3 balls)
   - Ball never passes through bricks/paddle
   - Particles when bricks break
   - 3 lives, score display
   - Game over and victory screens with restart

2. **Technical constraints**:
   - Single HTML file, no external resources except CDN URLs if needed
   - Everything inline: CSS in <style>, JS in <script>
   - Must work in 1280x800 viewport and adapt to 375px
   - First paint must show meaningful content
   - Games must start in self-playing demo mode behind "Click or press a key to start"
   - Zero console errors in latest Chrome

3. **Structure planning**:
   - Canvas setup with proper dimensions
   - Game state management (menu, playing, game over, victory)
   - Paddle class with mouse and keyboard controls
   - Ball class with physics (velocity, position, collision detection)
   - Brick class with different types (normal, tough, power-up)
   - Power-up class that drops from bricks and can be caught
   - Particle system for brick breaking effects
   - Score, lives, level tracking
   - Input handling for mouse and keyboard
   - Responsive design with proper scaling

Let me think about the bounce angle physics:
- When ball hits paddle, the angle should depend on where it hits
- Center hit: straight up (0 degrees relative to vertical)
- Left edge: bounce left at sharper angle
- Right edge: bounce right at sharper angle
- I can calculate the offset from paddle center and use that to determine the angle

Power-ups:
- Need to spawn when tough bricks or power-up bricks are broken
- Two types: wide paddle (10s duration) and multi-ball (3 balls)
- Should fall down and be catchable by paddle
- Need visual representation

Particles:
- When brick breaks, emit particles in different colors based on brick type
- Should be small circles that fade out

Game states:
- MENU: Show instructions, "Click to start"
- PLAYING: Game is active
- GAME_OVER: Player lost all lives
- VICTORY: All bricks destroyed

I'll need to handle:
- Collision detection between ball and bricks/paddle/walls
- Power-up collection
- Timer for power-ups
- Multi-ball management
- Lives and score tracking

Let me sketch the HTML structure:
```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Breakout Game</title>
  <style>
    /* CSS here */
  </style>
</head>
<body>
  <canvas id="gameCanvas"></canvas>
  <script>
    // Game code here
  </script>
</body>
</html>
```

For the canvas, I'll make it responsive using width/height attributes and CSS to scale.

Let me plan the JavaScript structure:

```javascript
// Game constants
const COLORS = {
  paddle: '#3498db',
  ball: '#e74c3c',
  brickNormal: '#2ecc71',
  brickTough: '#f39c12',
  brickPower: '#9b59b6',
  powerUpWide: '#3498db',
  powerUpMulti: '#e67e22'
};

// Game variables
let canvas, ctx;
let gameState = 'MENU'; // MENU, PLAYING, GAME_OVER, VICTORY
let score = 0;
let lives = 3;
let level = 1;
let bricks = [];
let balls = [];
let powerUps = [];
let particles = [];
let paddle;
let mouseX = 0;
let keys = {};
let powerUpActive = null;
let powerUpTimer = 0;

// Initialize game
function init() {
  canvas = document.getElementById('gameCanvas');
  ctx = canvas.getContext('2d');

  // Set canvas size
  resizeCanvas();

  // Event listeners
  window.addEventListener('resize', resizeCanvas);
  canvas.addEventListener('mousemove', handleMouseMove);
  canvas.addEventListener('click', startGame);
  window.addEventListener('keydown', handleKeyDown);
  window.addEventListener('keyup', handleKeyUp);

  // Create paddle
  paddle = new Paddle();

  // Create initial level
  createLevel();

  // Start game loop
  gameLoop();
}

// Game loop
function gameLoop(timestamp) {
  // Clear canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Update game state
  update();

  // Render
  render();

  // Continue loop
  requestAnimationFrame(gameLoop);
}

// Update game state
function update() {
  if (gameState === 'PLAYING') {
    // Update paddle
    paddle.update();

    // Update balls
    for (let i = balls.length - 1; i >= 0; i--) {
      balls[i].update();
      // Check collisions
      checkWallCollisions(balls[i]);
      checkPaddleCollision(balls[i]);
      checkBrickCollisions(balls[i]);
    }

    // Remove dead balls
    balls = balls.filter(ball => ball.active);

    // If no balls left, lose a life
    if (balls.length === 0) {
      lives--;
      if (lives <= 0) {
        gameState = 'GAME_OVER';
      } else {
        // Reset paddle and add new ball
        paddle.reset();
        balls.push(new Ball());
      }
    }

    // Update power-ups
    for (let i = powerUps.length - 1; i >= 0; i--) {
      powerUps[i].update();
      // Check if caught by paddle
      if (powerUps[i].y + powerUps[i].radius >= paddle.y &&
          powerUps[i].x > paddle.x &&
          powerUps[i].x < paddle.x + paddle.width) {
        activatePowerUp(powerUps[i].type);
        powerUps.splice(i, 1);
      }
      // Remove if off screen
      if (powerUps[i].y > canvas.height) {
        powerUps.splice(i, 1);
      }
    }

    // Update particles
    for (let i = particles.length - 1; i >= 0; i--) {
      particles[i].update();
      if (particles[i].alpha <= 0) {
        particles.splice(i, 1);
      }
    }

    // Check if all bricks are destroyed
    if (bricks.length === 0) {
      // Level complete
      if (level < 3) {
        level++;
        createLevel();
      } else {
        gameState = 'VICTORY';
      }
    }

    // Update power-up timer
    if (powerUpActive) {
      powerUpTimer -= 1/60;
      if (powerUpTimer <= 0) {
        deactivatePowerUp();
      }
    }
  }
}

// Render game
function render() {
  // Draw background
  drawBackground();

  // Draw bricks
  bricks.forEach(brick => brick.draw());

  // Draw balls
  balls.forEach(ball => ball.draw());

  // Draw paddle
  paddle.draw();

  // Draw power-ups
  powerUps.forEach(powerUp => powerUp.draw());

  // Draw particles
  particles.forEach(particle => particle.draw());

  // Draw UI
  drawUI();

  // Draw game state overlays
  if (gameState === 'MENU') {
    drawMenu();
  } else if (gameState === 'GAME_OVER') {
    drawGameOver();
  } else if (gameState === 'VICTORY') {
    drawVictory();
  }
}

// Helper classes
class Paddle {
  constructor() {
    this.width = 100;
    this.height = 15;
    this.x = (canvas.width - this.width) / 2;
    this.y = canvas.height - this.height - 20;
    this.speed = 8;
    this.color = COLORS.paddle;
    this.dx = 0;
    this.originalWidth = this.width;
  }

  update() {
    // Keyboard control
    if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
      this.dx = -this.speed;
    } else if (keys['ArrowRight'] || keys['d'] || keys['D']) {
      this.dx = this.speed;
    } else {
      this.dx = 0;
    }

    // Move paddle
    this.x += this.dx;

    // Boundary check
    if (this.x < 0) this.x = 0;
    if (this.x + this.width > canvas.width) this.x = canvas.width - this.width;

    // Mouse control
    // Already handled by mouse move event
  }

  draw() {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.width, this.height);

    // Draw center indicator
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    ctx.fillRect(this.x + this.width/2 - 2, this.y - 5, 4, 10);
  }

  reset() {
    this.width = this.originalWidth;
    this.x = (canvas.width - this.width) / 2;
  }
}

class Ball {
  constructor() {
    this.radius = 8;
    this.reset();
  }

  reset() {
    this.x = canvas.width / 2;
    this.y = canvas.height / 2;
    this.dx = 4 * (Math.random() > 0.5 ? 1 : -1);
    this.dy = -4;
    this.active = true;
  }

  update() {
    this.x += this.dx;
    this.y += this.dy;
  }

  draw() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fillStyle = COLORS.ball;
    ctx.fill();
    ctx.closePath();
  }
}

class Brick {
  constructor(x, y, type) {
    this.x = x;
    this.y = y;
    this.width = 75;
    this.height = 25;
    this.type = type; // 'normal', 'tough', 'power'
    this.hits = type === 'tough' ? 2 : 1;
    this.cracked = false;
    this.color = type === 'normal' ? COLORS.brickNormal :
                 type === 'tough' ? COLORS.brickTough : COLORS.brickPower;
    this.active = true;
  }

  draw() {
    if (!this.active) return;

    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.width, this.height);

    // Draw crack for tough bricks that have been hit once
    if (this.type === 'tough' && this.cracked) {
      ctx.strokeStyle = 'rgba(0,0,0,0.3)';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(this.x + 5, this.y + 5);
      ctx.lineTo(this.x + this.width - 5, this.y + this.height - 5);
      ctx.stroke();

      ctx.beginPath();
      ctx.moveTo(this.x + this.width - 5, this.y + 5);
      ctx.lineTo(this.x + 5, this.y + this.height - 5);
      ctx.stroke();
    }
  }

  hit() {
    this.hits--;
    if (this.hits <= 0) {
      this.active = false;
      // Create particles
      createParticles(this.x + this.width/2, this.y + this.height/2, this.color);
      // Chance to drop power-up
      if (this.type === 'power' || Math.random() < 0.2) {
        powerUps.push(new PowerUp(this.x + this.width/2, this.y));
      }
      return true;
    } else if (this.type === 'tough') {
      this.cracked = true;
    }
    return false;
  }
}

class PowerUp {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.radius = 10;
    this.type = Math.random() < 0.5 ? 'wide' : 'multi'; // 50% chance for each
    this.speed = 3;
    this.color = this.type === 'wide' ? COLORS.powerUpWide : COLORS.powerUpMulti;
    this.active = true;
  }

  update() {
    this.y += this.speed;
  }

  draw() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.closePath();

    // Draw icon
    ctx.fillStyle = 'white';
    ctx.font = 'bold 12px Arial';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    if (this.type === 'wide') {
      ctx.fillText('W', this.x, this.y);
    } else {
      ctx.fillText('M', this.x, this.y);
    }
  }
}

class Particle {
  constructor(x, y, color) {
    this.x = x;
    this.y = y;
    this.radius = Math.random() * 3 + 1;
    this.dx = (Math.random() - 0.5) * 4;
    this.dy = (Math.random() - 0.5) * 4;
    this.alpha = 1;
    this.color = color;
    this.decay = Math.random() * 0.02 + 0.01;
  }

  update() {
    this.x += this.dx;
    this.y += this.dy;
    this.alpha -= this.decay;
  }

  draw() {
    ctx.save();
    ctx.globalAlpha = this.alpha;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.closePath();
    ctx.restore();
  }
}

// Helper functions
function createParticles(x, y, color) {
  for (let i = 0; i < 30; i++) {
    particles.push(new Particle(x, y, color));
  }
}

function checkWallCollisions(ball) {
  // Top wall
  if (ball.y - ball.radius < 0) {
    ball.y = ball.radius;
    ball.dy = -ball.dy;
  }

  // Left wall
  if (ball.x - ball.radius < 0) {
    ball.x = ball.radius;
    ball.dx = -ball.dx;
  }

  // Right wall
  if (ball.x + ball.radius > canvas.width) {
    ball.x = canvas.width - ball.radius;
    ball.dx = -ball.dx;
  }
}

function checkPaddleCollision(ball) {
  if (ball.y + ball.radius > paddle.y &&
      ball.y - ball.radius < paddle.y + paddle.height &&
      ball.x > paddle.x &&
      ball.x < paddle.x + paddle.width) {

    // Calculate bounce angle based on where ball hits paddle
    const relativeIntersectX = (paddle.x + paddle.width/2) - ball.x;
    const normalizedRelativeIntersectX = relativeIntersectX / (paddle.width/2);
    const bounceAngle = normalizedRelativeIntersectX * (Math.PI / 3); // Max 60 degrees

    // Reflect ball with new angle
    const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
    ball.dx = speed * Math.sin(bounceAngle);
    ball.dy = -speed * Math.cos(bounceAngle);

    // Ensure ball moves upward
    if (ball.dy > 0) ball.dy = -ball.dy;
  }
}

function checkBrickCollisions(ball) {
  for (let i = bricks.length - 1; i >= 0; i--) {
    const brick = bricks[i];

    if (ball.x + ball.radius > brick.x &&
        ball.x - ball.radius < brick.x + brick.width &&
        ball.y + ball.radius > brick.y &&
        ball.y - ball.radius < brick.y + brick.height) {

      // Calculate which side was hit
      const dx = ball.x - (brick.x + brick.width/2);
      const dy = ball.y - (brick.y + brick.height/2);
      const angle = Math.atan2(dy, dx);

      // Reflect ball
      ball.dx = Math.sin(angle) * 5;
      ball.dy = Math.cos(angle) * 5;

      // Hit the brick
      if (brick.hit()) {
        score += brick.type === 'tough' ? 30 : brick.type === 'power' ? 50 : 10;
        bricks.splice(i, 1);
      }

      // Ensure ball moves away from brick
      if (ball.dx > 0) ball.dx = Math.abs(ball.dx);
      else ball.dx = -Math.abs(ball.dx);

      if (ball.dy > 0) ball.dy = Math.abs(ball.dy);
      else ball.dy = -Math.abs(ball.dy);

      break; // Only handle one collision per frame
    }
  }
}

function activatePowerUp(type) {
  if (type === 'wide') {
    paddle.width = paddle.originalWidth * 1.5;
    powerUpActive = 'wide';
    powerUpTimer = 10; // 10 seconds
  } else if (type === 'multi') {
    // Add 2 more balls
    for (let i = 0; i < 2; i++) {
      balls.push(new Ball());
      // Position new balls around the main ball
      balls[balls.length - 1].x = paddle.x + paddle.width/2;
      balls[balls.length - 1].y = paddle.y - 20;
      // Randomize direction slightly
      balls[balls.length - 1].dx = (Math.random() - 0.5) * 6;
      balls[balls.length - 1].dy = -4 - Math.random() * 2;
    }
    powerUpActive = 'multi';
    powerUpTimer = 10; // 10 seconds
  }
}

function deactivatePowerUp() {
  if (powerUpActive === 'wide') {
    paddle.width = paddle.originalWidth;
  }
  powerUpActive = null;
  powerUpTimer = 0;
}

function createLevel() {
  bricks = [];
  const rows = 6;
  const cols = Math.floor(canvas.width / 80);
  const brickWidth = canvas.width / cols - 2;
  const brickHeight = 25;

  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      // Skip some positions for power-up bricks
      if (Math.random() < 0.1 && r > 1) {
        bricks.push(new Brick(c * (brickWidth + 2), 50 + r * (brickHeight + 10), 'power'));
      } else if (Math.random() < 0.3) {
        bricks.push(new Brick(c * (brickWidth + 2), 50 + r * (brickHeight + 10), 'tough'));
      } else {
        bricks.push(new Brick(c * (brickWidth + 2), 50 + r * (brickHeight + 10), 'normal'));
      }
    }
  }
}

function handleMouseMove(e) {
  if (gameState === 'PLAYING') {
    const rect = canvas.getBoundingClientRect();
    mouseX = e.clientX - rect.left;
    paddle.x = mouseX - paddle.width/2;

    // Boundary check
    if (paddle.x < 0) paddle.x = 0;
    if (paddle.x + paddle.width > canvas.width) paddle.x = canvas.width - paddle.width;
  }
}

function handleKeyDown(e) {
  keys[e.key] = true;
}

function handleKeyUp(e) {
  keys[e.key] = false;
}

function startGame() {
  if (gameState === 'MENU') {
    gameState = 'PLAYING';
    // Start with one ball
    balls.push(new Ball());
  } else if (gameState === 'GAME_OVER' || gameState === 'VICTORY') {
    // Reset game
    gameState = 'PLAYING';
    score = 0;
    lives = 3;
    level = 1;
    bricks = [];
    balls = [];
    powerUps = [];
    particles = [];
    createLevel();
    balls.push(new Ball());
  }
}

function resizeCanvas() {
  const container = document.body;
  const size = Math.min(container.clientWidth, container.clientHeight * 1.6);
  canvas.width = size;
  canvas.height = size / 1.6;
}

function drawBackground() {
  // Gradient background
  const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
  gradient.addColorStop(0, '#1a1a2e');
  gradient.addColorStop(1, '#16213e');
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function drawUI() {
  // Score
  ctx.fillStyle = 'white';
  ctx.font = 'bold 20px Arial';
  ctx.textAlign = 'left';
  ctx.fillText(`Score: ${score}`, 20, 30);

  // Lives
  ctx.fillText(`Lives: ${lives}`, 20, 60);

  // Level
  ctx.textAlign = 'right';
  ctx.fillText(`Level: ${level}`, canvas.width - 20, 30);

  // Power-up timer if active
  if (powerUpActive) {
    ctx.textAlign = 'center';
    const timerText = powerUpActive === 'wide' ? 'WIDE PADDLE' : 'MULTI-BALL';
    ctx.fillText(`${timerText} (${powerUpTimer.toFixed(1)}s)`, canvas.width/2, 30);
  }
}

function drawMenu() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = 'white';
  ctx.font = 'bold 36px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('BREAKOUT', canvas.width/2, canvas.height/2 - 50);

  ctx.font = '24px Arial';
  ctx.fillText('Click or press a key to start', canvas.width/2, canvas.height/2);

  ctx.font = '18px Arial';
  ctx.fillText('Use mouse or arrow keys to move paddle', canvas.width/2, canvas.height/2 + 50);
  ctx.fillText('Catch power-ups (W = wide paddle, M = multi-ball)', canvas.width/2, canvas.height/2 + 80);
}

function drawGameOver() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = 'white';
  ctx.font = 'bold 36px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 30);

  ctx.font = '24px Arial';
  ctx.fillText(`Final Score: ${score}`, canvas.width/2, canvas.height/2);

  ctx.font = '18px Arial';
  ctx.fillText('Click or press a key to restart', canvas.width/2, canvas.height/2 + 50);
}

function drawVictory() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = 'white';
  ctx.font = 'bold 36px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('VICTORY!', canvas.width/2, canvas.height/2 - 30);

  ctx.font = '24px Arial';
  ctx.fillText(`Final Score: ${score}`, canvas.width/2, canvas.height/2);

  ctx.font = '18px Arial';
  ctx.fillText('Click or press a key to play again', canvas.width/2, canvas.height/2 + 50);
}

// Start the game
init();
```

Wait, I need to make sure the game starts in demo mode. The requirement 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 I need to add an AI paddle that moves automatically in demo mode. Let me modify the paddle update to handle demo mode:

```javascript
update() {
  if (gameState === 'PLAYING') {
    if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
      this.dx = -this.speed;
    } else if (keys['ArrowRight'] || keys['d'] || keys['D']) {
      this.dx = this.speed;
    } else {
      // In demo mode, move paddle automatically
      if (Math.random() < 0.02) {
        this.dx = Math.random() > 0.5 ? this.speed : -this.speed;
      }
    }

    // ... rest of update
  }
}
```

Actually, the requirement says "self-playing demo mode" which suggests the AI should play the game automatically. But the paddle is controlled by mouse/keys. Let me re-read: "Games must start in a self-playing demo mode (a simple AI plays) behind a 'Click or press a key to start' message"

This means the game should start with an AI controlling the paddle to demonstrate gameplay. So I need to add AI paddle logic that moves automatically when in demo mode (before the game starts).

Let me revise the paddle class and game state handling:

```javascript
class Paddle {
  // ... existing code

  update(aiControlled = false) {
    if (aiControlled) {
      // Simple AI: follow the ball
      const targetX = balls.length > 0 ? balls[0].x : canvas.width/2;
      if (targetX < this.x + this.width/2 - 20) {
        this.dx = -this.speed;
      } else if (targetX > this.x + this.width/2 + 20) {
        this.dx = this.speed;
      } else {
        this.dx = 0;
      }
    } else {
      // Keyboard control
      if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
        this.dx = -this.speed;
      } else if (keys['ArrowRight'] || keys['d'] || keys['D']) {
        this.dx = this.speed;
      } else {
        this.dx = 0;
      }
    }

    this.x += this.dx;
    if (this.x < 0) this.x = 0;
    if (this.x + this.width > canvas.width) this.x = canvas.width - this.width;
  }
}
```

And in the game loop:

```javascript
function update() {
  if (gameState === 'PLAYING') {
    paddle.update(false); // Player-controlled

    // ... rest of update
  } else if (gameState === 'MENU') {
    // AI demo mode
    paddle.update(true);
    if (balls.length === 0) {
      balls.push(new Ball());
    }
    // Update the ball
    if (balls.length > 0) {
      balls[0].update();
      checkWallCollisions(balls[0]);
      checkPaddleCollision(balls[0]);
      checkBrickCollisions(balls[0]);
    }
  }
}
```

Actually, I think the requirement means that when the game first loads, it should show the menu with a demo of the game playing itself. So I need to:

1. Start in MENU state
2. In MENU state, run an AI-controlled demo
3. Show "Click to start" overlay
4. When clicked, switch to PLAYING state with player control

Let me revise the update function:

```javascript
function update() {
  if (gameState === 'PLAYING') {
    paddle.update(false); // Player-controlled

    // Update balls
    for (let i = balls.length - 1; i >= 0; i--) {
      balls[i].update();
      checkWallCollisions(balls[i]);
      checkPaddleCollision(balls[i]);
      checkBrickCollisions(balls[i]);
    }

    // ... rest of playing state logic
  } else if (gameState === 'MENU') {
    // AI demo mode - move paddle to follow ball
    paddle.update(true);

    // Keep one ball in demo
    if (balls.length === 0) {
      balls.push(new Ball());
    }
    if (balls.length > 0) {
      balls[0].update();
      checkWallCollisions(balls[0]);
      checkPaddleCollision(balls[0]);
      checkBrickCollisions(balls[0]);
      // Remove dead balls in demo
      if (!balls[0].active) {
        balls = [];
      }
    }
  }

  // ... rest of update logic (power-ups, particles, etc.)
}
```

Now let me check all requirements again:

1. ✓ Paddle controlled with mouse and left/right arrow keys
2. ✓ Bounce angle depends on where ball hits paddle (center = straight, edges = sharp angle)
3. ✓ One level with at least 6 rows of bricks
4. ✓ Mix of normal bricks (1 hit) and tough bricks (2 hits, cracks after first)
5. ✓ Some bricks carry power-up icons (W for wide paddle, M for multi-ball)
6. ✓ Power-ups drop and can be caught
7. ✓ Ball never passes through bricks/paddle (collision detection)
8. ✓ Particles when brick breaks
9. ✓ 3 lives, score displayed
10. ✓ Game over and victory screens with restart
11. ✓ Starts in self-playing demo mode behind "Click to start" message
12. ✓ Single HTML file, no external resources
13. ✓ Responsive design
14. ✓ First paint shows meaningful content

I need to make sure the bounce angle is correct. Let me refine the checkPaddleCollision function:

```javascript
function checkPaddleCollision(ball) {
  if (ball.y + ball.radius > paddle.y &&
      ball.y - ball.radius < paddle.y + paddle.height &&
      ball.x > paddle.x &&
      ball.x < paddle.x + paddle.width) {

    // Calculate bounce angle based on where ball hits paddle
    const relativeIntersectX = (paddle.x + paddle.width/2) - ball.x;
    const normalizedRelativeIntersectX = relativeIntersectX / (paddle.width/2);
    const bounceAngle = normalizedRelativeIntersectX * (Math.PI / 3); // Max 60 degrees from vertical

    // Reflect ball with new angle
    const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
    ball.dx = speed * Math.sin(bounceAngle);
    ball.dy = -speed * Math.cos(bounceAngle);

    // Ensure ball moves upward
    if (ball.dy > 0) ball.dy = -ball.dy;
  }
}
```

This should give a nice angle effect where hitting the center gives a straight up bounce, and hitting the edges gives a sharper angle.

Now let me check the power-up system:
- Wide paddle: increases paddle width by 50% for 10 seconds
- Multi-ball: adds 2 more balls for 10 seconds
- Power-ups drop from bricks (20% chance from normal bricks, 100% from power bricks)
- Power-ups are caught when paddle overlaps them

I think this covers everything. Let me now write the complete HTML file with all the code properly integrated.

I need to make sure:
- The canvas is centered and responsive
- The game starts in demo mode
- All visual elements are polished
- The game is playable after clicking

Let me write the final code: