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 17 s
Cost
$0
Output tokens
7,310
Spent thinking
0 tokens
Speed
164.1 tok/s
Console errors
0
First token after
1 min 32 s
First visible text after
1 min 32 s
Prompt tokens
531
Code size
20.8 KB
Finish reason
stop
Served by
Nvidia
Model id
nvidia/nemotron-3-ultra-550b-a55b:free
Reasoning sent
{"effort":"none"}
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 · 732 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 { 
  background: #0d0d1a; 
  display: flex; 
  flex-direction: column; 
  align-items: center; 
  justify-content: center; 
  min-height: 100vh; 
  font-family: 'Segoe UI', system-ui, sans-serif; 
  color: #fff;
  overflow: hidden;
}
#gameCanvas { 
  display: block; 
  border-radius: 8px; 
  box-shadow: 0 0 30px rgba(0,200,255,0.3), 0 20px 40px rgba(0,0,0,0.5);
  background: #111;
}
#ui {
  display: flex;
  justify-content: space-between;
  width: 100%;
  max-width: 800px;
  padding: 12px 20px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.5px;
}
#ui .stat { display: flex; align-items: center; gap: 8px; }
#ui .lives { color: #ff4757; }
#ui .score { color: #ffd700; }
#ui .level { color: #2ed573; }
#overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(13, 13, 26, 0.92);
  backdrop-filter: blur(4px);
  border-radius: 8px;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
#overlay.hidden { opacity: 0; pointer-events: none; }
#overlay h1 { 
  font-size: 48px; 
  font-weight: 800; 
  background: linear-gradient(135deg, #00c8ff, #ff6b6b, #ffd700);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  margin-bottom: 8px;
  text-shadow: 0 0 40px rgba(0,200,255,0.5);
}
#overlay p { color: #aaa; margin-bottom: 24px; font-size: 18px; }
#overlay button {
  padding: 14px 40px;
  font-size: 18px;
  font-weight: 700;
  border: none;
  border-radius: 50px;
  background: linear-gradient(135deg, #00c8ff, #007bff);
  color: #fff;
  cursor: pointer;
  transition: transform 0.2s, box-shadow 0.2s;
  box-shadow: 0 4px 20px rgba(0,200,255,0.4);
}
#overlay button:hover { transform: translateY(-2px); box-shadow: 0 6px 30px rgba(0,200,255,0.6); }
#overlay button:active { transform: translateY(0); }
.powerup-icon { font-size: 14px; }
@media (max-width: 850px) {
  #gameCanvas { width: 100vw; height: auto; max-height: 70vh; border-radius: 0; }
  #ui { max-width: 100vw; padding: 10px 16px; font-size: 14px; }
  #overlay h1 { font-size: 36px; }
  #overlay p { font-size: 16px; }
}
</style>
</head>
<body>
<div id="ui">
  <div class="stat lives">LIVES: <span id="livesDisplay">3</span></div>
  <div class="stat score">SCORE: <span id="scoreDisplay">0</span></div>
  <div class="stat level">LEVEL: <span id="levelDisplay">1</span></div>
</div>
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div id="overlay">
  <h1 id="overlayTitle">BREAKOUT</h1>
  <p id="overlayText">Click or press a key to start</p>
  <button id="startBtn">START GAME</button>
</div>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;

const paddle = { x: W/2 - 50, y: H - 40, w: 100, h: 14, baseW: 100, color: '#00c8ff', speed: 0, wideUntil: 0 };
const balls = [];
const bricks = [];
const particles = [];
const powerups = [];

let score = 0;
let lives = 3;
let gameState = 'start'; // start, playing, paused, gameover, victory
let animationId = null;
let keys = { left: false, right: false };
let mouseX = W/2;
let aiMode = true;
let lastTime = 0;

// Brick types
const BRICK_TYPES = {
  normal: { hits: 1, color: '#ff6b6b', points: 10, powerupChance: 0.15 },
  tough: { hits: 2, color: '#ffa502', points: 25, powerupChance: 0.25, crackedColor: '#cc8400' }
};

const POWERUP_TYPES = {
  wide: { color: '#2ed573', icon: '▲', label: 'WIDE PADDLE', duration: 10000 },
  multiball: { color: '#ff4757', icon: '●', label: 'MULTI-BALL', duration: 0 }
};

function createBricks() {
  bricks.length = 0;
  const rows = 6;
  const cols = 10;
  const brickW = 70;
  const brickH = 24;
  const padding = 6;
  const offsetX = (W - (cols * brickW + (cols-1)*padding)) / 2;
  const offsetY = 60;
  
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      const isTough = (r < 2) || (r === 2 && (c === 0 || c === cols-1)) || (Math.random() < 0.15 && r > 1);
      const type = isTough ? 'tough' : 'normal';
      const hasPowerup = Math.random() < BRICK_TYPES[type].powerupChance;
      const powerupType = hasPowerup ? (Math.random() < 0.6 ? 'wide' : 'multiball') : null;
      
      bricks.push({
        x: offsetX + c * (brickW + padding),
        y: offsetY + r * (brickH + padding),
        w: brickW,
        h: brickH,
        type,
        hitsLeft: BRICK_TYPES[type].hits,
        maxHits: BRICK_TYPES[type].hits,
        color: BRICK_TYPES[type].color,
        points: BRICK_TYPES[type].points,
        powerup: powerupType,
        cracked: false
      });
    }
  }
}

function createBall(x, y, vx, vy) {
  return {
    x, y,
    r: 8,
    vx, vy,
    baseSpeed: 5.5,
    trail: [],
    maxTrail: 8
  };
}

function resetBall(ball) {
  ball.x = paddle.x + paddle.w / 2;
  ball.y = paddle.y - 10;
  ball.vx = (Math.random() - 0.5) * 4;
  ball.vy = -ball.baseSpeed;
  ball.trail = [];
}

function spawnPowerup(x, y, type) {
  powerups.push({
    x, y,
    w: 24, h: 24,
    vy: 2,
    type,
    rotation: 0,
    bobOffset: Math.random() * Math.PI * 2
  });
}

function spawnParticles(x, y, color, count = 12) {
  for (let i = 0; i < count; i++) {
    const angle = (Math.PI * 2 / count) * i + Math.random() * 0.5;
    const speed = 2 + Math.random() * 4;
    particles.push({
      x, y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      radius: 3 + Math.random() * 3,
      color,
      life: 1,
      decay: 0.015 + Math.random() * 0.02
    });
  }
}

function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }

function updatePaddle(dt) {
  if (aiMode && gameState === 'playing') {
    // Simple AI: track the first ball
    const targetBall = balls[0];
    if (targetBall) {
      const targetX = targetBall.x - paddle.w / 2;
      const diff = targetX - paddle.x;
      paddle.x += diff * 0.08;
    }
  } else {
    if (keys.left) paddle.x -= 10 * dt * 60;
    if (keys.right) paddle.x += 10 * dt * 60;
    // Mouse control (when not AI)
    if (!keys.left && !keys.right && mouseX !== undefined) {
      paddle.x += (mouseX - paddle.w/2 - paddle.x) * 0.3;
    }
  }
  paddle.x = clamp(paddle.x, 0, W - paddle.w);
  
  // Check wide paddle timeout
  if (paddle.wideUntil > 0 && Date.now() > paddle.wideUntil) {
    paddle.w = paddle.baseW;
    paddle.wideUntil = 0;
  }
}

function updateBalls(dt) {
  for (let i = balls.length - 1; i >= 0; i--) {
    const ball = balls[i];
    
    // Trail
    ball.trail.unshift({ x: ball.x, y: ball.y });
    if (ball.trail.length > ball.maxTrail) ball.trail.pop();
    
    ball.x += ball.vx * dt * 60;
    ball.y += ball.vy * dt * 60;
    
    // Wall collisions
    if (ball.x - ball.r <= 0) { ball.x = ball.r; ball.vx = Math.abs(ball.vx); }
    if (ball.x + ball.r >= W) { ball.x = W - ball.r; ball.vx = -Math.abs(ball.vx); }
    if (ball.y - ball.r <= 0) { ball.y = ball.r; ball.vy = Math.abs(ball.vy); }
    
    // Paddle collision
    if (ball.y + ball.r >= paddle.y && ball.y - ball.r <= paddle.y + paddle.h &&
        ball.x + ball.r >= paddle.x && ball.x - ball.r <= paddle.x + paddle.w) {
      
      const hitPos = (ball.x - paddle.x) / paddle.w; // 0 to 1
      const angle = (hitPos - 0.5) * Math.PI * 0.75; // -67.5 to 67.5 degrees
      const speed = Math.sqrt(ball.vx*ball.vx + ball.vy*ball.vy);
      
      ball.vx = Math.sin(angle) * speed;
      ball.vy = -Math.abs(Math.cos(angle) * speed);
      ball.y = paddle.y - ball.r;
      
      // Add slight speed increase
      const newSpeed = Math.min(speed * 1.005, 14);
      const currentSpeed = Math.sqrt(ball.vx*ball.vx + ball.vy*ball.vy);
      if (currentSpeed > 0) {
        ball.vx = (ball.vx / currentSpeed) * newSpeed;
        ball.vy = (ball.vy / currentSpeed) * newSpeed;
      }
    }
    
    // Brick collisions
    for (let j = bricks.length - 1; j >= 0; j--) {
      const brick = bricks[j];
      if (ball.x + ball.r > brick.x && ball.x - ball.r < brick.x + brick.w &&
          ball.y + ball.r > brick.y && ball.y - ball.r < brick.y + brick.h) {
        
        // Determine collision side
        const overlapX = Math.min(ball.x + ball.r, brick.x + brick.w) - Math.max(ball.x - ball.r, brick.x);
        const overlapY = Math.min(ball.y + ball.r, brick.y + brick.h) - Math.max(ball.y - ball.r, brick.y);
        
        if (overlapX < overlapY) {
          // Horizontal collision
          if (ball.x < brick.x + brick.w/2) { ball.x = brick.x - ball.r; ball.vx = -Math.abs(ball.vx); }
          else { ball.x = brick.x + brick.w + ball.r; ball.vx = Math.abs(ball.vx); }
        } else {
          // Vertical collision
          if (ball.y < brick.y + brick.h/2) { ball.y = brick.y - ball.r; ball.vy = -Math.abs(ball.vy); }
          else { ball.y = brick.y + brick.h + ball.r; ball.vy = Math.abs(ball.vy); }
        }
        
        // Damage brick
        brick.hitsLeft--;
        if (brick.type === 'tough' && brick.hitsLeft === 1) {
          brick.cracked = true;
        }
        
        if (brick.hitsLeft <= 0) {
          score += brick.points;
          updateScore();
          spawnParticles(brick.x + brick.w/2, brick.y + brick.h/2, brick.color, 16);
          
          if (brick.powerup) {
            spawnPowerup(brick.x + brick.w/2, brick.y + brick.h/2, brick.powerup);
          }
          
          bricks.splice(j, 1);
        }
        break; // Only one brick collision per frame
      }
    }
    
    // Ball lost
    if (ball.y - ball.r > H) {
      balls.splice(i, 1);
      if (balls.length === 0) {
        loseLife();
      }
    }
  }
}

function updatePowerups(dt) {
  for (let i = powerups.length - 1; i >= 0; i--) {
    const p = powerups[i];
    p.y += p.vy * dt * 60;
    p.rotation += 0.02 * dt * 60;
    p.bobOffset += 0.05 * dt * 60;
    
    // Paddle collision
    if (p.y + p.h >= paddle.y && p.y <= paddle.y + paddle.h &&
        p.x + p.w >= paddle.x && p.x <= paddle.x + paddle.w) {
      applyPowerup(p.type);
      powerups.splice(i, 1);
    } else if (p.y > H) {
      powerups.splice(i, 1);
    }
  }
}

function applyPowerup(type) {
  const pu = POWERUP_TYPES[type];
  if (type === 'wide') {
    paddle.w = paddle.baseW * 1.5;
    paddle.wideUntil = Date.now() + pu.duration;
  } else if (type === 'multiball') {
    const mainBall = balls[0];
    if (mainBall) {
      const speed = Math.sqrt(mainBall.vx*mainBall.vx + mainBall.vy*mainBall.vy);
      for (let i = 0; i < 2; i++) {
        const angle = (Math.random() - 0.5) * Math.PI * 0.5;
        const vx = Math.sin(angle) * speed;
        const vy = -Math.abs(Math.cos(angle) * speed);
        balls.push(createBall(mainBall.x, mainBall.y, vx, vy));
      }
    }
  }
  // Visual feedback
  spawnParticles(paddle.x + paddle.w/2, paddle.y, pu.color, 20);
}

function updateParticles(dt) {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.x += p.vx * dt * 60;
    p.y += p.vy * dt * 60;
    p.vy += 0.15 * dt * 60; // gravity
    p.life -= p.decay * dt * 60;
    p.radius *= 0.985;
    if (p.life <= 0 || p.radius < 0.5) particles.splice(i, 1);
  }
}

function loseLife() {
  lives--;
  updateLives();
  if (lives <= 0) {
    gameState = 'gameover';
    showOverlay('GAME OVER', `Final Score: ${score}`, 'TRY AGAIN');
  } else {
    resetBall(balls[0] || createBall(0,0,0,0));
    if (balls.length === 0) balls.push(createBall(0,0,0,0));
    paddle.w = paddle.baseW;
    paddle.wideUntil = 0;
    paddle.x = W/2 - paddle.w/2;
  }
}

function checkVictory() {
  if (bricks.length === 0) {
    gameState = 'victory';
    showOverlay('VICTORY!', `Score: ${score} | Lives: ${lives}`, 'PLAY AGAIN');
  }
}

function updateScore() { document.getElementById('scoreDisplay').textContent = score; }
function updateLives() { document.getElementById('livesDisplay').textContent = lives; }

function showOverlay(title, text, btnText) {
  document.getElementById('overlayTitle').textContent = title;
  document.getElementById('overlayText').textContent = text;
  document.getElementById('startBtn').textContent = btnText;
  document.getElementById('overlay').classList.remove('hidden');
}

function hideOverlay() {
  document.getElementById('overlay').classList.add('hidden');
}

function startGame() {
  if (gameState === 'playing') return;
  
  score = 0;
  lives = 3;
  updateScore();
  updateLives();
  createBricks();
  
  balls.length = 0;
  balls.push(createBall(W/2, paddle.y - 10, 0, 0));
  resetBall(balls[0]);
  
  particles.length = 0;
  powerups.length = 0;
  
  paddle.w = paddle.baseW;
  paddle.wideUntil = 0;
  paddle.x = W/2 - paddle.w/2;
  
  gameState = 'playing';
  aiMode = false;
  hideOverlay();
  lastTime = performance.now();
  requestAnimationFrame(gameLoop);
}

function togglePause() {
  if (gameState === 'playing') {
    gameState = 'paused';
    showOverlay('PAUSED', 'Press P or click to resume', 'RESUME');
  } else if (gameState === 'paused') {
    gameState = 'playing';
    hideOverlay();
    lastTime = performance.now();
    requestAnimationFrame(gameLoop);
  }
}

function gameLoop(time) {
  if (gameState !== 'playing') return;
  
  const dt = (time - lastTime) / 1000;
  lastTime = time;
  
  updatePaddle(dt);
  updateBalls(dt);
  updatePowerups(dt);
  updateParticles(dt);
  checkVictory();
  
  render();
  
  requestAnimationFrame(gameLoop);
}

function render() {
  // Clear with slight trail effect
  ctx.fillStyle = 'rgba(17, 17, 27, 0.3)';
  ctx.fillRect(0, 0, W, H);
  
  // Background grid
  ctx.strokeStyle = 'rgba(0, 200, 255, 0.03)';
  ctx.lineWidth = 1;
  for (let x = 0; x <= W; x += 40) {
    ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
  }
  for (let y = 0; y <= H; y += 40) {
    ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
  }
  
  // Bricks
  for (const brick of bricks) {
    const bt = BRICK_TYPES[brick.type];
    const ratio = brick.hitsLeft / brick.maxHits;
    
    // Glow
    ctx.shadowColor = brick.color;
    ctx.shadowBlur = 8;
    
    // Main brick
    const grad = ctx.createLinearGradient(brick.x, brick.y, brick.x, brick.y + brick.h);
    grad.addColorStop(0, brick.color);
    grad.addColorStop(1, bt.crackedColor || brick.color);
    ctx.fillStyle = grad;
    roundRect(ctx, brick.x, brick.y, brick.w, brick.h, 4);
    ctx.fill();
    
    // Crack effect for tough bricks
    if (brick.cracked) {
      ctx.strokeStyle = bt.crackedColor;
      ctx.lineWidth = 2;
      ctx.globalAlpha = 0.6;
      ctx.beginPath();
      ctx.moveTo(brick.x + 5, brick.y + brick.h - 5);
      ctx.lineTo(brick.x + brick.w - 5, brick.y + 5);
      ctx.moveTo(brick.x + brick.w/2, brick.y + 3);
      ctx.lineTo(brick.x + brick.w/2 - 8, brick.y + brick.h - 3);
      ctx.moveTo(brick.x + brick.w/2, brick.y + 3);
      ctx.lineTo(brick.x + brick.w/2 + 8, brick.y + brick.h - 3);
      ctx.stroke();
      ctx.globalAlpha = 1;
    }
    
    // Powerup indicator
    if (brick.powerup) {
      const pu = POWERUP_TYPES[brick.powerup];
      ctx.fillStyle = pu.color;
      ctx.font = 'bold 14px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(pu.icon, brick.x + brick.w/2, brick.y + brick.h/2 + 1);
    }
    
    ctx.shadowBlur = 0;
  }
  
  // Powerups
  for (const p of powerups) {
    const pu = POWERUP_TYPES[p.type];
    const bob = Math.sin(p.bobOffset) * 4;
    
    ctx.save();
    ctx.translate(p.x + p.w/2, p.y + p.h/2 + bob);
    ctx.rotate(p.rotation);
    
    // Glow
    ctx.shadowColor = pu.color;
    ctx.shadowBlur = 15;
    
    // Box
    const grad = ctx.createRadialGradient(0, 0, 0, 0, 0, p.w/2);
    grad.addColorStop(0, pu.color);
    grad.addColorStop(1, '#1a1a2e');
    ctx.fillStyle = grad;
    roundRect(ctx, -p.w/2, -p.h/2, p.w, p.h, 4);
    ctx.fill();
    
    // Icon
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 16px Arial';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(pu.icon, 0, 1);
    
    ctx.restore();
  }
  
  // Particles
  for (const p of particles) {
    ctx.globalAlpha = p.life;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
  
  // Balls with trails
  for (const ball of balls) {
    // Trail
    for (let i = ball.trail.length - 1; i >= 0; i--) {
      const t = ball.trail[i];
      const alpha = (1 - i / ball.trail.length) * 0.4;
      const radius = ball.r * (1 - i / ball.trail.length * 0.5);
      ctx.globalAlpha = alpha;
      ctx.fillStyle = '#00c8ff';
      ctx.beginPath();
      ctx.arc(t.x, t.y, radius, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;
    
    // Ball glow
    ctx.shadowColor = '#00c8ff';
    ctx.shadowBlur = 15;
    
    const grad = ctx.createRadialGradient(ball.x, ball.y, 0, ball.x, ball.y, ball.r);
    grad.addColorStop(0, '#fff');
    grad.addColorStop(0.5, '#00c8ff');
    grad.addColorStop(1, '#007bff');
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fill();
    ctx.shadowBlur = 0;
  }
  
  // Paddle
  ctx.shadowColor = paddle.color;
  ctx.shadowBlur = 10;
  
  const pGrad = ctx.createLinearGradient(paddle.x, paddle.y, paddle.x, paddle.y + paddle.h);
  pGrad.addColorStop(0, '#4df0ff');
  pGrad.addColorStop(1, paddle.color);
  ctx.fillStyle = pGrad;
  roundRect(ctx, paddle.x, paddle.y, paddle.w, paddle.h, 6);
  ctx.fill();
  
  // Paddle highlight
  ctx.fillStyle = 'rgba(255,255,255,0.2)';
  roundRect(ctx, paddle.x + 2, paddle.y + 2, paddle.w - 4, 4, 3);
  ctx.fill();
  
  ctx.shadowBlur = 0;
  
  // Wide paddle indicator
  if (paddle.wideUntil > 0) {
    const remaining = (paddle.wideUntil - Date.now()) / 10000;
    ctx.fillStyle = 'rgba(46, 213, 115, 0.5)';
    ctx.fillRect(paddle.x, paddle.y - 4, paddle.w * remaining, 3);
  }
}

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();
}

// Input handling
document.addEventListener('keydown', e => {
  if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = true;
  if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = true;
  if (e.key === ' ' || e.key === 'Enter') {
    e.preventDefault();
    if (gameState === 'start' || gameState === 'gameover' || gameState === 'victory') startGame();
    else if (gameState === 'playing' || gameState === 'paused') togglePause();
  }
  if (e.key === 'p' || e.key === 'P') togglePause();
});

document.addEventListener('keyup', e => {
  if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = false;
  if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = false;
});

canvas.addEventListener('mousemove', e => {
  const rect = canvas.getBoundingClientRect();
  mouseX = e.clientX - rect.left;
  if (gameState === 'start') { aiMode = false; }
});

canvas.addEventListener('click', e => {
  if (gameState === 'start' || gameState === 'gameover' || gameState === 'victory') {
    startGame();
  } else if (gameState === 'paused') {
    togglePause();
  }
  aiMode = false;
});

// Touch support
canvas.addEventListener('touchmove', e => {
  e.preventDefault();
  const rect = canvas.getBoundingClientRect();
  mouseX = e.touches[0].clientX - rect.left;
  if (gameState === 'start') { aiMode = false; }
}, { passive: false });

canvas.addEventListener('touchstart', e => {
  if (gameState === 'start' || gameState === 'gameover' || gameState === 'victory') {
    startGame();
  } else if (gameState === 'paused') {
    togglePause();
  }
  aiMode = false;
}, { passive: true });

// Initial render for start screen
function renderStart() {
  ctx.fillStyle = '#111';
  ctx.fillRect(0, 0, W, H);
  // Draw some demo bricks
  createBricks();
  for (const brick of bricks.slice(0, 20)) {
    const bt = BRICK_TYPES[brick.type];
    const grad = ctx.createLinearGradient(brick.x, brick.y, brick.x, brick.y + brick.h);
    grad.addColorStop(0, brick.color);
    grad.addColorStop(1, bt.crackedColor || brick.color);
    ctx.fillStyle = grad;
    roundRect(ctx, brick.x, brick.y, brick.w, brick.h, 4);
    ctx.fill();
  }
  bricks.length = 0;
  
  // Demo paddle
  ctx.fillStyle = '#00c8ff';
  roundRect(ctx, paddle.x, paddle.y, paddle.w, paddle.h, 6);
  ctx.fill();
  
  // Demo ball
  ctx.shadowColor = '#00c8ff';
  ctx.shadowBlur = 15;
  const grad = ctx.createRadialGradient(W/2, H/2 - 100, 0, W/2, H/2 - 100, 8);
  grad.addColorStop(0, '#fff');
  grad.addColorStop(0.5, '#00c8ff');
  grad.addColorStop(1, '#007bff');
  ctx.fillStyle = grad;
  ctx.beginPath();
  ctx.arc(W/2, H/2 - 100, 8, 0, Math.PI * 2);
  ctx.fill();
  ctx.shadowBlur = 0;
}

renderStart();
</script>
</body>
</html>