Run details
- Model
- nvidia/nemotron-3.5-super-vl:free
- Route
- OpenRouter API, one user message
- Reasoning
- provider default, 12,956 reasoning tokens
- Limits
- 60 min · 131,072 output tokens
- Date
- 21 Oct 2026
- Time
- 1 min 43 s
- Size
- 14 kB
- Tokens
- 94 in, 18,258 out
- List price
- $0.009 ($0.15 in / $0.5 out per M)
- Paid
- $0 (free route)
Exact prompt
Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice. It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Reply with the file in a single ```html code block.
Source
14 kB 540 lines Raw
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Juicy Breakout</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #050508;
color: #fff;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
overflow: hidden;
touch-action: none;
}
canvas {
display: block;
background: #000;
box-shadow: 0 0 40px rgba(0,0,0,0.8);
max-width: 95vw;
max-height: 90vh;
cursor: none;
}
#hud {
position: absolute;
top: 10px;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: 0 20px;
font-size: 18px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
pointer-events: none;
z-index: 5;
text-shadow: 0 0 8px rgba(0,0,0,0.8);
}
.overlay {
position: absolute;
inset: 0;
background: rgba(5, 5, 8, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 50;
text-align: center;
padding: 20px;
}
.hidden { display: none !important; }
h1 { margin: 0 0 10px; font-size: 48px; text-shadow: 0 0 20px #ff0055; }
p { margin-bottom: 25px; opacity: 0.9; line-height: 1.5; }
button {
background: #ff0055;
border: none;
color: #fff;
padding: 12px 32px;
font-size: 18px;
font-weight: bold;
border-radius: 6px;
cursor: pointer;
box-shadow: 0 0 20px rgba(255,0,85,0.5);
transition: transform 0.1s;
}
button:active { transform: scale(0.95); }
button:hover { background: #ff3377; }
</style>
</head>
<body>
<div id="hud">
<span>SCORE: <span id="score">0</span></span>
<span>LEVEL: <span id="level">1</span></span>
<span>LIVES: <span id="lives">3</span></span>
</div>
<canvas id="game" width="800" height="600"></canvas>
<div id="overlay-menu" class="overlay">
<h1>JUICY BREAKOUT</h1>
<p>Arrows / A-D : Move • Space / Click : Launch<br>Mouse also controls paddle</p>
<button onclick="startGame()">Start Game</button>
</div>
<div id="overlay-level" class="overlay hidden">
<h1>Level Cleared!</h1>
<button onclick="nextLevel()">Next Level</button>
</div>
<div id="overlay-gameover" class="overlay hidden">
<h1 id="go-title">Game Over</h1>
<button onclick="restartGame()">Try Again</button>
</div>
<script>
/* ========== GAME CODE ========== */
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const levelEl = document.getElementById('level');
const livesEl = document.getElementById('lives');
const overlayMenu = document.getElementById('overlay-menu');
const overlayLevel = document.getElementById('overlay-level');
const overlayGameOver = document.getElementById('overlay-gameover');
const goTitle = document.getElementById('go-title');
// --- Config ---
const PADDLE_W = 100;
const PADDLE_H = 14;
const BALL_R = 7;
const BRICK_PAD = 6;
// --- State ---
let state = 'MENU'; // MENU | PLAYING | LEVEL_COMPLETE | GAME_OVER
let score = 0;
let lives = 3;
let levelIndex = 0;
let shake = 0;
let audioCtx = null;
const keys = {};
let mouseX = canvas.width / 2;
const paddle = { x: 350, y: canvas.height - 40, w: PADDLE_W, h: PADDLE_H };
const ball = { x: 0, y: 0, r: BALL_R, vx: 0, vy: 0, speed: 4, launched: false };
const bricks = [];
const particles = [];
// --- Levels ---
const LEVELS = [
{
grid: [
[1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1]
],
colors: ['#ff4444', '#ff8800', '#ffdd00', '#00ff88']
},
{
grid: [
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,1,1,1,1,1,1,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
],
colors: ['#ff4444', '#ff8800', '#ffdd00', '#00ff88', '#4444ff']
},
{
grid: [
[0,0,0,0,1,1,0,0,0,0],
[0,0,0,1,1,1,1,0,0,0],
[0,0,1,1,1,1,1,1,0,0],
[0,1,1,1,1,1,1,1,1,0],
[1,1,1,1,1,1,1,1,1,1]
],
colors: ['#ff4444', '#ff8800', '#ffdd00', '#00ff88', '#4444ff']
},
{
grid: [
[1,0,1,0,1,0,1,0,1,0],
[0,1,0,1,0,1,0,1,0,1],
[1,0,1,0,1,0,1,0,1,0],
[0,1,0,1,0,1,0,1,0,1],
[1,0,1,0,1,0,1,0,1,0]
],
colors: ['#ff4444', '#ff8800', '#ffdd00', '#00ff88', '#4444ff']
},
{
grid: [
[1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,1,1,1,0,0,1],
[1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1]
],
colors: ['#ff4444', '#ff8800', '#ffdd00', '#00ff88', '#4444ff']
}
];
// --- Audio ---
function initAudio() {
if (!audioCtx) {
const Ctx = window.AudioContext || window.webkitAudioContext;
if (Ctx) audioCtx = new Ctx();
}
}
function playTone(freq, type = 'square', duration = 0.1, vol = 0.1) {
if (!audioCtx) return;
const t = audioCtx.currentTime;
const o = audioCtx.createOscillator();
const g = audioCtx.createGain();
o.type = type;
o.frequency.setValueAtTime(freq, t);
g.gain.setValueAtTime(vol, t);
g.gain.exponentialRampToValueAtTime(0.001, t + duration);
o.connect(g);
g.connect(audioCtx.destination);
o.start(t);
o.stop(t + duration);
}
// --- Helpers ---
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
function getMouseX(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
return (e.clientX - rect.left) * scaleX;
}
function updateHUD() {
scoreEl.textContent = score;
levelEl.textContent = levelIndex + 1;
livesEl.textContent = lives;
}
// --- Level loading ---
function loadLevel(idx) {
bricks.length = 0;
const lvl = LEVELS[idx % LEVELS.length];
const rows = lvl.grid.length;
const cols = lvl.grid[0].length;
const bw = 60, bh = 20;
const totalW = cols * bw + (cols - 1) * BRICK_PAD;
const startX = (canvas.width - totalW) / 2;
const startY = 50;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (lvl.grid[r][c]) {
bricks.push({
x: startX + c * (bw + BRICK_PAD),
y: startY + r * (bh + BRICK_PAD),
w: bw,
h: bh,
color: lvl.colors[r % lvl.colors.length],
alive: true
});
}
}
}
resetBall();
}
function resetBall() {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 2;
ball.vx = 0;
ball.vy = 0;
ball.launched = false;
}
function launchBall() {
if (ball.launched) return;
const baseSpeed = 4 + levelIndex * 0.4;
const dir = Math.random() < 0.5 ? -1 : 1;
ball.vx = dir * 3;
ball.vy = -baseSpeed;
ball.launched = true;
}
function spawnParticles(x, y, color, count) {
for (let i = 0; i < count; i++) {
particles.push({
x, y,
vx: (Math.random() - 0.5) * 12,
vy: (Math.random() - 0.5) * 12,
life: 1.0,
color,
size: Math.random() * 4 + 2
});
}
}
// --- Input ---
window.addEventListener('keydown', e => {
keys[e.key] = true;
if (['ArrowLeft','ArrowRight',' '].includes(e.key)) e.preventDefault();
if (e.key === ' ' && state === 'PLAYING' && !ball.launched) launchBall();
if ((e.key === ' ' || e.key === 'Enter') && state === 'MENU') startGame();
if ((e.key === ' ' || e.key === 'Enter') && state === 'LEVEL_COMPLETE') nextLevel();
if ((e.key === ' ' || e.key === 'Enter') && state === 'GAME_OVER') restartGame();
});
window.addEventListener('keyup', e => { keys[e.key] = false; });
window.addEventListener('mousemove', e => { mouseX = getMouseX(e); });
window.addEventListener('mousedown', e => {
if (state === 'MENU') { startGame(); return; }
if (state === 'LEVEL_COMPLETE') { nextLevel(); return; }
if (state === 'GAME_OVER') { restartGame(); return; }
if (state === 'PLAYING' && !ball.launched) launchBall();
});
// --- Game logic ---
function update() {
initAudio();
if (state === 'PLAYING') {
// Paddle
let ax = 0;
if (keys['ArrowLeft'] || keys['a'] || keys['A']) ax = -8;
if (keys['ArrowRight'] || keys['d'] || keys['D']) ax = 8;
if (ax !== 0) {
paddle.x += ax;
} else {
paddle.x = mouseX - paddle.w / 2;
}
paddle.x = clamp(paddle.x, 0, canvas.width - paddle.w);
if (!ball.launched) {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 2;
} else {
ball.x += ball.vx;
ball.y += ball.vy;
// Ball trail juice
if (Math.random() < 0.6) {
particles.push({
x: ball.x, y: ball.y,
vx: -ball.vx * 0.15, vy: -ball.vy * 0.15,
life: 0.35, color: '#fff', size: Math.random() * 2 + 1
});
}
// Walls
if (ball.x - ball.r < 0) { ball.x = ball.r; ball.vx *= -1; playTone(80, 'square', 0.04, 0.05); shake = 4; }
if (ball.x + ball.r > canvas.width) { ball.x = canvas.width - ball.r; ball.vx *= -1; playTone(80, 'square', 0.04, 0.05); shake = 4; }
if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy *= -1; playTone(110, 'square', 0.04, 0.05); shake = 4; }
// Paddle collision
if (ball.vy > 0 &&
ball.x >= paddle.x && ball.x <= paddle.x + paddle.w &&
ball.y + ball.r >= paddle.y && ball.y - ball.r <= paddle.y + paddle.h) {
const hitFactor = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
ball.vx = hitFactor * 7;
ball.vy = -Math.abs(ball.vy);
const spd = Math.hypot(ball.vx, ball.vy) * 1.03;
const ang = Math.atan2(ball.vy, ball.vx);
ball.vx = Math.cos(ang) * spd;
ball.vy = Math.sin(ang) * spd;
playTone(180, 'sine', 0.1, 0.1);
shake = 6;
}
// Bricks
for (let b of bricks) {
if (!b.alive) continue;
const cx = clamp(ball.x, b.x, b.x + b.w);
const cy = clamp(ball.y, b.y, b.y + b.h);
const dx = ball.x - cx;
const dy = ball.y - cy;
if (dx * dx + dy * dy < ball.r * ball.r) {
b.alive = false;
score += 10;
updateHUD();
spawnParticles(b.x + b.w / 2, b.y + b.h / 2, b.color, 10);
playTone(300 + Math.random() * 400, 'square', 0.08, 0.1);
shake = 10;
const overlapX = ball.r - Math.abs(dx);
const overlapY = ball.r - Math.abs(dy);
if (overlapX < overlapY) {
ball.vx *= -1;
ball.x += dx > 0 ? overlapX : -overlapX;
} else {
ball.vy *= -1;
ball.y += dy > 0 ? overlapY : -overlapY;
}
break; // one brick per frame
}
}
// Death
if (ball.y - ball.r > canvas.height) {
lives--;
updateHUD();
playTone(60, 'sawtooth', 0.4, 0.1);
shake = 25;
if (lives <= 0) {
state = 'GAME_OVER';
goTitle.textContent = 'GAME OVER';
overlayGameOver.classList.remove('hidden');
} else {
resetBall();
}
}
}
// Level clear
if (bricks.every(b => !b.alive)) {
state = 'LEVEL_COMPLETE';
overlayLevel.classList.remove('hidden');
playTone(500, 'square', 0.1, 0.1);
setTimeout(() => playTone(620, 'square', 0.1, 0.1), 120);
setTimeout(() => playTone(800, 'square', 0.25, 0.1), 240);
}
}
// Particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.vx *= 0.94;
p.vy *= 0.94;
p.life -= 0.05;
if (p.life <= 0) particles.splice(i, 1);
}
if (shake > 0) shake *= 0.92;
else shake = 0;
}
// --- Draw ---
function draw() {
const sx = shake > 0 ? (Math.random() - 0.5) * shake : 0;
const sy = shake > 0 ? (Math.random() - 0.5) * shake : 0;
ctx.save();
ctx.translate(sx, sy);
// background
ctx.fillStyle = '#050508';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// faint grid
ctx.strokeStyle = '#111';
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();
}
// bricks
for (const b of bricks) {
if (!b.alive) continue;
const g = ctx.createLinearGradient(b.x, b.y, b.x, b.y + b.h);
g.addColorStop(0, b.color);
g.addColorStop(1, '#000');
ctx.fillStyle = g;
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.strokeStyle = 'rgba(255,255,255,0.4)';
ctx.lineWidth = 2;
ctx.strokeRect(b.x + 1.5, b.y + 1.5, b.w - 3, b.h - 3);
}
// paddle
const pg = ctx.createLinearGradient(paddle.x, paddle.y, paddle.x, paddle.y + paddle.h);
pg.addColorStop(0, '#ffffff');
pg.addColorStop(1, '#aaaaaa');
ctx.fillStyle = pg;
ctx.fillRect(paddle.x, paddle.y, paddle.w, paddle.h);
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.strokeRect(paddle.x, paddle.y, paddle.w, paddle.h);
// ball
const glow = 10;
const ballGrad = ctx.createRadialGradient(ball.x, ball.y, 0, ball.x, ball.y, ball.r + glow);
ballGrad.addColorStop(0, '#fff');
ballGrad.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = ballGrad;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r + glow, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
// particles
for (const p of particles) {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.fillRect(p.x - p.size / 2, p.y - p.size / 2, p.size, p.size);
ctx.globalAlpha = 1;
}
ctx.restore();
}
// --- Loop ---
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
// --- Public functions for buttons ---
function startGame() {
initAudio();
state = 'PLAYING';
score = 0;
lives = 3;
levelIndex = 0;
scoreEl.textContent = '0';
livesEl.textContent = '3';
levelEl.textContent = '1';
overlayMenu.classList.add('hidden');
overlayLevel.classList.add('hidden');
overlayGameOver.classList.add('hidden');
loadLevel(0);
}
function nextLevel() {
overlayLevel.classList.add('hidden');
levelIndex++;
if (levelIndex >= LEVELS.length) {
state = 'GAME_OVER';
goTitle.textContent = 'YOU WIN!';
overlayGameOver.classList.remove('hidden');
} else {
state = 'PLAYING';
levelEl.textContent = levelIndex + 1;
loadLevel(levelIndex);
}
}
function restartGame() {
overlayGameOver.classList.add('hidden');
startGame();
}
// Start
loop();
</script>
</body>
</html>

