Nemotron 3 Ultra (free)
by NVIDIAReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 24 s
- Cost
- $0
- Output tokens
- 8,102
- Spent thinking
- 269 tokens
- Speed
- 156.5 tok/s
- Console errors
- 0
- First token after
- 1 min 32 s
- First visible text after
- 1 min 37 s
- Prompt tokens
- 531
- Code size
- 23.1 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
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 · 799 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: 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;
color: #e0e0e0;
overflow: hidden;
}
#gameContainer {
position: relative;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 0 2px rgba(255,255,255,0.1);
background: #0d0d1a;
}
canvas { display: block; border-radius: 12px; }
#ui {
position: absolute;
top: 16px;
left: 16px;
right: 16px;
display: flex;
justify-content: space-between;
pointer-events: none;
z-index: 10;
}
.stat {
background: rgba(0,0,0,0.7);
padding: 8px 16px;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.1);
}
.stat span { color: #00d4ff; margin-left: 6px; }
#lives span { color: #ff4757; }
#overlay {
position: absolute;
inset: 0;
border-radius: 12px;
background: rgba(13, 13, 26, 0.95);
backdrop-filter: blur(8px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
z-index: 20;
opacity: 1;
transition: opacity 0.3s ease;
}
#overlay.hidden { opacity: 0; pointer-events: none; }
#overlay h1 {
font-size: 48px;
font-weight: 700;
background: linear-gradient(135deg, #00d4ff, #ff6b6b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 0 30px rgba(0,212,255,0.5);
}
#overlay p { font-size: 18px; opacity: 0.8; }
#overlay button {
padding: 14px 40px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: 50px;
background: linear-gradient(135deg, #00d4ff, #0099cc);
color: #0d0d1a;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 20px rgba(0,212,255,0.4);
}
#overlay button:hover { transform: scale(1.05); box-shadow: 0 6px 30px rgba(0,212,255,0.6); }
#overlay button:active { transform: scale(0.98); }
.powerup-icon { font-size: 20px; }
@media (max-width: 600px) {
#overlay h1 { font-size: 32px; }
.stat { font-size: 12px; padding: 6px 12px; }
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="canvas"></canvas>
<div id="ui">
<div class="stat" id="score">SCORE <span>0</span></div>
<div class="stat" id="lives">LIVES <span>3</span></div>
</div>
<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>
</div>
<script>
// Game constants
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 600;
const PADDLE_HEIGHT = 16;
const PADDLE_WIDTH = 120;
const PADDLE_Y = CANVAS_HEIGHT - 40;
const BALL_RADIUS = 10;
const BALL_SPEED = 6;
const BRICK_ROWS = 6;
const BRICK_COLS = 10;
const BRICK_WIDTH = 72;
const BRICK_HEIGHT = 28;
const BRICK_PADDING = 4;
const BRICK_OFFSET_TOP = 80;
const BRICK_OFFSET_LEFT = 40;
const POWERUP_DROP_CHANCE = 0.25;
const POWERUP_SIZE = 24;
const POWERUP_SPEED = 3;
const PARTICLE_COUNT = 12;
// Power-up types
const POWERUP_WIDE = 'wide';
const POWERUP_MULTI = 'multi';
// Game state
let canvas, ctx;
let paddle = { x: 0, y: PADDLE_Y, width: PADDLE_WIDTH, height: PADDLE_HEIGHT, targetX: 0 };
let balls = [];
let bricks = [];
let particles = [];
let powerups = [];
let score = 0;
let lives = 3;
let gameState = 'demo'; // demo, playing, gameover, victory
let demoMode = true;
let widePaddleTimer = 0;
let lastTime = 0;
let keys = { left: false, right: false };
// Colors
const COLORS = {
bg: '#0d0d1a',
paddle: '#00d4ff',
paddleGlow: 'rgba(0,212,255,0.5)',
ball: '#ff6b6b',
ballGlow: 'rgba(255,107,107,0.6)',
brickNormal: ['#ff6b6b', '#ff9f43', '#feca57', '#48dbfb', '#1dd1a1', '#5f27cd'],
brickTough: '#ffffff',
brickToughCrack: '#cccccc',
particle: ['#ff6b6b', '#ff9f43', '#feca57', '#48dbfb', '#1dd1a1', '#00d4ff'],
powerupWide: '#00d4ff',
powerupMulti: '#ff6b6b',
text: '#e0e0e0',
uiBg: 'rgba(0,0,0,0.7)',
uiBorder: 'rgba(255,255,255,0.1)'
};
// Initialize canvas
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
function resizeCanvas() {
const container = document.getElementById('gameContainer');
const maxWidth = Math.min(CANVAS_WIDTH, window.innerWidth - 32);
const maxHeight = Math.min(CANVAS_HEIGHT, window.innerHeight - 32);
const scale = Math.min(maxWidth / CANVAS_WIDTH, maxHeight / CANVAS_HEIGHT);
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
canvas.style.width = (CANVAS_WIDTH * scale) + 'px';
canvas.style.height = (CANVAS_HEIGHT * scale) + 'px';
}
// Input handling
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const scaleX = CANVAS_WIDTH / rect.width;
paddle.targetX = (e.clientX - rect.left) * scaleX - paddle.width / 2;
if (demoMode) exitDemo();
});
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') keys.left = true;
if (e.key === 'ArrowRight') keys.right = true;
if (demoMode && (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter')) exitDemo();
});
window.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft') keys.left = false;
if (e.key === 'ArrowRight') keys.right = false;
});
canvas.addEventListener('click', () => {
if (gameState === 'gameover' || gameState === 'victory') restartGame();
});
document.getElementById('startBtn').addEventListener('click', () => {
if (demoMode) exitDemo();
else if (gameState === 'gameover' || gameState === 'victory') restartGame();
});
function exitDemo() {
demoMode = false;
gameState = 'playing';
document.getElementById('overlay').classList.add('hidden');
spawnBall();
}
function restartGame() {
score = 0;
lives = 3;
widePaddleTimer = 0;
paddle.width = PADDLE_WIDTH;
paddle.x = (CANVAS_WIDTH - paddle.width) / 2;
paddle.targetX = paddle.x;
balls = [];
bricks = [];
particles = [];
powerups = [];
createBricks();
gameState = 'playing';
document.getElementById('overlay').classList.add('hidden');
document.getElementById('overlayTitle').textContent = 'BREAKOUT';
document.getElementById('overlayText').textContent = 'Click or press a key to start';
document.getElementById('startBtn').textContent = 'Start Game';
spawnBall();
updateUI();
}
// Ball spawning
function spawnBall(x, y, vx, vy) {
const ball = {
x: x || paddle.x + paddle.width / 2,
y: y || PADDLE_Y - BALL_RADIUS - 2,
radius: BALL_RADIUS,
vx: vx || (Math.random() - 0.5) * 4,
vy: vy || -BALL_SPEED,
trail: []
};
balls.push(ball);
return ball;
}
// Brick creation
function createBricks() {
bricks = [];
for (let row = 0; row < BRICK_ROWS; row++) {
for (let col = 0; col < BRICK_COLS; col++) {
const x = BRICK_OFFSET_LEFT + col * (BRICK_WIDTH + BRICK_PADDING);
const y = BRICK_OFFSET_TOP + row * (BRICK_HEIGHT + BRICK_PADDING);
const isTough = row < 2; // Top 2 rows are tough bricks
const hasPowerup = !isTough && Math.random() < POWERUP_DROP_CHANCE;
const powerupType = hasPowerup ? (Math.random() < 0.5 ? POWERUP_WIDE : POWERUP_MULTI) : null;
bricks.push({
x, y,
width: BRICK_WIDTH,
height: BRICK_HEIGHT,
hits: isTough ? 2 : 1,
maxHits: isTough ? 2 : 1,
color: isTough ? COLORS.brickTough : COLORS.brickNormal[row % COLORS.brickNormal.length],
hasPowerup,
powerupType,
destroyed: false,
glow: 0
});
}
}
}
// Collision detection - circle vs rect
function circleRectCollide(circle, rect) {
const distX = Math.abs(circle.x - rect.x - rect.width / 2);
const distY = Math.abs(circle.y - rect.y - rect.height / 2);
if (distX > rect.width / 2 + circle.radius) return null;
if (distY > rect.height / 2 + circle.radius) return null;
if (distX <= rect.width / 2) return 'top';
if (distY <= rect.height / 2) return 'side';
const cornerDist = Math.pow(distX - rect.width / 2, 2) + Math.pow(distY - rect.height / 2, 2);
return cornerDist <= Math.pow(circle.radius, 2) ? 'corner' : null;
}
// Game update
function update(deltaTime) {
if (gameState !== 'playing') return;
const dt = deltaTime / 1000;
// Update paddle
if (demoMode) {
// Simple AI: follow the first ball
if (balls.length > 0) {
const target = balls[0].x - paddle.width / 2;
paddle.targetX = target;
}
} else {
if (keys.left) paddle.targetX -= 600 * dt;
if (keys.right) paddle.targetX += 600 * dt;
}
paddle.targetX = Math.max(0, Math.min(CANVAS_WIDTH - paddle.width, paddle.targetX));
paddle.x += (paddle.targetX - paddle.x) * Math.min(1, 15 * dt);
// Wide paddle timer
if (widePaddleTimer > 0) {
widePaddleTimer -= dt;
if (widePaddleTimer <= 0) {
paddle.width = PADDLE_WIDTH;
widePaddleTimer = 0;
}
}
// Update balls
for (let i = balls.length - 1; i >= 0; i--) {
const ball = balls[i];
// Store trail
ball.trail.unshift({ x: ball.x, y: ball.y });
if (ball.trail.length > 8) ball.trail.pop();
// Move ball
ball.x += ball.vx * dt * 60;
ball.y += ball.vy * dt * 60;
// Wall collisions
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.vx = Math.abs(ball.vx);
}
if (ball.x + ball.radius > CANVAS_WIDTH) {
ball.x = CANVAS_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.y - ball.radius <= paddle.y + paddle.height &&
ball.x >= paddle.x && ball.x <= paddle.x + paddle.width) {
ball.y = paddle.y - ball.radius;
// Calculate bounce angle based on hit position
const hitPos = (ball.x - paddle.x) / paddle.width; // 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.cos(angle) * speed;
// Add slight paddle velocity influence
ball.vx += (paddle.targetX - paddle.x) * 0.1;
}
// Brick collisions
for (const brick of bricks) {
if (brick.destroyed) continue;
const collision = circleRectCollide(ball, brick);
if (collision) {
// Determine bounce direction
const prevX = ball.x - ball.vx * dt * 60;
const prevY = ball.y - ball.vy * dt * 60;
const prevCollision = circleRectCollide({x: prevX, y: prevY, radius: ball.radius}, brick);
if (collision === 'top' || (collision === 'corner' && prevCollision === 'side')) {
ball.vy = -Math.abs(ball.vy);
ball.y = brick.y - ball.radius;
} else if (collision === 'side' || (collision === 'corner' && prevCollision === 'top')) {
ball.vx = ball.x < brick.x + brick.width / 2 ? -Math.abs(ball.vx) : Math.abs(ball.vx);
ball.x = ball.x < brick.x + brick.width / 2 ? brick.x - ball.radius : brick.x + brick.width + ball.radius;
}
// Damage brick
brick.hits--;
brick.glow = 1;
if (brick.hits <= 0) {
brick.destroyed = true;
score += brick.maxHits === 2 ? 20 : 10;
createParticles(brick.x + brick.width/2, brick.y + brick.height/2, brick.color);
// Drop powerup
if (brick.hasPowerup && brick.powerupType) {
powerups.push({
x: brick.x + brick.width/2,
y: brick.y + brick.height/2,
type: brick.powerupType,
width: POWERUP_SIZE,
height: POWERUP_SIZE,
vy: POWERUP_SPEED,
rotation: 0
});
}
}
break; // Only hit one brick per frame
}
}
// Ball lost
if (ball.y - ball.radius > CANVAS_HEIGHT) {
balls.splice(i, 1);
if (balls.length === 0) {
loseLife();
}
}
}
// Update powerups
for (let i = powerups.length - 1; i >= 0; i--) {
const p = powerups[i];
p.y += p.vy * dt * 60;
p.rotation += dt * 2;
// Check paddle collision
if (p.y + p.height/2 >= paddle.y &&
p.y - p.height/2 <= paddle.y + paddle.height &&
p.x >= paddle.x && p.x <= paddle.x + paddle.width) {
applyPowerup(p.type);
powerups.splice(i, 1);
continue;
}
if (p.y - p.height/2 > CANVAS_HEIGHT) {
powerups.splice(i, 1);
}
}
// Update particles
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 += 300 * dt; // gravity
p.life -= dt;
p.alpha = p.life / p.maxLife;
if (p.life <= 0) particles.splice(i, 1);
}
// Update brick glow
for (const brick of bricks) {
if (brick.glow > 0) brick.glow = Math.max(0, brick.glow - dt * 5);
}
// Check victory
const remainingBricks = bricks.filter(b => !b.destroyed).length;
if (remainingBricks === 0) {
victory();
}
updateUI();
}
function loseLife() {
lives--;
if (lives <= 0) {
gameOver();
} else {
// Reset ball after short delay
setTimeout(() => {
if (gameState === 'playing') spawnBall();
}, 1000);
}
}
function gameOver() {
gameState = 'gameover';
demoMode = false;
document.getElementById('overlayTitle').textContent = 'GAME OVER';
document.getElementById('overlayText').textContent = `Final Score: ${score}`;
document.getElementById('startBtn').textContent = 'Play Again';
document.getElementById('overlay').classList.remove('hidden');
}
function victory() {
gameState = 'victory';
demoMode = false;
document.getElementById('overlayTitle').textContent = 'VICTORY!';
document.getElementById('overlayText').textContent = `Final Score: ${score} | Lives: ${lives}`;
document.getElementById('startBtn').textContent = 'Play Again';
document.getElementById('overlay').classList.remove('hidden');
}
function applyPowerup(type) {
if (type === POWERUP_WIDE) {
paddle.width = PADDLE_WIDTH * 1.6;
widePaddleTimer = 10;
} else if (type === POWERUP_MULTI) {
// Create 2 additional balls
for (let i = 0; i < 2; i++) {
const ball = balls[0];
if (ball) {
spawnBall(ball.x, ball.y,
(Math.random() - 0.5) * 8,
-BALL_SPEED * (0.8 + Math.random() * 0.4));
}
}
}
}
function createParticles(x, y, color) {
for (let i = 0; i < PARTICLE_COUNT; i++) {
const angle = (Math.PI * 2 * i) / PARTICLE_COUNT + Math.random() * 0.5;
const speed = 100 + Math.random() * 200;
particles.push({
x, y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - 100,
radius: 3 + Math.random() * 3,
color: color,
life: 0.5 + Math.random() * 0.5,
maxLife: 1,
alpha: 1
});
}
}
function updateUI() {
document.getElementById('score').querySelector('span').textContent = score;
document.getElementById('lives').querySelector('span').textContent = lives;
}
// Rendering
function render() {
// Clear
ctx.fillStyle = COLORS.bg;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw subtle grid background
ctx.strokeStyle = 'rgba(255,255,255,0.02)';
ctx.lineWidth = 1;
for (let x = 0; x < CANVAS_WIDTH; x += 40) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, CANVAS_HEIGHT);
ctx.stroke();
}
for (let y = 0; y < CANVAS_HEIGHT; y += 40) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(CANVAS_WIDTH, y);
ctx.stroke();
}
// Draw bricks
for (const brick of bricks) {
if (brick.destroyed) continue;
const isTough = brick.maxHits === 2;
const glowIntensity = brick.glow;
// Brick shadow
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.fillRect(brick.x + 2, brick.y + 2, brick.width, brick.height);
// Brick body
let brickColor = brick.color;
if (isTough && brick.hits === 1) {
// Cracked tough brick
brickColor = COLORS.brickToughCrack;
}
const gradient = ctx.createLinearGradient(brick.x, brick.y, brick.x, brick.y + brick.height);
gradient.addColorStop(0, lightenColor(brickColor, 30));
gradient.addColorStop(1, brickColor);
ctx.fillStyle = gradient;
roundRect(ctx, brick.x, brick.y, brick.width, brick.height, 4);
ctx.fill();
// Highlight
ctx.fillStyle = 'rgba(255,255,255,0.15)';
roundRect(ctx, brick.x, brick.y, brick.width, 4, 4);
ctx.fill();
// Glow effect on hit
if (glowIntensity > 0) {
ctx.strokeStyle = `rgba(255,255,255,${glowIntensity * 0.8})`;
ctx.lineWidth = 3;
roundRect(ctx, brick.x, brick.y, brick.width, brick.height, 4);
ctx.stroke();
}
// Crack effect for tough bricks
if (isTough && brick.hits === 1) {
ctx.strokeStyle = '#666';
ctx.lineWidth = 2;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(brick.x + 8, brick.y + 8);
ctx.lineTo(brick.x + brick.width - 8, brick.y + brick.height - 8);
ctx.moveTo(brick.x + brick.width - 8, brick.y + 8);
ctx.lineTo(brick.x + 8, brick.y + brick.height - 8);
ctx.stroke();
}
// Powerup indicator
if (brick.hasPowerup && !brick.destroyed) {
const pulse = 0.7 + 0.3 * Math.sin(Date.now() / 200);
ctx.save();
ctx.translate(brick.x + brick.width/2, brick.y + brick.height/2);
ctx.scale(pulse, pulse);
ctx.translate(-(brick.x + brick.width/2), -(brick.y + brick.height/2));
if (brick.powerupType === POWERUP_WIDE) {
// Wide paddle icon
ctx.fillStyle = COLORS.powerupWide;
roundRect(ctx, brick.x + brick.width/2 - 16, brick.y + brick.height/2 - 5, 32, 10, 3);
ctx.fill();
} else {
// Multi-ball icon
ctx.fillStyle = COLORS.powerupMulti;
ctx.beginPath();
ctx.arc(brick.x + brick.width/2, brick.y + brick.height/2, 8, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = COLORS.bg;
ctx.beginPath();
ctx.arc(brick.x + brick.width/2 - 3, brick.y + brick.height/2 - 2, 2, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(brick.x + brick.width/2 + 3, brick.y + brick.height/2 + 2, 2, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
}
// Draw particles
for (const p of particles) {
ctx.globalAlpha = p.alpha;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius * p.alpha, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// Draw powerups
for (const p of powerups) {
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.rotation);
// Glow
const glowColor = p.type === POWERUP_WIDE ? COLORS.powerupWide : COLORS.powerupMulti;
ctx.shadowColor = glowColor;
ctx.shadowBlur = 15;
if (p.type === POWERUP_WIDE) {
// Wide paddle powerup
const grad = ctx.createLinearGradient(-12, -12, 12, 12);
grad.addColorStop(0, lightenColor(glowColor, 40));
grad.addColorStop(1, glowColor);
ctx.fillStyle = grad;
roundRect(ctx, -12, -12, 24, 24, 6);
ctx.fill();
// Icon
ctx.fillStyle = COLORS.bg;
roundRect(ctx, -10, -4, 20, 8, 2);
ctx.fill();
} else {
// Multi-ball powerup
const grad = ctx.createRadialGradient(-4, -4, 0, 0, 0, 12);
grad.addColorStop(0, lightenColor(glowColor, 40));
grad.addColorStop(1, glowColor);
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(0, 0, 12, 0, Math.PI * 2);
ctx.fill();
// Small balls
ctx.fillStyle = COLORS.bg;
ctx.beginPath();
ctx.arc(-4, -3, 3, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(4, 3, 3, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
// Draw 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 = (i + 1) / ball.trail.length * 0.3;
const radius = ball.radius * (i + 1) / ball.trail.length;
ctx.globalAlpha = alpha;
ctx.fillStyle = COLORS.ball;
ctx.beginPath();
ctx.arc(t.x, t.y, radius, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// Ball glow
ctx.shadowColor = COLORS.ballGlow;
ctx.shadowBlur = 20;
// Ball body
const grad = ctx.createRadialGradient(
ball.x - 3, ball.y - 3, 0,
ball.x, ball.y, ball.radius
);
grad.addColorStop(0, '#ffaaaa');
grad.addColorStop(1, COLORS.ball);
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
// Draw paddle
// Paddle glow
ctx.shadowColor = COLORS.paddleGlow;
ctx.shadowBlur = 20;
const paddleGrad = ctx.createLinearGradient(paddle.x, paddle.y, paddle.x, paddle.y + paddle.height);
paddleGrad.addColorStop(0, '#40e0ff');
paddleGrad.addColorStop(1, COLORS.paddle);
ctx.fillStyle = paddleGrad;
roundRect(ctx, paddle.x, paddle.y, paddle.width, paddle.height, 8);
ctx.fill();
ctx.shadowBlur = 0;
// Paddle highlight
ctx.fillStyle = 'rgba(255,255,255,0.3)';
roundRect(ctx, paddle.x, paddle.y, paddle.width, 4, 8);
ctx.fill();
// Wide paddle indicator
if (widePaddleTimer > 0) {
ctx.strokeStyle = `rgba(0,212,255,${0.5 + 0.5 * Math.sin(Date.now() / 100)})`;
ctx.lineWidth = 2;
roundRect(ctx, paddle.x - 2, paddle.y - 2, paddle.width + 4, paddle.height + 4, 10);
ctx.stroke();
}
}
// Helper functions
function roundRect(ctx, x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
function lightenColor(color, percent) {
const num = parseInt(color.replace('#', ''), 16);
const amt = Math.round(2.55 * percent);
const R = Math.min(255, (num >> 16) + amt);
const G = Math.min(255, ((num >> 8) & 0x00FF) + amt);
const B = Math.min(255, (num & 0x0000FF) + amt);
return '#' + (0x1000000 + (R << 16) + (G << 8) + B).toString(16).slice(1);
}
// Game loop
function gameLoop(time) {
const deltaTime = time - lastTime;
lastTime = time;
if (demoMode) {
// In demo mode, run AI paddle
if (balls.length === 0) spawnBall();
}
update(deltaTime);
render();
requestAnimationFrame(gameLoop);
}
// Initialize
createBricks();
requestAnimationFrame(gameLoop);
</script>
</body>
</html>Reasoning trace (269 tokens)
The user wants a polished Breakout game in a single HTML file. Let me plan this carefully. Key requirements: 1. Canvas-based game 2. Paddle controlled by mouse AND arrow keys 3. Ball bounce angle depends on hit position on paddle 4. 6+ rows of bricks, mix of normal (1 hit) and tough (2 hits, cracks on first hit) 5. Some bricks have power-up icons, drop when broken 6. Power-ups: wider paddle (10s) and multi-ball (3 balls) 7. Ball never passes through bricks/paddle (proper collision detection) 8. Particles on brick break 9. 3 lives, score display 10. Game over and victory screens with restart 11. Self-playing demo mode behind "Click or press a key to start" Let me structure this: - HTML structure with canvas - CSS for styling - JavaScript game engine with: - Game states: demo, playing, gameover, victory - Entities: paddle, ball(s), bricks, particles, power-ups - Collision detection with proper physics - Demo AI for paddle - Input handling I'll make it visually polished with nice colors, gradients, and effects. Let me write the complete code.