Models 44

Nemotron 3 Ultra (free)

by NVIDIAReasoningClean
Open in new window

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

How it went

Total time
2 min 20 s
Cost
$0
Output tokens
9,213
Spent thinking
325 tokens
Speed
193.2 tok/s
Console errors
0
First token after
1 min 32 s
First visible text after
1 min 40 s
Prompt tokens
531
Code size
26.3 KB
Finish reason
stop
Served by
Nvidia
Model id
nvidia/nemotron-3-ultra-550b-a55b:free
Reasoning sent
{"effort":"high"}
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 · 852 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; }
  body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    color: #e0e0e0;
    overflow: hidden;
  }
  #gameContainer {
    position: relative;
    width: 100%;
    max-width: 800px;
    aspect-ratio: 5 / 4;
  }
  canvas {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 8px;
    box-shadow: 0 20px 60px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.1);
    background: #0a0a15;
  }
  .overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    background: rgba(10, 10, 21, 0.95);
    backdrop-filter: blur(8px);
    padding: 2rem;
    text-align: center;
    z-index: 10;
    transition: opacity 0.3s ease, visibility 0.3s ease;
  }
  .overlay.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
  .overlay h1 {
    font-size: clamp(2rem, 5vw, 3.5rem);
    font-weight: 700;
    background: linear-gradient(135deg, #ffd700, #ff6b35, #ffd700);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    margin-bottom: 0.5rem;
    letter-spacing: 2px;
  }
  .overlay p { font-size: clamp(1rem, 2.5vw, 1.2rem); opacity: 0.8; margin-bottom: 1.5rem; }
  .btn {
    padding: 0.875rem 2.5rem;
    font-size: 1.1rem;
    font-weight: 600;
    border: none;
    border-radius: 50px;
    cursor: pointer;
    background: linear-gradient(135deg, #ff6b35, #f7931e);
    color: #fff;
    box-shadow: 0 4px 20px rgba(255, 107, 53, 0.4);
    transition: transform 0.2s, box-shadow 0.2s;
  }
  .btn:hover { transform: translateY(-2px); box-shadow: 0 6px 30px rgba(255, 107, 53, 0.6); }
  .btn:active { transform: translateY(0); }
  .stats {
    display: flex;
    justify-content: space-between;
    width: 100%;
    max-width: 800px;
    margin-top: 1rem;
    padding: 0 0.5rem;
    font-size: clamp(0.85rem, 2vw, 1rem);
  }
  .stat { display: flex; align-items: center; gap: 0.5rem; }
  .stat-label { opacity: 0.6; }
  .stat-value { font-weight: 700; color: #ffd700; }
  .lives-display { display: flex; gap: 0.35rem; }
  .life { width: 18px; height: 18px; border-radius: 50%; background: radial-gradient(circle at 30% 30%, #ff6b35, #c0392b); box-shadow: 0 0 10px #ff6b35; }
  .life.lost { background: #333; box-shadow: none; opacity: 0.3; }
  @media (max-width: 400px) { .stats { flex-wrap: wrap; gap: 0.5rem; justify-content: center; } }
</style>
</head>
<body>
<div id="gameContainer">
  <canvas id="canvas"></canvas>
  <div id="startOverlay" class="overlay">
    <h1>BREAKOUT</h1>
    <p>Click or press any key to start</p>
    <button class="btn" id="startBtn">Start Game</button>
  </div>
  <div id="gameOverOverlay" class="overlay hidden">
    <h1>GAME OVER</h1>
    <p id="finalScore">Score: 0</p>
    <button class="btn" id="restartBtn">Play Again</button>
  </div>
  <div id="victoryOverlay" class="overlay hidden">
    <h1>VICTORY!</h1>
    <p id="victoryScore">Score: 0</p>
    <button class="btn" id="victoryRestartBtn">Play Again</button>
  </div>
</div>
<div class="stats">
  <div class="stat"><span class="stat-label">Score:</span><span class="stat-value" id="scoreDisplay">0</span></div>
  <div class="stat"><span class="stat-label">Lives:</span><div class="lives-display" id="livesDisplay"></div></div>
  <div class="stat"><span class="stat-label">Level:</span><span class="stat-value" id="levelDisplay">1</span></div>
</div>

<script>
// ===== CONFIG =====
const CONFIG = {
  canvasWidth: 800,
  canvasHeight: 640,
  paddleHeight: 16,
  paddleWidth: 100,
  paddleSpeed: 8,
  ballRadius: 8,
  ballSpeed: 5,
  brickRows: 6,
  brickCols: 10,
  brickPadding: 4,
  brickOffsetTop: 60,
  brickOffsetLeft: 30,
  lives: 3,
  powerUpDropChance: 0.25,
  powerUpFallSpeed: 2,
  powerUpDuration: 10000,
  particleCount: 12,
  demoMode: true
};

// ===== GAME STATE =====
const state = {
  running: false,
  demoMode: true,
  score: 0,
  lives: CONFIG.lives,
  level: 1,
  paddleX: 0,
  paddleWidth: CONFIG.paddleWidth,
  paddleTargetX: 0,
  balls: [],
  bricks: [],
  powerUps: [],
  particles: [],
  powerUpTimers: { widePaddle: 0, multiBall: 0 },
  keys: { left: false, right: false },
  mouseX: 0
};

// ===== CANVAS SETUP =====
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
  const container = document.getElementById('gameContainer');
  const rect = container.getBoundingClientRect();
  const dpr = window.devicePixelRatio || 1;
  canvas.width = rect.width * dpr;
  canvas.height = rect.height * dpr;
  canvas.style.width = rect.width + 'px';
  canvas.style.height = rect.height + 'px';
  ctx.scale(dpr, dpr);
  state.paddleX = (CONFIG.canvasWidth - state.paddleWidth) / 2;
  state.paddleTargetX = state.paddleX;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();

// ===== UTILITIES =====
const rand = (min, max) => Math.random() * (max - min) + min;
const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
function circleRectCollide(cx, cy, r, rx, ry, rw, rh) {
  const closestX = clamp(cx, rx, rx + rw);
  const closestY = clamp(cy, ry, ry + rh);
  const dx = cx - closestX;
  const dy = cy - closestY;
  return dx * dx + dy * dy < r * r;
}
function rectRectCollide(x1, y1, w1, h1, x2, y2, w2, h2) {
  return x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2;
}

// ===== BRICK CREATION =====
function createBricks() {
  state.bricks = [];
  const brickWidth = (CONFIG.canvasWidth - CONFIG.brickOffsetLeft * 2 - CONFIG.brickPadding * (CONFIG.brickCols - 1)) / CONFIG.brickCols;
  const brickHeight = 28;
  const colors = [
    { base: '#e74c3c', light: '#ff6b5b', dark: '#c0392b' },
    { base: '#e67e22', light: '#ff9f43', dark: '#d35400' },
    { base: '#f39c12', light: '#f5b041', dark: '#d4ac0d' },
    { base: '#2ecc71', light: '#58d68d', dark: '#27ae60' },
    { base: '#3498db', light: '#5dade2', dark: '#2980b9' },
    { base: '#9b59b6', light: '#bb8fce', dark: '#8e44ad' }
  ];
  for (let r = 0; r < CONFIG.brickRows; r++) {
    for (let c = 0; c < CONFIG.brickCols; c++) {
      const x = CONFIG.brickOffsetLeft + c * (brickWidth + CONFIG.brickPadding);
      const y = CONFIG.brickOffsetTop + r * (brickHeight + CONFIG.brickPadding);
      const colorSet = colors[r % colors.length];
      const isTough = Math.random() < 0.35 && r > 1;
      const hasPowerUp = !isTough && Math.random() < CONFIG.powerUpDropChance;
      const powerUpType = hasPowerUp ? (Math.random() < 0.5 ? 'widePaddle' : 'multiBall') : null;
      state.bricks.push({
        x, y, width: brickWidth, height: brickHeight,
        color: colorSet.base, lightColor: colorSet.light, darkColor: colorSet.dark,
        hits: isTough ? 2 : 1, maxHits: isTough ? 2 : 1,
        hasPowerUp, powerUpType, destroyed: false
      });
    }
  }
}

// ===== BALL CREATION =====
function createBall(x, y, vx, vy) {
  return {
    x, y, vx, vy, radius: CONFIG.ballRadius,
    trail: [], maxTrail: 8
  };
}
function launchBall(x, y, angle = -Math.PI / 2, speed = CONFIG.ballSpeed) {
  state.balls.push(createBall(x, y, Math.cos(angle) * speed, Math.sin(angle) * speed));
}

// ===== POWER-UPS =====
function spawnPowerUp(x, y, type) {
  state.powerUps.push({ x, y, type, width: 24, height: 24, vy: CONFIG.powerUpFallSpeed, rotation: 0 });
}
function activatePowerUp(type) {
  const now = Date.now();
  if (type === 'widePaddle') {
    state.powerUpTimers.widePaddle = now + CONFIG.powerUpDuration;
    state.paddleWidth = CONFIG.paddleWidth * 1.6;
  } else if (type === 'multiBall') {
    state.powerUpTimers.multiBall = now + CONFIG.powerUpDuration;
    const mainBall = state.balls[0];
    if (mainBall) {
      launchBall(mainBall.x, mainBall.y, -Math.PI / 2 - 0.5, CONFIG.ballSpeed);
      launchBall(mainBall.x, mainBall.y, -Math.PI / 2 + 0.5, CONFIG.ballSpeed);
    }
  }
}
function updatePowerUpTimers() {
  const now = Date.now();
  if (state.powerUpTimers.widePaddle && now > state.powerUpTimers.widePaddle) {
    state.paddleWidth = CONFIG.paddleWidth;
    state.powerUpTimers.widePaddle = 0;
  }
  if (state.powerUpTimers.multiBall && now > state.powerUpTimers.multiBall) {
    // Keep only first ball
    if (state.balls.length > 1) state.balls.length = 1;
    state.powerUpTimers.multiBall = 0;
  }
}

// ===== PARTICLES =====
function spawnParticles(x, y, color, count) {
  for (let i = 0; i < count; i++) {
    const angle = rand(0, Math.PI * 2);
    const speed = rand(1, 5);
    state.particles.push({
      x, y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 1, decay: rand(0.015, 0.03),
      size: rand(2, 6),
      color: color
    });
  }
}
function updateParticles() {
  for (let i = state.particles.length - 1; i >= 0; i--) {
    const p = state.particles[i];
    p.x += p.vx;
    p.y += p.vy;
    p.vy += 0.08; // gravity
    p.life -= p.decay;
    if (p.life <= 0) state.particles.splice(i, 1);
  }
}

// ===== COLLISION HANDLING =====
function handleBallBrickCollision(ball) {
  for (let i = state.bricks.length - 1; i >= 0; i--) {
    const brick = state.bricks[i];
    if (brick.destroyed) continue;
    if (circleRectCollide(ball.x, ball.y, ball.radius, brick.x, brick.y, brick.width, brick.height)) {
      // Determine collision side
      const ballLeft = ball.x - ball.radius;
      const ballRight = ball.x + ball.radius;
      const ballTop = ball.y - ball.radius;
      const ballBottom = ball.y + ball.radius;
      const brickLeft = brick.x;
      const brickRight = brick.x + brick.width;
      const brickTop = brick.y;
      const brickBottom = brick.y + brick.height;
      
      const overlapLeft = ballRight - brickLeft;
      const overlapRight = brickRight - ballLeft;
      const overlapTop = ballBottom - brickTop;
      const overlapBottom = brickBottom - ballTop;
      
      const minOverlapX = Math.min(overlapLeft, overlapRight);
      const minOverlapY = Math.min(overlapTop, overlapBottom);
      
      if (minOverlapX < minOverlapY) {
        // Horizontal collision
        ball.vx = -ball.vx;
        if (overlapLeft < overlapRight) ball.x = brickLeft - ball.radius;
        else ball.x = brickRight + ball.radius;
      } else {
        // Vertical collision
        ball.vy = -ball.vy;
        if (overlapTop < overlapBottom) ball.y = brickTop - ball.radius;
        else ball.y = brickBottom + ball.radius;
      }
      
      // Damage brick
      brick.hits--;
      if (brick.hits === 1 && brick.maxHits === 2) {
        // Cracked state
        spawnParticles(ball.x, ball.y, brick.color, 6);
      } else if (brick.hits <= 0) {
        brick.destroyed = true;
        state.score += brick.maxHits === 2 ? 20 : 10;
        spawnParticles(brick.x + brick.width/2, brick.y + brick.height/2, brick.color, CONFIG.particleCount);
        if (brick.hasPowerUp) spawnPowerUp(brick.x + brick.width/2, brick.y + brick.height/2, brick.powerUpType);
      }
      updateScoreDisplay();
      return true;
    }
  }
  return false;
}

function handleBallPaddleCollision(ball) {
  const paddleY = CONFIG.canvasHeight - CONFIG.paddleHeight - 20;
  const paddleTop = paddleY;
  const paddleBottom = paddleY + CONFIG.paddleHeight;
  const paddleLeft = state.paddleX;
  const paddleRight = state.paddleX + state.paddleWidth;
  
  if (ball.y + ball.radius >= paddleTop && ball.y - ball.radius <= paddleBottom &&
      ball.x + ball.radius >= paddleLeft && ball.x - ball.radius <= paddleRight &&
      ball.vy > 0) {
    // Calculate hit position (-1 to 1)
    const hitPos = (ball.x - (state.paddleX + state.paddleWidth / 2)) / (state.paddleWidth / 2);
    const clampedHitPos = clamp(hitPos, -1, 1);
    
    // Angle ranges from -60° to 60° from vertical
    const maxAngle = Math.PI / 3; // 60 degrees
    const angle = clampedHitPos * maxAngle;
    
    const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
    ball.vx = Math.sin(angle) * speed;
    ball.vy = -Math.cos(angle) * speed;
    
    // Ensure ball is above paddle
    ball.y = paddleTop - ball.radius;
    
    // Add slight speed increase over time
    const newSpeed = Math.min(speed * 1.005, CONFIG.ballSpeed * 1.8);
    const currentAngle = Math.atan2(ball.vy, ball.vx);
    ball.vx = Math.cos(currentAngle) * newSpeed;
    ball.vy = Math.sin(currentAngle) * newSpeed;
    
    return true;
  }
  return false;
}

function handleWallCollision(ball) {
  if (ball.x - ball.radius <= 0) {
    ball.x = ball.radius;
    ball.vx = Math.abs(ball.vx);
  } else if (ball.x + ball.radius >= CONFIG.canvasWidth) {
    ball.x = CONFIG.canvasWidth - ball.radius;
    ball.vx = -Math.abs(ball.vx);
  }
  if (ball.y - ball.radius <= 0) {
    ball.y = ball.radius;
    ball.vy = Math.abs(ball.vy);
  }
}

// ===== DEMO AI =====
function updateDemoAI() {
  if (!state.demoMode || state.balls.length === 0) return;
  // Track the lowest ball
  let targetBall = state.balls[0];
  for (const ball of state.balls) {
    if (ball.y > targetBall.y) targetBall = ball;
  }
  // Predict where ball will hit paddle level
  const paddleY = CONFIG.canvasHeight - CONFIG.paddleHeight - 20;
  if (targetBall.vy > 0) {
    const timeToPaddle = (paddleY - targetBall.y) / targetBall.vy;
    let predictedX = targetBall.x + targetBall.vx * timeToPaddle;
    // Account for wall bounces
    while (predictedX < 0 || predictedX > CONFIG.canvasWidth) {
      if (predictedX < 0) predictedX = -predictedX;
      if (predictedX > CONFIG.canvasWidth) predictedX = 2 * CONFIG.canvasWidth - predictedX;
    }
    state.paddleTargetX = predictedX - state.paddleWidth / 2;
  } else {
    state.paddleTargetX = CONFIG.canvasWidth / 2 - state.paddleWidth / 2;
  }
  state.paddleTargetX = clamp(state.paddleTargetX, 0, CONFIG.canvasWidth - state.paddleWidth);
}

// ===== UPDATE =====
function update() {
  if (!state.running) return;
  
  updateDemoAI();
  
  // Paddle movement (smooth follow for demo, direct for player)
  if (state.demoMode) {
    state.paddleX += (state.paddleTargetX - state.paddleX) * 0.15;
  } else {
    if (state.keys.left) state.paddleX -= CONFIG.paddleSpeed;
    if (state.keys.right) state.paddleX += CONFIG.paddleSpeed;
    state.paddleX = clamp(state.paddleX, 0, CONFIG.canvasWidth - state.paddleWidth);
  }
  
  // Update balls
  for (let i = state.balls.length - 1; i >= 0; i--) {
    const ball = state.balls[i];
    
    // Trail
    ball.trail.push({ x: ball.x, y: ball.y });
    if (ball.trail.length > ball.maxTrail) ball.trail.shift();
    
    ball.x += ball.vx;
    ball.y += ball.vy;
    
    handleWallCollision(ball);
    handleBallBrickCollision(ball);
    handleBallPaddleCollision(ball);
    
    // Ball lost
    if (ball.y - ball.radius > CONFIG.canvasHeight) {
      state.balls.splice(i, 1);
    }
  }
  
  // All balls lost
  if (state.balls.length === 0) {
    state.lives--;
    updateLivesDisplay();
    if (state.lives <= 0) {
      gameOver();
    } else {
      // Reset ball after short delay
      setTimeout(() => {
        if (state.running) {
          launchBall(CONFIG.canvasWidth / 2, CONFIG.canvasHeight - CONFIG.paddleHeight - 40);
        }
      }, 1000);
    }
  }
  
  // Power-ups
  for (let i = state.powerUps.length - 1; i >= 0; i--) {
    const pu = state.powerUps[i];
    pu.y += pu.vy;
    pu.rotation += 0.03;
    // Check paddle collision
    const paddleY = CONFIG.canvasHeight - CONFIG.paddleHeight - 20;
    if (pu.y + pu.height >= paddleY && pu.y <= paddleY + CONFIG.paddleHeight &&
        pu.x + pu.width >= state.paddleX && pu.x <= state.paddleX + state.paddleWidth) {
      activatePowerUp(pu.type);
      state.powerUps.splice(i, 1);
    } else if (pu.y > CONFIG.canvasHeight) {
      state.powerUps.splice(i, 1);
    }
  }
  
  updatePowerUpTimers();
  updateParticles();
  
  // Victory check
  if (state.bricks.every(b => b.destroyed)) {
    victory();
  }
}

// ===== RENDER =====
function drawPaddle() {
  const y = CONFIG.canvasHeight - CONFIG.paddleHeight - 20;
  const x = state.paddleX;
  const w = state.paddleWidth;
  const h = CONFIG.paddleHeight;
  const r = 8;
  
  // Glow
  const gradient = ctx.createLinearGradient(x, y, x, y + h);
  gradient.addColorStop(0, '#ff6b35');
  gradient.addColorStop(0.5, '#ff9f43');
  gradient.addColorStop(1, '#e67e22');
  
  ctx.shadowColor = '#ff6b35';
  ctx.shadowBlur = 15;
  ctx.fillStyle = gradient;
  roundRect(ctx, x, y, w, h, r);
  ctx.fill();
  ctx.shadowBlur = 0;
  
  // Highlight
  ctx.fillStyle = 'rgba(255,255,255,0.2)';
  roundRect(ctx, x + 2, y + 2, w - 4, h / 2, r);
  ctx.fill();
}

function drawBall(ball) {
  // Trail
  for (let i = 0; i < ball.trail.length; i++) {
    const t = ball.trail[i];
    const alpha = (i / ball.trail.length) * 0.4;
    const size = ball.radius * (i / ball.trail.length);
    ctx.globalAlpha = alpha;
    ctx.fillStyle = '#ffd700';
    ctx.beginPath();
    ctx.arc(t.x, t.y, size, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
  
  // Ball glow
  ctx.shadowColor = '#ffd700';
  ctx.shadowBlur = 12;
  
  // Ball gradient
  const grad = ctx.createRadialGradient(
    ball.x - ball.radius/3, ball.y - ball.radius/3, 0,
    ball.x, ball.y, ball.radius
  );
  grad.addColorStop(0, '#fff8dc');
  grad.addColorStop(0.5, '#ffd700');
  grad.addColorStop(1, '#e6c200');
  
  ctx.fillStyle = grad;
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
  ctx.fill();
  ctx.shadowBlur = 0;
}

function drawBrick(brick) {
  if (brick.destroyed) return;
  const { x, y, width, height, color, lightColor, darkColor, hits, maxHits, hasPowerUp, powerUpType } = brick;
  const r = 4;
  
  // Base
  const grad = ctx.createLinearGradient(x, y, x, y + height);
  grad.addColorStop(0, lightColor);
  grad.addColorStop(0.5, color);
  grad.addColorStop(1, darkColor);
  ctx.fillStyle = grad;
  roundRect(ctx, x, y, width, height, r);
  ctx.fill();
  
  // Crack effect for 2-hit bricks after first hit
  if (maxHits === 2 && hits === 1) {
    ctx.strokeStyle = 'rgba(0,0,0,0.4)';
    ctx.lineWidth = 2;
    ctx.lineCap = 'round';
    ctx.beginPath();
    // Draw a few crack lines
    const cx = x + width/2, cy = y + height/2;
    for (let i = 0; i < 3; i++) {
      const angle = rand(0, Math.PI * 2);
      const len = rand(width * 0.2, width * 0.4);
      ctx.moveTo(cx, cy);
      ctx.lineTo(cx + Math.cos(angle) * len, cy + Math.sin(angle) * len * 0.5);
    }
    ctx.stroke();
  }
  
  // Highlight
  ctx.fillStyle = 'rgba(255,255,255,0.15)';
  roundRect(ctx, x + 2, y + 2, width - 4, height / 2, r);
  ctx.fill();
  
  // Power-up indicator
  if (hasPowerUp) {
    ctx.save();
    ctx.translate(x + width/2, y + height/2);
    ctx.rotate(Date.now() * 0.002);
    ctx.fillStyle = powerUpType === 'widePaddle' ? '#2ecc71' : '#9b59b6';
    ctx.shadowColor = ctx.fillStyle;
    ctx.shadowBlur = 8;
    // Icon shape
    if (powerUpType === 'widePaddle') {
      // Double arrow
      ctx.beginPath();
      ctx.moveTo(-8, 0); ctx.lineTo(0, -6); ctx.lineTo(0, -2); ctx.lineTo(8, -2); ctx.lineTo(8, 2); ctx.lineTo(0, 2); ctx.lineTo(0, 6); ctx.closePath();
      ctx.fill();
      ctx.beginPath();
      ctx.moveTo(8, 0); ctx.lineTo(0, -6); ctx.lineTo(0, -2); ctx.lineTo(-8, -2); ctx.lineTo(-8, 2); ctx.lineTo(0, 2); ctx.lineTo(0, 6); ctx.closePath();
      ctx.fill();
    } else {
      // Three dots
      for (let i = -1; i <= 1; i++) {
        ctx.beginPath();
        ctx.arc(i * 6, 0, 3, 0, Math.PI * 2);
        ctx.fill();
      }
    }
    ctx.restore();
  }
}

function drawPowerUps() {
  for (const pu of state.powerUps) {
    ctx.save();
    ctx.translate(pu.x + pu.width/2, pu.y + pu.height/2);
    ctx.rotate(pu.rotation);
    const grad = ctx.createRadialGradient(0, 0, 0, 0, 0, pu.width/2);
    const color = pu.type === 'widePaddle' ? '#2ecc71' : '#9b59b6';
    grad.addColorStop(0, color);
    grad.addColorStop(1, pu.type === 'widePaddle' ? '#27ae60' : '#8e44ad');
    ctx.fillStyle = grad;
    ctx.shadowColor = color;
    ctx.shadowBlur = 10;
    roundRect(ctx, -pu.width/2, -pu.height/2, pu.width, pu.height, 6);
    ctx.fill();
    ctx.shadowBlur = 0;
    // Icon
    ctx.fillStyle = 'rgba(255,255,255,0.9)';
    if (pu.type === 'widePaddle') {
      ctx.fillRect(-7, -2, 14, 4);
      ctx.fillRect(-5, -5, 2, 10);
      ctx.fillRect(3, -5, 2, 10);
    } else {
      for (let i = -1; i <= 1; i++) {
        ctx.beginPath();
        ctx.arc(i * 5, 0, 2.5, 0, Math.PI * 2);
        ctx.fill();
      }
    }
    ctx.restore();
  }
}

function drawParticles() {
  for (const p of state.particles) {
    ctx.globalAlpha = p.life;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
}

function drawBackground() {
  // Subtle grid pattern
  ctx.strokeStyle = 'rgba(255,255,255,0.02)';
  ctx.lineWidth = 1;
  for (let x = 0; x <= CONFIG.canvasWidth; x += 40) {
    ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, CONFIG.canvasHeight); ctx.stroke();
  }
  for (let y = 0; y <= CONFIG.canvasHeight; y += 40) {
    ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(CONFIG.canvasWidth, y); ctx.stroke();
  }
}

function render() {
  // Clear
  ctx.fillStyle = '#0a0a15';
  ctx.fillRect(0, 0, CONFIG.canvasWidth, CONFIG.canvasHeight);
  
  drawBackground();
  drawParticles();
  
  for (const brick of state.bricks) drawBrick(brick);
  drawPowerUps();
  drawPaddle();
  for (const ball of state.balls) drawBall(ball);
  
  // Power-up indicators
  if (state.powerUpTimers.widePaddle) drawPowerUpIndicator('widePaddle', state.powerUpTimers.widePaddle);
  if (state.powerUpTimers.multiBall) drawPowerUpIndicator('multiBall', state.powerUpTimers.multiBall);
}

function drawPowerUpIndicator(type, endTime) {
  const remaining = (endTime - Date.now()) / CONFIG.powerUpDuration;
  if (remaining <= 0) return;
  const x = type === 'widePaddle' ? 20 : 20;
  const y = type === 'widePaddle' ? 20 : 50;
  ctx.fillStyle = type === 'widePaddle' ? '#2ecc71' : '#9b59b6';
  ctx.globalAlpha = 0.7 + 0.3 * Math.sin(Date.now() * 0.005);
  roundRect(ctx, x, y, 120 * remaining, 8, 4);
  ctx.fill();
  ctx.globalAlpha = 1;
}

function roundRect(ctx, x, y, w, h, r) {
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.lineTo(x + w - r, y);
  ctx.quadraticCurveTo(x + w, y, x + w, y + r);
  ctx.lineTo(x + w, y + h - r);
  ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
  ctx.lineTo(x + r, y + h);
  ctx.quadraticCurveTo(x, y + h, x, y + h - r);
  ctx.lineTo(x, y + r);
  ctx.quadraticCurveTo(x, y, x + r, y);
  ctx.closePath();
}

// ===== UI UPDATES =====
function updateScoreDisplay() {
  document.getElementById('scoreDisplay').textContent = state.score;
}
function updateLivesDisplay() {
  const container = document.getElementById('livesDisplay');
  container.innerHTML = '';
  for (let i = 0; i < CONFIG.lives; i++) {
    const div = document.createElement('div');
    div.className = 'life' + (i >= state.lives ? ' lost' : '');
    container.appendChild(div);
  }
}

// ===== GAME FLOW =====
function startGame() {
  state.running = true;
  state.demoMode = false;
  state.score = 0;
  state.lives = CONFIG.lives;
  state.level = 1;
  state.paddleWidth = CONFIG.paddleWidth;
  state.paddleX = (CONFIG.canvasWidth - state.paddleWidth) / 2;
  state.paddleTargetX = state.paddleX;
  state.powerUpTimers = { widePaddle: 0, multiBall: 0 };
  state.balls = [];
  state.powerUps = [];
  state.particles = [];
  createBricks();
  launchBall(CONFIG.canvasWidth / 2, CONFIG.canvasHeight - CONFIG.paddleHeight - 40);
  updateScoreDisplay();
  updateLivesDisplay();
  document.getElementById('levelDisplay').textContent = state.level;
  hideOverlays();
}

function startDemo() {
  state.running = true;
  state.demoMode = true;
  state.score = 0;
  state.lives = CONFIG.lives;
  state.level = 1;
  state.paddleWidth = CONFIG.paddleWidth;
  state.paddleX = (CONFIG.canvasWidth - state.paddleWidth) / 2;
  state.paddleTargetX = state.paddleX;
  state.powerUpTimers = { widePaddle: 0, multiBall: 0 };
  state.balls = [];
  state.powerUps = [];
  state.particles = [];
  createBricks();
  launchBall(CONFIG.canvasWidth / 2, CONFIG.canvasHeight - CONFIG.paddleHeight - 40);
  updateScoreDisplay();
  updateLivesDisplay();
  document.getElementById('levelDisplay').textContent = state.level;
  hideOverlays();
}

function hideOverlays() {
  document.getElementById('startOverlay').classList.add('hidden');
  document.getElementById('gameOverOverlay').classList.add('hidden');
  document.getElementById('victoryOverlay').classList.add('hidden');
}

function showOverlay(id) {
  document.getElementById(id).classList.remove('hidden');
}

function gameOver() {
  state.running = false;
  state.demoMode = false;
  document.getElementById('finalScore').textContent = `Final Score: ${state.score}`;
  showOverlay('gameOverOverlay');
}

function victory() {
  state.running = false;
  state.demoMode = false;
  document.getElementById('victoryScore').textContent = `Final Score: ${state.score}`;
  showOverlay('victoryOverlay');
}

// ===== INPUT =====
window.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowLeft') state.keys.left = true;
  if (e.key === 'ArrowRight') state.keys.right = true;
  if (!state.running && (e.key === ' ' || e.key === 'Enter')) {
    if (state.demoMode) startGame();
  }
});
window.addEventListener('keyup', (e) => {
  if (e.key === 'ArrowLeft') state.keys.left = false;
  if (e.key === 'ArrowRight') state.keys.right = false;
});

canvas.addEventListener('mousemove', (e) => {
  if (!state.running || state.demoMode) return;
  const rect = canvas.getBoundingClientRect();
  const scaleX = CONFIG.canvasWidth / rect.width;
  state.mouseX = (e.clientX - rect.left) * scaleX;
  state.paddleX = clamp(state.mouseX - state.paddleWidth / 2, 0, CONFIG.canvasWidth - state.paddleWidth);
});

canvas.addEventListener('click', () => {
  if (state.demoMode) startGame();
});

document.getElementById('startBtn').addEventListener('click', startGame);
document.getElementById('restartBtn').addEventListener('click', startGame);
document.getElementById('victoryRestartBtn').addEventListener('click', startGame);

// Any key to start from demo
window.addEventListener('keydown', (e) => {
  if (state.demoMode && state.running) {
    startGame();
  }
});

// ===== MAIN LOOP =====
function gameLoop() {
  update();
  render();
  requestAnimationFrame(gameLoop);
}

// Start demo mode immediately
startDemo();
gameLoop();
</script>
</body>
</html>
Reasoning trace (325 tokens)
The user wants a polished Breakout game in a single HTML file. Let me break down the requirements:

1. Paddle controlled by mouse AND left/right arrow keys
2. Bounce angle depends on hit position on paddle
3. One level with at least 6 rows of bricks, mixing normal (1 hit) and tougher (2 hits with visible crack) bricks
4. Some bricks have power-up icons that drop when broken:
   - Wider paddle (10 seconds)
   - Multi-ball (3 balls)
5. Ball must never pass through bricks or paddle (proper collision detection)
6. Particles when brick breaks
7. 3 lives, score display
8. Game over and victory screens with restart option
9. Self-playing demo mode behind "Click or press a key to start" message
10. Must work in 1280x800 and adapt to 375px wide
11. No horizontal scrollbar
12. All inline CSS/JS, no external resources
13. No localStorage, alert, etc.

Let me design this carefully.

Key technical considerations:
- Canvas rendering
- Game loop with requestAnimationFrame
- Proper collision detection (AABB for paddle, circle-rect for ball-brick)
- Ball physics with angle based on paddle hit position
- Power-up system
- Particle system
- State management (menu, playing, game over, victory)
- Demo AI for self-playing mode
- Responsive design

I'll create a complete, polished implementation.