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
6 min 14 s
Cost
$0
Output tokens
9,259
Spent thinking
218 tokens
Speed
80.3 tok/s
Console errors
0
First token after
4 min 19 s
First visible text after
4 min 39 s
Prompt tokens
378
Code size
24.6 KB
Finish reason
stop
Served by
Nvidia
Model id
nvidia/nemotron-3-ultra-550b-a55b:free
Reasoning sent
{"effort":"medium"}
Max output
32,000 tokens
Generated
Nov 9, 2026

What it was asked

The prompt
“i want a breakout game where the paddle is a hamburger and every brick you break explodes into tons of little hearts”
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 · 885 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Burger Breakout</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-family: 'Segoe UI', system-ui, sans-serif;
    overflow: hidden;
    touch-action: none;
  }
  canvas {
    display: block;
    border-radius: 12px;
    box-shadow: 
      0 20px 60px rgba(0,0,0,0.5),
      0 0 0 4px rgba(255, 100, 150, 0.2),
      inset 0 0 60px rgba(255, 100, 150, 0.1);
    background: rgba(0,0,0,0.3);
  }
  .ui {
    position: fixed;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 32px;
    color: #fff;
    text-shadow: 0 2px 8px rgba(0,0,0,0.8);
    z-index: 10;
    pointer-events: none;
  }
  .stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
  }
  .stat-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: #ff6496;
    opacity: 0.8;
  }
  .stat-value {
    font-size: 24px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
  }
  .start-overlay {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(0,0,0,0.85);
    backdrop-filter: blur(8px);
    z-index: 100;
    transition: opacity 0.3s ease, pointer-events 0.3s ease;
  }
  .start-overlay.hidden {
    opacity: 0;
    pointer-events: none;
  }
  .start-title {
    font-size: clamp(32px, 8vw, 64px);
    font-weight: 800;
    background: linear-gradient(135deg, #ff6496, #ffb347, #ff6496);
    background-size: 200% 200%;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    animation: gradientShift 3s ease infinite;
    margin-bottom: 16px;
    text-shadow: none;
  }
  @keyframes gradientShift {
    0%, 100% { background-position: 0% 50%; }
    50% { background-position: 100% 50%; }
  }
  .start-subtitle {
    font-size: clamp(14px, 3vw, 18px);
    color: rgba(255,255,255,0.7);
    margin-bottom: 32px;
    text-align: center;
    max-width: 80%;
    line-height: 1.5;
  }
  .start-btn {
    padding: 16px 48px;
    font-size: 18px;
    font-weight: 600;
    color: #1a1a2e;
    background: linear-gradient(135deg, #ff6496, #ff9a56);
    border: none;
    border-radius: 50px;
    cursor: pointer;
    box-shadow: 0 8px 30px rgba(255, 100, 150, 0.4);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
  }
  .start-btn:active { transform: scale(0.96); }
  .start-btn:hover { box-shadow: 0 12px 40px rgba(255, 100, 150, 0.6); }
  .lives-display {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    z-index: 10;
    pointer-events: none;
  }
  .life-icon {
    width: 28px;
    height: 28px;
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));
    transition: transform 0.2s ease;
  }
  .life-icon.lost { opacity: 0.2; transform: scale(0.7); }
  @media (max-width: 480px) {
    .ui { top: 8px; gap: 16px; }
    .stat-value { font-size: 18px; }
    .lives-display { bottom: 12px; }
    .life-icon { width: 22px; height: 22px; }
  }
</style>
</head>
<body>
<div class="ui">
  <div class="stat">
    <div class="stat-label">Score</div>
    <div class="stat-value" id="score">0</div>
  </div>
  <div class="stat">
    <div class="stat-label">Level</div>
    <div class="stat-value" id="level">1</div>
  </div>
  <div class="stat">
    <div class="stat-label">Bricks</div>
    <div class="stat-value" id="bricks">0</div>
  </div>
</div>
<div class="lives-display" id="livesDisplay"></div>
<canvas id="game"></canvas>
<div class="start-overlay" id="startOverlay">
  <div class="start-title">BURGER BREAKOUT</div>
  <div class="start-subtitle">Move the burger to bounce the ball.<br>Break bricks to release showers of love!</div>
  <button class="start-btn" id="startBtn">Click or Press Any Key to Start</button>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const startOverlay = document.getElementById('startOverlay');
const startBtn = document.getElementById('startBtn');
const scoreEl = document.getElementById('score');
const levelEl = document.getElementById('level');
const bricksEl = document.getElementById('bricks');
const livesDisplay = document.getElementById('livesDisplay');

let width, height, dpr;
function resize() {
  dpr = Math.min(window.devicePixelRatio || 1, 2);
  const maxW = Math.min(1280, window.innerWidth - 24);
  const maxH = Math.min(800, window.innerHeight - 24);
  const aspect = 16 / 10;
  if (maxW / maxH > aspect) {
    width = Math.floor(maxH * aspect);
    height = maxH;
  } else {
    width = maxW;
    height = Math.floor(maxW / aspect);
  }
  canvas.width = width * dpr;
  canvas.height = height * dpr;
  canvas.style.width = width + 'px';
  canvas.style.height = height + 'px';
  ctx.scale(dpr, dpr);
}
window.addEventListener('resize', resize);
resize();

// Game state
let running = false;
let demoMode = true;
let score = 0;
let level = 1;
let lives = 3;
let bricks = [];
let particles = [];
let screenShake = 0;

// Paddle (burger)
const paddle = {
  x: 0,
  y: 0,
  width: 120,
  height: 28,
  targetX: 0,
  speed: 0
};

// Ball
const ball = {
  x: 0,
  y: 0,
  radius: 10,
  vx: 0,
  vy: 0,
  baseSpeed: 5,
  speed: 5,
  trail: []
};

// Input
const input = { left: false, right: false, mouseX: null };

// Colors
const BRICK_COLORS = [
  { main: '#ff4757', light: '#ff6b7a', dark: '#c4303d' },
  { main: '#ff6b35', light: '#ff9a5c', dark: '#cc542a' },
  { main: '#f7b731', light: '#ffcc5c', dark: '#c49227' },
  { main: '#2ed573', light: '#5aff92', dark: '#25a85a' },
  { main: '#1e90ff', light: '#5bb3ff', dark: '#1873cc' },
  { main: '#a55eea', light: '#c78bff', dark: '#844ad4' }
];

// Heart particle class
class HeartParticle {
  constructor(x, y, color) {
    this.x = x;
    this.y = y;
    this.vx = (Math.random() - 0.5) * 14;
    this.vy = -Math.random() * 10 - 4;
    this.size = Math.random() * 8 + 6;
    this.rotation = Math.random() * Math.PI * 2;
    this.rotSpeed = (Math.random() - 0.5) * 0.3;
    this.life = 1;
    this.decay = Math.random() * 0.015 + 0.008;
    this.color = color || `hsl(${Math.random() * 60 + 330}, 85%, 60%)`;
    this.gravity = 0.25;
    this.alpha = 1;
    this.scale = 1;
  }
  update() {
    this.vy += this.gravity;
    this.x += this.vx;
    this.y += this.vy;
    this.vx *= 0.99;
    this.rotation += this.rotSpeed;
    this.life -= this.decay;
    this.alpha = this.life;
    this.scale = this.life;
    return this.life > 0;
  }
  draw(ctx) {
    if (this.life <= 0) return;
    ctx.save();
    ctx.globalAlpha = this.alpha;
    ctx.translate(this.x, this.y);
    ctx.rotate(this.rotation);
    ctx.scale(this.scale, this.scale);
    
    const s = this.size;
    ctx.fillStyle = this.color;
    ctx.beginPath();
    // Heart shape using bezier curves
    ctx.moveTo(0, s * 0.3);
    ctx.bezierCurveTo(-s * 0.5, -s * 0.3, -s, s * 0.1, 0, s * 0.7);
    ctx.bezierCurveTo(s, s * 0.1, s * 0.5, -s * 0.3, 0, s * 0.3);
    ctx.fill();
    
    // Little highlight
    ctx.fillStyle = 'rgba(255,255,255,0.4)';
    ctx.beginPath();
    ctx.moveTo(-s * 0.15, s * 0.05);
    ctx.bezierCurveTo(-s * 0.25, -s * 0.05, -s * 0.3, s * 0.1, -s * 0.1, s * 0.25);
    ctx.bezierCurveTo(s * 0.1, s * 0.15, s * 0.05, -s * 0.05, -s * 0.15, s * 0.05);
    ctx.fill();
    
    ctx.restore();
  }
}

// Spark particle for extra juice
class Spark {
  constructor(x, y, color) {
    this.x = x;
    this.y = y;
    this.vx = (Math.random() - 0.5) * 8;
    this.vy = (Math.random() - 0.5) * 8;
    this.life = 1;
    this.decay = Math.random() * 0.05 + 0.03;
    this.color = color;
    this.size = Math.random() * 3 + 1;
  }
  update() {
    this.x += this.vx;
    this.y += this.vy;
    this.vy += 0.1;
    this.life -= this.decay;
    return this.life > 0;
  }
  draw(ctx) {
    ctx.save();
    ctx.globalAlpha = this.life;
    ctx.fillStyle = this.color;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.size * this.life, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }
}

// Brick class
class Brick {
  constructor(x, y, w, h, colorIdx, hits) {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    this.colorIdx = colorIdx;
    this.hits = hits;
    this.maxHits = hits;
    this.alive = true;
    this.hitFlash = 0;
    this.bobOffset = Math.random() * Math.PI * 2;
    this.bobSpeed = 0.02 + Math.random() * 0.02;
  }
  update() {
    if (this.hitFlash > 0) this.hitFlash -= 0.15;
    this.bobOffset += this.bobSpeed;
  }
  hit() {
    this.hits--;
    this.hitFlash = 1;
    if (this.hits <= 0) {
      this.alive = false;
      this.explode();
      return true;
    }
    return false;
  }
  explode() {
    const cx = this.x + this.width / 2;
    const cy = this.y + this.height / 2;
    const color = BRICK_COLORS[this.colorIdx];
    
    // Hearts explosion
    for (let i = 0; i < 28; i++) {
      particles.push(new HeartParticle(cx, cy, color.main));
    }
    // Sparks
    for (let i = 0; i < 20; i++) {
      particles.push(new Spark(cx, cy, color.light));
    }
    screenShake = 8;
  }
  draw(ctx) {
    if (!this.alive) return;
    
    const color = BRICK_COLORS[this.colorIdx];
    const bob = Math.sin(this.bobOffset) * 2;
    const y = this.y + bob;
    
    // Flash effect
    let r, g, b;
    if (this.hitFlash > 0) {
      ctx.save();
      ctx.globalAlpha = this.hitFlash;
      ctx.fillStyle = '#fff';
      ctx.fillRect(this.x, y, this.width, this.height);
      ctx.restore();
    }
    
    // Main brick
    const grad = ctx.createLinearGradient(this.x, y, this.x, y + this.height);
    grad.addColorStop(0, color.light);
    grad.addColorStop(0.5, color.main);
    grad.addColorStop(1, color.dark);
    ctx.fillStyle = grad;
    roundRect(ctx, this.x, y, this.width, this.height, 6);
    ctx.fill();
    
    // Top highlight
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    roundRect(ctx, this.x + 2, y + 2, this.width - 4, 4, 3);
    ctx.fill();
    
    // Hit indicator
    if (this.maxHits > 1) {
      ctx.fillStyle = 'rgba(0,0,0,0.3)';
      ctx.font = 'bold 14px sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(this.hits, this.x + this.width / 2, y + this.height / 2 + 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();
}

// Draw burger paddle
function drawBurger(ctx, x, y, w, h) {
  const cx = x + w / 2;
  const bunH = h * 0.45;
  const pattyH = h * 0.3;
  const cheeseH = h * 0.12;
  const lettuceH = h * 0.13;
  
  // Bottom bun
  const grad = ctx.createLinearGradient(x, y + h - bunH, x, y + h);
  grad.addColorStop(0, '#e8a87c');
  grad.addColorStop(1, '#c9895c');
  ctx.fillStyle = grad;
  roundRect(ctx, x + 4, y + h - bunH, w - 8, bunH, bunH / 2);
  ctx.fill();
  
  // Sesame seeds on bottom bun
  ctx.fillStyle = '#f5e6c8';
  for (let i = 0; i < 6; i++) {
    const sx = x + 15 + i * (w - 30) / 5 + Math.sin(Date.now() * 0.001 + i) * 2;
    const sy = y + h - bunH + 4 + (i % 2) * (bunH - 8);
    ctx.beginPath();
    ctx.ellipse(sx, sy, 3, 1.5, 0, 0, Math.PI * 2);
    ctx.fill();
  }
  
  // Lettuce
  ctx.fillStyle = '#2d993d';
  ctx.beginPath();
  ctx.moveTo(x + 2, y + h - bunH);
  for (let i = 0; i <= 10; i++) {
    const lx = x + 2 + i * (w - 4) / 10;
    const ly = y + h - bunH - Math.sin(i * 0.8 + Date.now() * 0.002) * 3;
    ctx.lineTo(lx, ly);
  }
  ctx.lineTo(x + w - 2, y + h - bunH - lettuceH);
  ctx.lineTo(x + 2, y + h - bunH - lettuceH);
  ctx.closePath();
  ctx.fill();
  
  // Cheese
  const cheeseGrad = ctx.createLinearGradient(x, y + h - bunH - lettuceH, x, y + h - bunH - lettuceH - cheeseH);
  cheeseGrad.addColorStop(0, '#ffd630');
  cheeseGrad.addColorStop(1, '#e6b800');
  ctx.fillStyle = cheeseGrad;
  ctx.beginPath();
  ctx.moveTo(x + 6, y + h - bunH - lettuceH);
  ctx.lineTo(x + w - 6, y + h - bunH - lettuceH);
  ctx.lineTo(x + w - 8, y + h - bunH - lettuceH - cheeseH);
  ctx.lineTo(x + 8, y + h - bunH - lettuceH - cheeseH);
  // Drippy cheese corners
  ctx.quadraticCurveTo(x + 6, y + h - bunH - lettuceH - cheeseH + 4, x + 6, y + h - bunH - lettuceH);
  ctx.closePath();
  ctx.fill();
  
  // Patty
  const pattyGrad = ctx.createLinearGradient(x, y + h - bunH - lettuceH - cheeseH, x, y + h - bunH - lettuceH - cheeseH - pattyH);
  pattyGrad.addColorStop(0, '#5d3a1a');
  pattyGrad.addColorStop(0.5, '#8b4513');
  pattyGrad.addColorStop(1, '#5d3a1a');
  ctx.fillStyle = pattyGrad;
  roundRect(ctx, x + 8, y + h - bunH - lettuceH - cheeseH - pattyH, w - 16, pattyH, pattyH / 2);
  ctx.fill();
  
  // Patty grill marks
  ctx.strokeStyle = 'rgba(0,0,0,0.2)';
  ctx.lineWidth = 2;
  for (let i = 0; i < 3; i++) {
    const mx = x + w / 2 - 15 + i * 15;
    ctx.beginPath();
    ctx.moveTo(mx, y + h - bunH - lettuceH - cheeseH);
    ctx.lineTo(mx, y + h - bunH - lettuceH - cheeseH - pattyH);
    ctx.stroke();
  }
  
  // Top bun
  const topBunGrad = ctx.createLinearGradient(x, y, x, y + bunH);
  topBunGrad.addColorStop(0, '#f0c49a');
  topBunGrad.addColorStop(1, '#e8a87c');
  ctx.fillStyle = topBunGrad;
  roundRect(ctx, x, y, w, bunH, bunH / 2);
  ctx.fill();
  
  // Sesame seeds on top bun
  ctx.fillStyle = '#f5e6c8';
  for (let i = 0; i < 8; i++) {
    const sx = x + 10 + i * (w - 20) / 7 + Math.cos(Date.now() * 0.001 + i * 2) * 2;
    const sy = y + 6 + (i % 2) * (bunH - 12);
    ctx.beginPath();
    ctx.ellipse(sx, sy, 3, 1.5, Math.sin(Date.now() * 0.0005 + i) * 0.2, 0, Math.PI * 2);
    ctx.fill();
  }
  
  // Glare on top bun
  ctx.fillStyle = 'rgba(255,255,255,0.15)';
  roundRect(ctx, x + 8, y + 4, w - 16, bunH * 0.3, 4);
  ctx.fill();
}

// Draw ball with trail
function drawBall(ctx) {
  // Trail
  for (let i = 0; i < ball.trail.length; i++) {
    const t = ball.trail[i];
    const alpha = (i / ball.trail.length) * 0.4;
    const radius = ball.radius * (i / ball.trail.length) * 0.8;
    ctx.globalAlpha = alpha;
    const grad = ctx.createRadialGradient(t.x, t.y, 0, t.x, t.y, radius);
    grad.addColorStop(0, '#ff6496');
    grad.addColorStop(1, 'rgba(255,100,150,0)');
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.arc(t.x, t.y, radius, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
  
  // Ball glow
  const glow = ctx.createRadialGradient(ball.x, ball.y, 0, ball.x, ball.y, ball.radius * 2);
  glow.addColorStop(0, 'rgba(255,100,150,0.3)');
  glow.addColorStop(1, 'rgba(255,100,150,0)');
  ctx.fillStyle = glow;
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.radius * 2, 0, Math.PI * 2);
  ctx.fill();
  
  // Ball
  const ballGrad = ctx.createRadialGradient(ball.x - 3, ball.y - 3, 0, ball.x, ball.y, ball.radius);
  ballGrad.addColorStop(0, '#fff0f5');
  ballGrad.addColorStop(0.5, '#ff6496');
  ballGrad.addColorStop(1, '#c4303d');
  ctx.fillStyle = ballGrad;
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
  ctx.fill();
  
  // Highlight
  ctx.fillStyle = 'rgba(255,255,255,0.5)';
  ctx.beginPath();
  ctx.arc(ball.x - 3, ball.y - 3, ball.radius * 0.3, 0, Math.PI * 2);
  ctx.fill();
}

// Initialize level
function initLevel() {
  bricks = [];
  const rows = 5 + Math.min(level - 1, 3);
  const cols = 10;
  const brickW = (width - 80) / cols;
  const brickH = 28;
  const startX = 40;
  const startY = 60;
  
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      const hits = r < 2 ? 1 : (r < 4 ? 2 : 3);
      bricks.push(new Brick(
        startX + c * brickW,
        startY + r * (brickH + 6),
        brickW - 4,
        brickH,
        r % BRICK_COLORS.length,
        hits
      ));
    }
  }
  updateBricksCount();
}

// Reset ball
function resetBall() {
  ball.x = paddle.x + paddle.width / 2;
  ball.y = paddle.y - ball.radius - 2;
  ball.vx = (Math.random() - 0.5) * 4;
  ball.vy = -ball.speed;
  ball.trail = [];
  ball.speed = ball.baseSpeed + (level - 1) * 0.5;
}

// Update lives display
function updateLivesDisplay() {
  livesDisplay.innerHTML = '';
  for (let i = 0; i < 3; i++) {
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('class', 'life-icon' + (i >= lives ? ' lost' : ''));
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', '28');
    svg.setAttribute('height', '28');
    svg.innerHTML = '<path fill="#ff6496" d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>';
    livesDisplay.appendChild(svg);
  }
}

function updateBricksCount() {
  const alive = bricks.filter(b => b.alive).length;
  bricksEl.textContent = alive;
}

// Game loop
let lastTime = 0;
function gameLoop(time) {
  const dt = Math.min((time - lastTime) / 16.67, 3);
  lastTime = time;
  
  // Clear
  ctx.clearRect(0, 0, width, height);
  
  // Screen shake
  if (screenShake > 0) {
    ctx.save();
    ctx.translate((Math.random() - 0.5) * screenShake, (Math.random() - 0.5) * screenShake);
    screenShake *= 0.9;
    if (screenShake < 0.5) screenShake = 0;
  }
  
  // Background grid
  drawBackground();
  
  // Update paddle
  if (demoMode) {
    // Simple AI: track ball with some lag and error
    const target = ball.x - paddle.width / 2;
    const diff = target - paddle.x;
    paddle.x += diff * 0.12;
    // Add slight imperfection
    paddle.x += Math.sin(time * 0.003) * 0.5;
  } else {
    if (input.left) paddle.speed = Math.max(paddle.speed - 1.2, -14);
    else if (input.right) paddle.speed = Math.min(paddle.speed + 1.2, 14);
    else paddle.speed *= 0.85;
    paddle.x += paddle.speed;
    if (input.mouseX !== null) {
      const target = input.mouseX - paddle.width / 2;
      paddle.x += (target - paddle.x) * 0.3;
    }
  }
  paddle.x = Math.max(0, Math.min(width - paddle.width, paddle.x));
  paddle.y = height - 40;
  
  // Update ball
  if (!demoMode || running) {
    ball.x += ball.vx * dt;
    ball.y += ball.vy * dt;
    
    // Trail
    ball.trail.unshift({ x: ball.x, y: ball.y });
    if (ball.trail.length > 10) ball.trail.pop();
    
    // Wall collisions
    if (ball.x - ball.radius < 0) { ball.x = ball.radius; ball.vx = Math.abs(ball.vx); }
    if (ball.x + ball.radius > width) { ball.x = width - ball.radius; ball.vx = -Math.abs(ball.vx); }
    if (ball.y - ball.radius < 0) { ball.y = ball.radius; ball.vy = Math.abs(ball.vy); }
    
    // Paddle collision
    if (ball.y + ball.radius > paddle.y && 
        ball.x > paddle.x && 
        ball.x < paddle.x + paddle.width &&
        ball.vy > 0) {
      const hitPos = (ball.x - paddle.x) / paddle.width - 0.5;
      ball.vx = hitPos * ball.speed * 1.8;
      ball.vy = -Math.sqrt(ball.speed * ball.speed - ball.vx * ball.vx);
      ball.y = paddle.y - ball.radius;
      
      // Speed up slightly
      ball.speed = Math.min(ball.speed * 1.01, 16);
      
      // Paddle particles
      for (let i = 0; i < 5; i++) {
        particles.push(new Spark(ball.x, ball.y, '#ff6496'));
      }
    }
    
    // Brick collisions
    for (const brick of bricks) {
      if (!brick.alive) continue;
      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) {
        
        // Determine collision side
        const overlapX = Math.min(ball.x + ball.radius, brick.x + brick.width) - Math.max(ball.x - ball.radius, brick.x);
        const overlapY = Math.min(ball.y + ball.radius, brick.y + brick.height) - Math.max(ball.y - ball.radius, brick.y);
        
        if (overlapX < overlapY) {
          if (ball.x < brick.x + brick.width / 2) ball.x = brick.x - ball.radius;
          else ball.x = brick.x + brick.width + ball.radius;
          ball.vx = -ball.vx;
        } else {
          if (ball.y < brick.y + brick.height / 2) ball.y = brick.y - ball.radius;
          else ball.y = brick.y + brick.height + ball.radius;
          ball.vy = -ball.vy;
        }
        
        const destroyed = brick.hit();
        if (destroyed) {
          score += 100 * brick.maxHits;
          scoreEl.textContent = score;
        } else {
          score += 20;
          scoreEl.textContent = score;
        }
        break;
      }
    }
    
    // Bottom - lose life
    if (ball.y - ball.radius > height) {
      lives--;
      updateLivesDisplay();
      if (lives <= 0) {
        gameOver();
      } else {
        resetBall();
      }
    }
  }
  
  // Update particles
  for (let i = particles.length - 1; i >= 0; i--) {
    if (!particles[i].update()) {
      particles.splice(i, 1);
    }
  }
  
  // Update bricks
  for (const brick of bricks) brick.update();
  
  // Draw bricks
  for (const brick of bricks) brick.draw(ctx);
  
  // Draw particles
  for (const p of particles) p.draw(ctx);
  
  // Draw paddle (burger)
  drawBurger(ctx, paddle.x, paddle.y, paddle.width, paddle.height);
  
  // Draw ball
  drawBall(ctx);
  
  // Screen shake restore
  if (screenShake > 0) ctx.restore();
  
  // Check win
  const aliveBricks = bricks.filter(b => b.alive).length;
  if (aliveBricks === 0 && bricks.length > 0) {
    level++;
    levelEl.textContent = level;
    initLevel();
    resetBall();
  }
  
  requestAnimationFrame(gameLoop);
}

function gameOver() {
  running = false;
  demoMode = true;
  startOverlay.classList.remove('hidden');
  startBtn.textContent = 'Play Again';
  // Reset for next game
  setTimeout(() => {
    score = 0;
    level = 1;
    lives = 3;
    scoreEl.textContent = '0';
    levelEl.textContent = '1';
    updateLivesDisplay();
    initLevel();
    resetBall();
    paddle.x = width / 2 - paddle.width / 2;
  }, 500);
}

function startGame() {
  if (running) return;
  running = true;
  demoMode = false;
  startOverlay.classList.add('hidden');
}

function startDemo() {
  demoMode = true;
  running = true;
  startOverlay.classList.add('hidden');
  // Reset demo state
  score = 0;
  level = 1;
  lives = 3;
  scoreEl.textContent = '0';
  levelEl.textContent = '1';
  updateLivesDisplay();
  initLevel();
  resetBall();
  paddle.x = width / 2 - paddle.width / 2;
}

// Input handlers
window.addEventListener('keydown', (e) => {
  if (!running || demoMode) { startGame(); return; }
  if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') input.left = true;
  if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') input.right = true;
});
window.addEventListener('keyup', (e) => {
  if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') input.left = false;
  if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') input.right = false;
});

canvas.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect();
  input.mouseX = (e.clientX - rect.left) * (width / rect.width);
});
canvas.addEventListener('mouseleave', () => { input.mouseX = null; });
canvas.addEventListener('click', () => { if (!running || demoMode) startGame(); });
canvas.addEventListener('touchmove', (e) => {
  e.preventDefault();
  const rect = canvas.getBoundingClientRect();
  input.mouseX = (e.touches[0].clientX - rect.left) * (width / rect.width);
}, { passive: false });
canvas.addEventListener('touchstart', (e) => { if (!running || demoMode) startGame(); });

startBtn.addEventListener('click', () => { if (demoMode) startGame(); else startDemo(); });

// Background
function drawBackground() {
  // Subtle gradient
  const grad = ctx.createLinearGradient(0, 0, 0, height);
  grad.addColorStop(0, '#0f0f23');
  grad.addColorStop(1, '#1a1a2e');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, width, height);
  
  // Floating hearts in background
  const time = Date.now() * 0.0003;
  for (let i = 0; i < 12; i++) {
    const x = (i * 127 + Math.sin(time + i) * 30) % width;
    const y = (i * 89 + Math.cos(time * 0.7 + i) * 40) % height;
    const size = 8 + Math.sin(time * 2 + i) * 3;
    const alpha = 0.03 + Math.sin(time + i * 2) * 0.02;
    ctx.save();
    ctx.globalAlpha = alpha;
    ctx.translate(x, y);
    ctx.rotate(time + i);
    ctx.fillStyle = '#ff6496';
    ctx.beginPath();
    ctx.moveTo(0, size * 0.3);
    ctx.bezierCurveTo(-size * 0.5, -size * 0.3, -size, size * 0.1, 0, size * 0.7);
    ctx.bezierCurveTo(size, size * 0.1, size * 0.5, -size * 0.3, 0, size * 0.3);
    ctx.fill();
    ctx.restore();
  }
}

// Initialize and start demo
initLevel();
resetBall();
paddle.x = width / 2 - paddle.width / 2;
updateLivesDisplay();
requestAnimationFrame(gameLoop);
</script>
</body>
</html>
Reasoning trace (218 tokens)
The user wants a Breakout game with:
1. Paddle is a hamburger
2. Bricks explode into lots of little hearts when broken
3. Self-playing demo mode with "Click or press a key to start"
4. Must work in 1280x800 and down to 375px wide
5. No external resources, everything inline
6. No localStorage, alerts, etc.

Let me build this with Canvas for good particle performance. I'll create:
- Hamburger paddle (draw with arcs/rects)
- Ball physics
- Bricks that spawn heart particles on destruction
- Heart particle system (lots of small hearts flying out)
- Simple AI for demo mode
- Responsive design

For the hamburger, I'll draw it procedurally: bun (top/bottom), patty, cheese, lettuce, tomato.

Hearts can be drawn with a simple path or using a unicode character rendered as text. Drawing with path is better for performance and scaling.

Let me structure the code carefully.