Models 44

Nemotron 3.5 Lightning (free)

by NVIDIAReasoning1 console error
Open in new window

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

How it went

Total time
1 min 31 s
Cost
$0
Output tokens
7,731
Spent thinking
560 tokens
Speed
85 tok/s
Console errors
1
First token after
239 ms
First visible text after
7.2 s
Prompt tokens
531
Code size
24.9 KB
Finish reason
stop
Served by
Nvidia
Model id
nvidia/nemotron-3.5-lightning:free
Reasoning sent
model default
Max output
32,000 tokens
Generated
Nov 9, 2026

Console errors

Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.

Cannot read properties of undefined (reading 'addEventListener')

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 · 892 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Breakout</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    canvas {
      display: block;
    }
    
    body {
      background: linear-gradient(180deg, #0a0a0f 0%, #1a1a24 100%);
      overflow-x: hidden;
      min-height: 100vh;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    }
    
    #ui {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
    }
    
    #title {
      position: fixed;
      top: 40px;
      left: 50%;
      transform: translateX(-50%);
      color: #fff;
      font-size: 28px;
      font-weight: 600;
      letter-spacing: 4px;
      text-transform: uppercase;
      text-shadow: 0 0 20px rgba(255, 200, 50, 0.5);
      pointer-events: auto;
      transition: opacity 0.3s;
    }
    
    #status {
      position: fixed;
      top: 100px;
      left: 50%;
      transform: translateX(-50%);
      color: #e0e0e0;
      font-size: 14px;
      text-shadow: 0 2px 4px rgba(0,0,0,0.5);
      pointer-events: auto;
      min-width: 200px;
      text-align: center;
    }
    
    #start-instruction {
      position: fixed;
      bottom: 40px;
      left: 50%;
      transform: translateX(-50%);
      color: #666;
      font-size: 13px;
      text-shadow: 0 2px 4px rgba(0,0,0,0.5);
      pointer-events: auto;
      transition: opacity 0.3s;
    }
    
    #score {
      position: fixed;
      top: 40px;
      left: 20px;
      color: #fff;
      font-size: 14px;
      text-shadow: 0 2px 4px rgba(0,0,0,0.5);
      pointer-events: auto;
    }
    
    #lives {
      position: fixed;
      top: 40px;
      right: 20px;
      color: #fff;
      font-size: 14px;
      text-shadow: 0 2px 4px rgba(0,0,0,0.5);
      pointer-events: auto;
    }
    
    #game-over, #victory {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(10, 10, 15, 0.95);
      display: none;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      pointer-events: auto;
    }
    
    #game-over.active, #victory.active {
      display: flex;
    }
    
    .over-title {
      color: #fff;
      font-size: 24px;
      margin-bottom: 20px;
      text-shadow: 0 4px 12px rgba(0,0,0,0.5);
    }
    
    .final-score {
      color: #ffc800;
      font-size: 16px;
      margin-bottom: 40px;
      text-shadow: 0 2px 4px rgba(0,0,0,0.5);
    }
    
    .btn {
      padding: 12px 30px;
      font-size: 16px;
      font-weight: 500;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      background: linear-gradient(135deg, #ff6b35, #feb422);
      color: #fff;
      text-transform: uppercase;
      letter-spacing: 1px;
      transition: transform 0.1s, box-shadow 0.1s;
    }
    
    .btn:active {
      transform: scale(0.98);
    }
    
    .btn:hover {
      box-shadow: 0 4px 20px rgba(255, 107, 53, 0.4);
    }
    
    .btn-restart {
      background: linear-gradient(135deg, #4facfe, #00f2fe);
    }
  </style>
</head>
<body>
  <div id="ui">
    <div id="title">Breakout</div>
    <div id="status"></div>
    <div id="score">Score: 0</div>
    <div id="lives">Lives: 3</div>
    <div id="start-instruction">Click or press a key to start</div>
    
    <div id="game-over">
      <div class="over-title">Game Over</div>
      <div class="final-score" id="final-score">0</div>
      <button class="btn btn-restart" id="restart-btn">Restart</button>
    </div>
    
    <div id="victory">
      <div class="over-title">Victory!</div>
      <div class="final-score" id="final-score-victory">0</div>
      <button class="btn btn-restart" id="restart-btn-victory">Restart</button>
    </div>
  </div>
  
  <canvas id="gameCanvas"></canvas>
  
  <script>
    // Game configuration
    const CONFIG = {
      canvasWidth: 1200,
      canvasHeight: 800,
      paddleWidth: 140,
      paddleHeight: 20,
      paddleSpeed: 8,
      ballRadius: 12,
      ballSpeed: 6,
      brickRowCount: 6,
      brickColumnCount: 8,
      brickWidth: 130,
      brickHeight: 30,
      brickPadding: 8,
      brickOffsetTop: 60,
      brickOffsetLeft: 60,
      lives: 3,
      particlesCount: 15
    };
    
    // Game state
    let canvas, ctx;
    let score = 0;
    let lives = CONFIG.lives;
    let gameStarted = false;
    let gamePaused = false;
    let particles = [];
    let powerUps = [];
    let balls = [];
    
    // Paddle state
    let paddle = {
      x: 0,
      y: 0,
      width: CONFIG.paddleWidth,
      height: CONFIG.paddleHeight
    };
    
    // Ball state
    let ball = {
      x: 0,
      y: 0,
      radius: CONFIG.ballRadius,
      dx: 0,
      dy: 0
    };
    
    // Bricks
    let bricks = [];
    
    // Initialize canvas
    function initCanvas() {
      canvas = document.getElementById('gameCanvas');
      ctx = canvas.getContext('2d');
      
      // Set canvas size with pixel ratio
      const dpr = window.devicePixelRatio || 1;
      canvas.width = CONFIG.canvasWidth * dpr;
      canvas.height = CONFIG.canvasHeight * dpr;
      canvas.style.width = `${CONFIG.canvasWidth}px`;
      canvas.style.height = `${CONFIG.canvasHeight}px`;
      ctx.scale(dpr, dpr);
      
      // Responsive handling
      resizeCanvas();
      window.addEventListener('resize', resizeCanvas);
    }
    
    function resizeCanvas() {
      const width = Math.min(1280, window.innerWidth - 20);
      const height = Math.max(600, window.innerHeight - 80);
      const ratio = width / CONFIG.canvasWidth;
      canvas.style.width = `${width}px`;
      canvas.style.height = `${height * ratio}px`;
    }
    
    // Color palette
    const colors = {
      normal: '#ff6b35',
      tough: '#e8c545',
      powerup: '#4facfe',
      background: '#0a0a0f',
      uiBackground: 'rgba(10, 10, 15, 0.8)'
    };
    
    // Brick types: 0 = normal, 1 = tough, 2 = powerup
    function createBricks() {
      bricks = [];
      const totalBricks = CONFIG.brickRowCount * CONFIG.brickColumnCount;
      let typeCount = { normal: 0, tough: 0, powerup: 0 };
      
      for (let row = 0; row < CONFIG.brickRowCount; row++) {
        bricks[row] = [];
        for (let col = 0; col < CONFIG.brickColumnCount; col++) {
          // Determine brick type - mix it up
          let type;
          const rand = Math.random();
          if (row < 2) {
            type = 0; // normal in first 2 rows
            typeCount.normal++;
          } else if (row < 4) {
            // Mix: 60% normal, 40% tough in middle rows
            type = Math.random() > 0.4 ? 0 : 1;
            if (type === 0) typeCount.normal++;
            else typeCount.tough++;
          } else {
            type = Math.random() > 0.3 ? 0 : 1;
            if (type === 0) typeCount.normal++;
            else typeCount.tough++;
          }
          
          // Occasionally add powerup to tough bricks
          if (type === 1 && Math.random() > 0.7 && typeCount.tough > 4) {
            type = 2; // powerup
            typeCount.powerup++;
            typeCount.tough--;
          }
          
          bricks[row][col] = {
            x: col * (CONFIG.brickWidth + CONFIG.brickPadding) + CONFIG.brickOffsetLeft,
            y: row * (CONFIG.brickHeight + CONFIG.brickPadding) + CONFIG.brickOffsetTop,
            width: CONFIG.brickWidth,
            height: CONFIG.brickHeight,
            status: 1, // 1 = intact, 0 = destroyed
            type: type,
            cracks: 0 // for tough bricks
          };
        }
      }
      return typeCount;
    }
    
    // Initialize game objects
    function initGame() {
      score = 0;
      lives = CONFIG.lives;
      createBricks();
      
      // Paddle position
      paddle.x = (CONFIG.canvasWidth - CONFIG.paddleWidth) / 2;
      paddle.y = CONFIG.canvasHeight - 60;
      
      // Ball position and serve
      ball.x = CONFIG.canvasWidth / 2;
      ball.y = CONFIG.canvasHeight / 2;
      ball.dx = CONFIG.ballSpeed * (Math.random() > 0.5 ? 1 : -1);
      ball.dy = -CONFIG.ballSpeed;
      
      balls = [{ x: ball.x, y: ball.y, dx: ball.dx, dy: ball.dy }];
      particles = [];
      powerUps = [];
      
      // Hide UI elements
      document.getElementById('start-instruction').style.opacity = '1';
      document.getElementById('title').style.opacity = '1';
    }
    
    // Handle input - mouse
    let mouseX = 0;
    canvas.addEventListener('mousemove', (e) => {
      if (!gameStarted) return;
      const rect = canvas.getBoundingClientRect();
      mouseX = e.clientX - rect.left;
      updatePaddlePosition(mouseX);
    });
    
    // Handle input - keyboard
    let leftPressed = false;
    let rightPressed = false;
    document.addEventListener('keydown', (e) => {
      if (e.key === 'ArrowLeft') {
        leftPressed = true;
      } else if (e.key === 'ArrowRight') {
        rightPressed = true;
      }
      if (!gameStarted && (e.key === ' ' || e.keyCode >= 37 && e.keyCode <= 40)) {
        gameStarted = true;
        document.getElementById('start-instruction').style.opacity = '0';
        document.getElementById('title').style.opacity = '0';
      }
    });
    
    document.addEventListener('keyup', (e) => {
      if (e.key === 'ArrowLeft') {
        leftPressed = false;
      } else if (e.key === 'ArrowRight') {
        rightPressed = false;
      }
    });
    
    // Update paddle position based on control method
    function updatePaddlePosition(targetX) {
      // Smooth movement to mouse position
      const speed = 0.15;
      paddle.x += (targetX - paddle.x - CONFIG.paddleWidth / 2) * speed;
      
      // Keep paddle in bounds
      if (paddle.x < 0) paddle.x = 0;
      if (paddle.x + CONFIG.paddleWidth > CONFIG.canvasWidth) {
        paddle.x = CONFIG.canvasWidth - CONFIG.paddleWidth;
      }
    }
    
    function updatePaddleKeys() {
      if (!gameStarted) return;
      
      if (leftPressed) {
        paddle.x -= CONFIG.paddleSpeed;
      }
      if (rightPressed) {
        paddle.x += CONFIG.paddleSpeed;
      }
      
      // Keep paddle in bounds
      if (paddle.x < 0) paddle.x = 0;
      if (paddle.x + CONFIG.paddleWidth > CONFIG.canvasWidth) {
        paddle.x = CONFIG.canvasWidth - CONFIG.paddleWidth;
      }
    }
    
    // Spawn particles when brick breaks
    function spawnParticles(brick) {
      const centerX = brick.x + brick.width / 2;
      const centerY = brick.y + brick.height / 2;
      
      for (let i = 0; i < CONFIG.particlesCount; i++) {
        const angle = (Math.PI * 2 * i) / CONFIG.particlesCount;
        const velocity = 3 + Math.random() * 3;
        particles.push({
          x: centerX,
          y: centerY,
          dx: Math.cos(angle) * velocity,
          dy: Math.sin(angle) * velocity,
          radius: 2 + Math.random() * 3,
          color: brick.type === 2 ? '#4facfe' : (brick.type === 1 ? '#e8c545' : '#ff6b35'),
          opacity: 1,
          friction: 0.99
        });
      }
    }
    
    // Spawn power-up
    function spawnPowerUp(brick) {
      const centerX = brick.x + brick.width / 2;
      const centerY = brick.y + brick.height / 2;
      
      // 50% chance of wide paddle, 50% chance of multi-ball
      const type = Math.random() > 0.5 ? 'wide' : 'multi';
      
      powerUps.push({
        x: centerX - 15,
        y: centerY - 15,
        width: 30,
        height: 30,
        type: type,
        dy: 2
      });
    }
    
    // Draw paddle
    function drawPaddle() {
      // Handle wide paddle power-up
      const displayWidth = paddle.width * (paddle.isWide ? 1.5 : 1);
      
      ctx.fillStyle = '#fff';
      ctx.shadowColor = '#ff6b35';
      ctx.shadowBlur = 15;
      ctx.fillRect(paddle.x, paddle.y, displayWidth, paddle.height);
      ctx.shadowBlur = 0;
    }
    
    // Draw ball
    function drawBall(ballObj) {
      ctx.fillStyle = '#fff';
      ctx.shadowColor = '#fff';
      ctx.shadowBlur = 15;
      ctx.beginPath();
      ctx.arc(ballObj.x, ballObj.y, ballObj.radius, 0, Math.PI * 2);
      ctx.fill();
      ctx.shadowBlur = 0;
    }
    
    // Draw bricks
    function drawBricks() {
      for (let row = 0; row < CONFIG.brickRowCount; row++) {
        for (let col = 0; col < CONFIG.brickColumnCount; col++) {
          const brick = bricks[row][col];
          if (brick.status === 0) continue;
          
          const brickX = brick.x;
          const brickY = brick.y;
          
          // Determine color based on type
          let color;
          if (brick.type === 2) {
            color = '#ff6b35'; // powerup bricks are orange with indicator
          } else if (brick.type === 1) {
            color = '#e8c545'; // tough bricks are gold
          } else {
            color = '#ff6b35'; // normal bricks are orange
          }
          
          ctx.fillStyle = color;
          ctx.shadowColor = color;
          ctx.shadowBlur = 5;
          
          // Draw brick
          ctx.fillRect(brickX, brickY, brick.width, brick.height);
          
          // Draw crack pattern for tough bricks that have been hit
          if (brick.type === 1 && brick.cracks > 0) {
            ctx.strokeStyle = 'rgba(0,0,0,0.5)';
            ctx.lineWidth = 2;
            
            // Horizontal crack
            ctx.beginPath();
            ctx.moveTo(brickX + 10, brickY + brick.height / 2);
            ctx.lineTo(brickX + brick.width - 10, brickY + brick.height / 2);
            ctx.stroke();
            
            // Vertical crack (if heavily cracked)
            if (brick.cracks >= 2) {
              ctx.beginPath();
              ctx.moveTo(brickX + brick.width / 2, brickY + 10);
              ctx.lineTo(brickX + brick.width / 2, brickY + brick.height - 10);
              ctx.stroke();
            }
            
            // Number cracks
            if (brick.cracks >= 3) {
              ctx.beginPath();
              ctx.moveTo(brickX + 15, brickY + 15);
              ctx.lineTo(brickX + brick.width - 15, brickY + brick.height - 15);
              ctx.stroke();
              
              ctx.beginPath();
              ctx.moveTo(brickX + brick.width - 15, brickY + 15);
              ctx.lineTo(brickX + 15, brickY + brick.height - 15);
              ctx.stroke();
            }
          }
          
          // Draw power-up icon on powerup bricks
          if (brick.type === 2) {
            ctx.fillStyle = '#fff';
            ctx.font = 'bold 16px system-ui';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillIcon(brickX + brick.width / 2, brickY + brick.height / 2, '⚡');
          }
          
          ctx.shadowBlur = 0;
        }
      }
    }
    
    // Helper to draw icon text (since we can't use SVG)
    function fillIcon(x, y, text) {
      // This will be called in the draw loop, we'll handle it differently
      // Actually let's just draw a simple shape instead
      ctx.fillStyle = '#fff';
      ctx.beginPath();
      // Draw a simple triangle/arrow
      ctx.moveTo(x - 8, y - 6);
      ctx.lineTo(x + 8, y - 6);
      ctx.lineTo(x, y + 6);
      ctx.closePath();
      ctx.fill();
    }
    
    // Draw power-ups
    function drawPowerUps() {
      powerUps.forEach((pu, pi) => {
        ctx.fillStyle = '#4facfe';
        ctx.shadowColor = '#4facfe';
        ctx.shadowBlur = 10;
        ctx.beginPath();
        ctx.arc(pu.x, pu.y, 12, 0, Math.PI * 2);
        ctx.fill();
        ctx.shadowBlur = 0;
        
        // Draw icon based on type
        ctx.fillStyle = '#fff';
        ctx.font = 'bold 18px system-ui';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        if (pu.type === 'wide') {
          ctx.fillText('↔', pu.x, pu.y);
        } else {
          ctx.fillText('★', pu.x, pu.y);
        }
      });
    }
    
    // Draw particles
    function drawParticles() {
      particles.forEach((p, pi) => {
        ctx.fillStyle = p.color;
        ctx.globalAlpha = p.opacity;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
        ctx.fill();
        p.x += p.dx;
        p.y += p.dy;
        p.dx *= p.friction;
        p.dy *= p.friction;
        p.opacity -= 0.02;
      });
      ctx.globalAlpha = 1;
    }
    
    // Draw score and lives
    function drawUI() {
      ctx.fillStyle = '#fff';
      ctx.font = '14px system-ui';
      ctx.fillText(`Score: ${score}`, 20, 35);
      
      ctx.fillText(`Lives: ${lives}`, CONFIG.canvasWidth - 80, 35);
    }
    
    // Draw start instruction
    function drawStartInstruction() {
      ctx.fillStyle = '#666';
      ctx.font = '13px system-ui';
      ctx.textAlign = 'center';
      ctx.fillText('Click or press a key to start', CONFIG.canvasWidth / 2, CONFIG.canvasHeight - 40);
    }
    
    // Draw game over
    function drawGameOver() {
      const goDiv = document.getElementById('game-over');
      goDiv.style.display = 'flex';
    }
    
    // Draw victory
    function drawVictory() {
      const finalScoreEl = document.getElementById('final-score-victory');
      finalScoreEl.textContent = score;
      const victoryDiv = document.getElementById('victory');
      victoryDiv.style.display = 'flex';
    }
    
    // Reset ball to center
    function resetBall() {
      ball.x = CONFIG.canvasWidth / 2;
      ball.y = CONFIG.canvasHeight / 2;
      ball.dx = CONFIG.ballSpeed * (Math.random() > 0.5 ? 1 : -1);
      ball.dy = -CONFIG.ballSpeed;
    }
    
    // Check ball-paddle collision
    function checkPaddleCollision(ballObj) {
      if (ballObj.y + ballObj.radius > paddle.y &&
          ballObj.y + ballObj.radius < paddle.y + paddle.height) {
        
        if (ballObj.x > paddle.x && ballObj.x < paddle.x + paddle.width) {
          // Calculate hit position (-1 to 1 from left to right)
          const paddleCenter = paddle.x + paddle.width / 2;
          const hitPos = (ballObj.x - paddleCenter) / (paddle.width / 2);
          
          // Angle depends on where ball hits paddle
          const maxAngle = Math.PI / 6; // 30 degrees
          const angle = hitPos * maxAngle;
          
          const speed = Math.sqrt(ballObj.dx * ballObj.dx + ballObj.dy * ballObj.dy);
          ballObj.dx = speed * Math.sin(angle);
          ballObj.dy = -speed * Math.cos(angle);
          
          return true;
        }
      }
      return false;
    }
    
    // Check brick collision
    function checkBrickCollision(ballObj) {
      for (let row = 0; row < CONFIG.brickRowCount; row++) {
        for (let col = 0; col < CONFIG.brickColumnCount; col++) {
          const brick = bricks[row][col];
          if (brick.status === 0) continue;
          
          if (ballObj.x + ballObj.radius > brick.x &&
              ballObj.x - ballObj.radius < brick.x + brick.width &&
              ballObj.y + ballObj.radius > brick.y &&
              ballObj.y - ballObj.radius < brick.y + brick.height) {
            
            // Hit! Determine which side was hit
            const brickCenterX = brick.x + brick.width / 2;
            const brickCenterY = brick.y + brick.height / 2;
            
            // Calculate collision normal
let hitSide = null;

const dx = Math.abs(ballObj.x - brickCenterX) - brick.width / 2;
const dy = Math.abs(ballObj.y - brickCenterY) - brick.height / 2;

if (dx > dy) {
  hitSide = 'vertical'; // Hit left or right side
} else {
  hitSide = 'horizontal'; // Hit top or bottom
}

            // Destroy brick based on type
            if (brick.type === 2) {
              // Powerup brick - always drops powerup, destroys in 1 hit
              brick.status = 0;
              score += 50;
              spawnPowerUp(brick);
            } else if (brick.type === 1) {
              // Tough brick - needs 2 hits
              brick.cracks++;
              if (brick.cracks >= 3) {
                brick.status = 0;
                score += 70;
                spawnParticles(brick);
              } else {
                score += 30;
              }
            } else {
              // Normal brick
              brick.status = 0;
              score += 40;
              // 20% chance of powerup
              if (Math.random() > 0.8) {
                spawnPowerUp(brick);
              }
            }
            
            // Reverse ball direction
            if (hitSide === 'horizontal') {
              ballObj.dy = -ballObj.dy;
            } else {
              ballObj.dx = -ballObj.dx;
            }
            
            // Add particle effect
            spawnParticles(brick);
            
            return true;
          }
        }
      }
      return false;
    }
    
    // Check power-up collision with paddle
    function checkPowerUpCollision() {
      powerUps.forEach((pu, pi) => {
        if (pu.y + pu.height > paddle.y &&
            pu.y < paddle.y + paddle.height &&
            pu.x + pu.width > paddle.x &&
            pu.x < paddle.x + paddle.width) {
          
          // Apply power-up
          if (pu.type === 'wide') {
            paddle.isWide = true;
            setTimeout(() => {
              paddle.isWide = false;
            }, 10000);
          } else if (pu.type === 'multi') {
            // Add 2 extra balls
            for (let i = 0; i < 2; i++) {
              balls.push({
                x: ball.x,
                y: ball.y,
                dx: ball.dx + (Math.random() - 0.5) * 2,
                dy: ball.dy
              });
            }
          }
          
          powerUps.splice(pi, 1);
        }
      });
    }
    
    // Update all balls
    function updateBalls() {
      // Update each ball
      for (let i = balls.length - 1; i >= 0; i--) {
        const ballObj = balls[i];
        
        // Move ball
        ballObj.x += ballObj.dx;
        ballObj.y += ballObj.dy;
        
        // Wall collision (left/right)
        if (ballObj.x - ballObj.radius < 0 || ballObj.x + ballObj.radius > CONFIG.canvasWidth) {
          ballObj.dx = -ballObj.dx;
          ballObj.x = Math.max(ballObj.radius, Math.min(CONFIG.canvasWidth - ballObj.radius, ballObj.x));
        }
        
        // Wall collision (top)
        if (ballObj.y - ballObj.radius < 0) {
          ballObj.dy = -ballObj.dy;
          ballObj.y = ballObj.radius;
        }
        
        // Paddle collision
        if (!checkPaddleCollision(ballObj)) {
          // Check brick collision
          if (!checkBrickCollision(ballObj)) {
            // Check if ball fell below paddle
            if (ballObj.y + ballObj.radius > CONFIG.canvasHeight) {
              // Lose this ball
              balls.splice(i, 1);
              
              // If no balls left, lose life
              if (balls.length === 0) {
                lives--;
                document.getElementById('lives').textContent = `Lives: ${lives}`;
                
                if (lives <= 0) {
                  drawGameOver();
                  return;
                }
                // Reset balls
                resetBall();
                balls.push({ ...ball });
              }
            }
          }
        }
      }
    }
    
    // Update power-ups
    function updatePowerUps() {
      powerUps.forEach((pu, pi) => {
        pu.y += pu.dy;
        
        // Remove if off screen
        if (pu.y > CONFIG.canvasHeight) {
          powerUps.splice(pi, 1);
        }
      });
    }
    
    // Update particles
    function updateParticles() {
      // Particles are drawn and updated in drawParticles
    }
    
    // Check victory condition
    function checkVictory() {
      let remaining = 0;
      for (let row = 0; row < CONFIG.brickRowCount; row++) {
        for (let col = 0; col < CONFIG.brickColumnCount; col++) {
          if (bricks[row][col].status === 1) remaining++;
        }
      }
      if (remaining === 0) {
        drawVictory();
      }
    }
    
    // Main game loop
    function gameLoop() {
      // Clear canvas
      ctx.fillStyle = '#0a0a0f';
      ctx.fillRect(0, 0, CONFIG.canvasWidth, CONFIG.canvasHeight);
      
      if (gameStarted && !gamePaused) {
        // Update paddle position (mouse or keys)
        updatePaddleKeys();
        
        // Update all balls
        updateBalls();
        
        // Update power-ups
        updatePowerUps();
        
        // Check power-up collection
        checkPowerUpCollision();
        
        // Check victory
        checkVictory();
      }
      
      // Draw everything
      drawBricks();
      drawPowerUps();
      
      // Draw all balls
      balls.forEach(drawBall);
      
      drawPaddle();
      drawUI();
      
      if (!gameStarted) {
        drawStartInstruction();
      }
      
      drawParticles();
    }
    
    // Start button / restart
    document.getElementById('restart-btn').addEventListener('click', () => {
      document.getElementById('game-over').style.display = 'none';
      initGame();
    });
    
    document.getElementById('restart-btn-victory').addEventListener('click', () => {
      document.getElementById('victory').style.display = 'none';
      initGame();
    });
    
    // Initialize and start
    initCanvas();
    initGame();
    
    // Game loop
    setInterval(gameLoop, 16);
  </script>
</body>
</html>
Reasoning trace (560 tokens)
Here's my plan for the Breakout game:

1. **Core Structure**: Single HTML file with canvas, vanilla JS
2. **Game Elements**:
   - Paddle (mouse + arrow keys control)
   - Ball with physics
   - Bricks (6 rows, mixed normal/tough bricks that crack)
   - Power-ups (wider paddle, multi-ball)
   - Particles on brick break
   - Lives, score
   - Game over / victory screens

3. **Key Features**:
   - Angle depends on hit position on paddle
   - Cracked tough bricks visible
   - Power-up dropping and catching
   - Particles
   - 3 lives
   - Self-playing demo mode initially

4. **Constraints**:
   - No external files, images, fonts
   - Everything inline
   - Responsive (1280×800 to 375px)
   - No localStorage/cookies/alert/confirm/prompt
   - Must work in sandbox
   - Auto-play demo mode behind "Click or press a key to start"

Let me build this step by step, keeping it all in one file with inline CSS/JS, using system fonts, and making it visually appealing without external assets.

For bricks: I'll use colored squares with different opacities/patterns indicated by their appearance. For tough bricks, I'll draw a crack pattern. For power-ups, I'll use simple shapes/icons drawn on the bricks.

For particles: simple fading circles with random colors.

For power-ups: wider paddle (size increase with timer) and multi-ball (split ball into 3).

Let me code this now. I'll need to be careful about:
- The canvas being responsive
- No horizontal scrollbar
- Demo mode with simple AI
- Keyboard/mouse controls
- All the game mechanics

Let me structure the code:

1. HTML skeleton with canvas
2. CSS for full-page canvas, no scrollbar
3. JS game logic

For the "images" - I'll draw everything with canvas API. For brick patterns, I'll use different fill colors and maybe simple stripe patterns drawn with rects. For power-up icons, I'll draw simple shapes (triangle for wide paddle, star for multi-ball).

Let me think about the brick layout:
- 6 rows
- Mix of normal and tough bricks
- Some with power-up icons

I'll make about 8 bricks per row = 48 bricks total, with maybe 1/3 tough, 1/3 with power-ups.

For the demo mode: I'll have a simple AI that moves the paddle to follow the ball's predicted position.

Let me code this now.