Hamburger breakoutOne-line
Nemotron 3 Ultra (free)
by NVIDIAReasoningClean
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.