Run details
- Model
- openai/gpt-6.1-terra-pro
- Route
- OpenRouter API, one user message
- Served by
- OpenAI
- Effort
- medium, OpenRouter's default for this model
- Limits
- 60 min · 128,000 output tokens (the model's limit)
- Date
- 27 Oct 2026
- Time
- 1 min 42 s
- Size
- 21 kB
- Tokens
- 17,315 in, 16,398 out (2,283 of them thinking)
- List price
- $0.099 at today's price ($1 in / $5 out per M)
- Paid
- $0.099
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
21 kB 718 lines Raw
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Neon Breakout</title>
<style>
:root {
color-scheme: dark;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #080b18;
color: #f4f6ff;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 20px;
background:
radial-gradient(circle at 20% 0%, #202050 0, transparent 35%),
radial-gradient(circle at 90% 90%, #102b42 0, transparent 35%),
#080b18;
}
.app { width: min(100%, 1020px); }
header {
display: flex;
align-items: end;
justify-content: space-between;
gap: 16px;
margin-bottom: 14px;
}
h1 {
margin: 0;
font-size: clamp(1.35rem, 4vw, 2.1rem);
letter-spacing: .11em;
line-height: 1;
}
h1 span { color: #71f4ee; }
.subtitle {
color: #a0a8c8;
font-size: .82rem;
margin-top: 7px;
}
.toolbar { display: flex; gap: 8px; }
button {
border: 1px solid #465172;
border-radius: 9px;
background: #1a2038;
color: #eef4ff;
padding: 9px 13px;
font: inherit;
font-size: .83rem;
font-weight: 700;
cursor: pointer;
}
button:hover { background: #2b3555; }
button:focus-visible { outline: 3px solid #71f4ee; outline-offset: 3px; }
.hud {
display: flex;
align-items: center;
gap: 24px;
padding: 12px 17px;
border: 1px solid #333b5a;
border-bottom: 0;
border-radius: 14px 14px 0 0;
background: #141a30;
}
.stat { display: flex; align-items: baseline; gap: 7px; }
.label {
color: #8d98bb;
font-size: .68rem;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
}
.value { font-weight: 800; font-variant-numeric: tabular-nums; }
#lives { color: #ff799e; }
#combo { color: #ffd574; margin-left: auto; }
.game-wrap {
position: relative;
overflow: hidden;
border: 1px solid #333b5a;
border-radius: 0 0 14px 14px;
background: #0b1022;
box-shadow: 0 20px 70px #0008;
}
canvas {
display: block;
width: 100%;
aspect-ratio: 8 / 5;
touch-action: none;
cursor: crosshair;
}
.overlay {
position: absolute;
inset: 0;
display: grid;
place-items: center;
padding: 20px;
text-align: center;
background: #080c1acc;
backdrop-filter: blur(5px);
}
.overlay[hidden] { display: none; }
.card {
max-width: 440px;
padding: 26px;
border: 1px solid #4b587b;
border-radius: 18px;
background: linear-gradient(145deg, #202944, #141a30);
box-shadow: 0 20px 65px #0008;
}
.eyebrow {
color: #71f4ee;
font-size: .72rem;
font-weight: 900;
letter-spacing: .18em;
text-transform: uppercase;
}
.card h2 { margin: 10px 0; font-size: clamp(1.5rem, 4vw, 2.3rem); }
.card p { color: #bcc5e1; line-height: 1.55; margin: 0 0 18px; }
.primary {
color: #091323;
background: #71f4ee;
border-color: #71f4ee;
padding: 12px 24px;
font-size: .95rem;
}
.primary:hover { background: #adfff9; }
.help {
display: flex;
justify-content: space-between;
gap: 12px;
color: #8f9ab9;
font-size: .78rem;
line-height: 1.5;
margin-top: 12px;
}
@media (max-width: 560px) {
body { padding: 10px; }
header { align-items: center; }
.subtitle { display: none; }
.hud { gap: 12px; padding: 10px; }
.stat { gap: 4px; }
.label { font-size: .59rem; }
.value { font-size: .9rem; }
.help { display: block; }
.card { padding: 18px; }
.card p { font-size: .88rem; }
}
</style>
</head>
<body>
<main class="app">
<header>
<div>
<h1>NEON <span>BREAKOUT</span></h1>
<div class="subtitle">Five levels. Three lives. One more bounce.</div>
</div>
<div class="toolbar">
<button id="pauseButton" type="button" aria-label="Pause game">Pause</button>
<button id="soundButton" type="button" aria-pressed="true">Sound: on</button>
</div>
</header>
<div class="hud" aria-label="Game statistics">
<div class="stat"><span class="label">Score</span><span class="value" id="score">0</span></div>
<div class="stat"><span class="label">Level</span><span class="value" id="level">1 / 5</span></div>
<div class="stat"><span class="label">Lives</span><span class="value" id="lives">♥ ♥ ♥</span></div>
<div class="stat" id="combo"></div>
</div>
<div class="game-wrap">
<canvas id="game" width="960" height="600" aria-label="Breakout game board"></canvas>
<div class="overlay" id="overlay">
<div class="card">
<div class="eyebrow" id="overlayEyebrow">Ready to play?</div>
<h2 id="overlayTitle">Break the glow.</h2>
<p id="overlayText">Move with your mouse or the arrow keys. Click or press Space to launch. Clear all five levels without losing your three lives.</p>
<button class="primary" id="actionButton" type="button">Start game</button>
</div>
</div>
</div>
<div class="help">
<span>Move: mouse, touch, ← → or A / D · Launch: click or Space</span>
<span>Pause: P or Esc</span>
</div>
</main>
<script>
(() => {
"use strict";
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height;
const scoreEl = document.getElementById("score");
const levelEl = document.getElementById("level");
const livesEl = document.getElementById("lives");
const comboEl = document.getElementById("combo");
const overlay = document.getElementById("overlay");
const overlayEyebrow = document.getElementById("overlayEyebrow");
const overlayTitle = document.getElementById("overlayTitle");
const overlayText = document.getElementById("overlayText");
const actionButton = document.getElementById("actionButton");
const pauseButton = document.getElementById("pauseButton");
const soundButton = document.getElementById("soundButton");
const colors = ["#ff6b9a", "#ff9b71", "#ffd36e", "#75ead0", "#79bcff", "#b895ff", "#f58cff"];
const stars = Array.from({ length: 65 }, () => ({
x: Math.random() * W, y: Math.random() * H,
r: .5 + Math.random() * 1.3, a: .12 + Math.random() * .35
}));
let state = "menu";
let pausedState = "ready";
let level = 1, score = 0, lives = 3, combo = 0;
let bricks = [], particles = [], popups = [], trail = [];
let shake = 0, flash = 0;
let soundOn = true, audio = null;
let lastTime = 0, clock = 0;
const keys = new Set();
const paddle = { x: W / 2, target: W / 2, y: 548, w: 130, h: 16 };
const ball = { x: W / 2, y: 0, vx: 0, vy: 0, r: 9 };
function sound(freq, duration = .08, type = "sine", volume = .045, endFreq = freq) {
if (!soundOn) return;
try {
const AudioClass = window.AudioContext || window.webkitAudioContext;
if (!AudioClass) return;
if (!audio) audio = new AudioClass();
if (audio.state === "suspended") audio.resume().catch(() => {});
const now = audio.currentTime;
const osc = audio.createOscillator();
const gain = audio.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, now);
osc.frequency.exponentialRampToValueAtTime(Math.max(1, endFreq), now + duration);
gain.gain.setValueAtTime(volume, now);
gain.gain.exponentialRampToValueAtTime(.001, now + duration);
osc.connect(gain);
gain.connect(audio.destination);
osc.start(now);
osc.stop(now + duration);
} catch (_) {}
}
function updateHUD() {
scoreEl.textContent = score.toLocaleString();
levelEl.textContent = level + " / 5";
livesEl.textContent = "♥ ".repeat(lives).trim() || "—";
comboEl.textContent = combo >= 2 ? "×" + combo + " COMBO" : "";
}
function showOverlay(eyebrow, title, text, buttonText, callback) {
overlayEyebrow.textContent = eyebrow;
overlayTitle.textContent = title;
overlayText.textContent = text;
actionButton.textContent = buttonText;
actionButton.onclick = callback;
overlay.hidden = false;
}
function hideOverlay() {
overlay.hidden = true;
canvas.focus();
}
function makeLevel(n) {
const rows = [5, 6, 6, 7, 7][n - 1];
const cols = 12, gap = 7, left = 42;
const width = (W - left * 2 - gap * (cols - 1)) / cols;
const result = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
let include = true;
if (n === 2) include = !(row === 0 && (col < 2 || col > 9));
if (n === 3) include = !((row + col) % 5 === 0);
if (n === 4) include = !(row % 2 === 1 && (col === 0 || col === 11 || col === 5 || col === 6));
if (n === 5) include = !((row === 0 || row === 6) && (col < 2 || col > 9));
if (!include) continue;
let hp = 1;
if (n >= 2 && row < 2 && (col + row + n) % 3 === 0) hp = 2;
if (n >= 4 && row === 0 && col % 3 === 1) hp = 3;
result.push({
x: left + col * (width + gap),
y: 65 + row * 29,
w: width, h: 22, hp, maxHp: hp,
color: colors[(row + n - 1) % colors.length]
});
}
}
return result;
}
function resetBall() {
paddle.x = paddle.target = W / 2;
ball.x = paddle.x;
ball.y = paddle.y - ball.r - 2;
ball.vx = ball.vy = 0;
trail = [];
state = "ready";
pauseButton.textContent = "Pause";
}
function startLevel() {
bricks = makeLevel(level);
particles = [];
popups = [];
combo = 0;
paddle.w = Math.max(100, 130 - (level - 1) * 6);
resetBall();
updateHUD();
hideOverlay();
}
function newGame() {
level = 1;
score = 0;
lives = 3;
startLevel();
}
function launch() {
if (state !== "ready") return;
const speed = 400 + (level - 1) * 25;
ball.vx = speed * (Math.random() < .5 ? -.4 : .4);
ball.vy = -Math.sqrt(speed * speed - ball.vx * ball.vx);
state = "playing";
sound(420, .12, "sine", .05, 700);
}
function togglePause() {
if (state === "playing" || state === "ready") {
pausedState = state;
state = "paused";
keys.clear();
pauseButton.textContent = "Resume";
} else if (state === "paused") {
state = pausedState;
pauseButton.textContent = "Pause";
}
}
function burst(x, y, color, count = 12, power = 1) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = (60 + Math.random() * 230) * power;
const life = .3 + Math.random() * .5;
particles.push({
x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed,
life, maxLife: life, size: 2 + Math.random() * 4, color
});
}
if (particles.length > 450) particles.splice(0, particles.length - 450);
}
function loseLife() {
lives--;
combo = 0;
shake = 12;
flash = .28;
burst(ball.x, H - 15, "#ff6b9a", 20);
sound(220, .35, "sawtooth", .035, 65);
updateHUD();
if (lives <= 0) {
state = "gameover";
showOverlay("Game over", "Nice run.", "Final score: " + score.toLocaleString() + ". Ready for another shot?", "Play again", newGame);
} else {
resetBall();
}
}
function clearLevel() {
state = "levelclear";
score += level * 100;
updateHUD();
sound(520, .18, "sine", .05, 880);
setTimeout(() => sound(780, .25, "sine", .04, 1100), 120);
if (level === 5) {
state = "won";
showOverlay("All levels cleared", "You broke the glow!", "Brilliant work. Final score: " + score.toLocaleString() + ".", "Play again", newGame);
} else {
showOverlay("Level " + level + " complete", "Looking good.", "Level bonus: " + (level * 100) + " points. Get ready for the next round.", "Next level", () => {
level++;
startLevel();
});
}
}
function hitBrick(brick, index) {
brick.hp--;
score += 10;
burst(ball.x, ball.y, brick.color, brick.hp ? 5 : 13, brick.hp ? .6 : 1);
sound(brick.hp ? 350 : 480 + Math.min(combo, 10) * 30, .07, "triangle", .035, 650);
if (brick.hp <= 0) {
bricks.splice(index, 1);
combo++;
const bonus = 20 + Math.min(combo - 1, 10) * 5;
score += bonus;
popups.push({ x: brick.x + brick.w / 2, y: brick.y, text: "+" + (10 + bonus), life: .8 });
shake = Math.min(8, shake + 2);
if (!bricks.length) clearLevel();
}
updateHUD();
}
function updateBall(dt) {
const speed = Math.hypot(ball.vx, ball.vy);
const steps = Math.max(1, Math.ceil(speed * dt / (ball.r * .55)));
const step = dt / steps;
for (let s = 0; s < steps && state === "playing"; s++) {
const oldY = ball.y;
ball.x += ball.vx * step;
ball.y += ball.vy * step;
if (ball.x < ball.r) {
ball.x = ball.r;
ball.vx = Math.abs(ball.vx);
sound(240, .035, "sine", .025);
} else if (ball.x > W - ball.r) {
ball.x = W - ball.r;
ball.vx = -Math.abs(ball.vx);
sound(240, .035, "sine", .025);
}
if (ball.y < ball.r) {
ball.y = ball.r;
ball.vy = Math.abs(ball.vy);
sound(270, .035, "sine", .025);
}
if (ball.vy > 0 && oldY + ball.r <= paddle.y + 3 &&
ball.y + ball.r >= paddle.y &&
ball.x >= paddle.x - paddle.w / 2 - ball.r &&
ball.x <= paddle.x + paddle.w / 2 + ball.r) {
ball.y = paddle.y - ball.r;
const offset = Math.max(-1, Math.min(1, (ball.x - paddle.x) / (paddle.w / 2)));
const currentSpeed = Math.min(650, Math.hypot(ball.vx, ball.vy) + 5);
const angle = offset * 1.08;
ball.vx = currentSpeed * Math.sin(angle);
ball.vy = -currentSpeed * Math.cos(angle);
combo = 0;
updateHUD();
burst(ball.x, paddle.y, "#71f4ee", 7, .5);
sound(320, .07, "triangle", .04, 450);
}
for (let i = bricks.length - 1; i >= 0; i--) {
const b = bricks[i];
const nearX = Math.max(b.x, Math.min(ball.x, b.x + b.w));
const nearY = Math.max(b.y, Math.min(ball.y, b.y + b.h));
const dx = ball.x - nearX, dy = ball.y - nearY;
if (dx * dx + dy * dy > ball.r * ball.r) continue;
const overlapLeft = ball.x + ball.r - b.x;
const overlapRight = b.x + b.w - (ball.x - ball.r);
const overlapTop = ball.y + ball.r - b.y;
const overlapBottom = b.y + b.h - (ball.y - ball.r);
const minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
if (minOverlap === overlapLeft) {
ball.x = b.x - ball.r;
ball.vx = -Math.abs(ball.vx);
} else if (minOverlap === overlapRight) {
ball.x = b.x + b.w + ball.r;
ball.vx = Math.abs(ball.vx);
} else if (minOverlap === overlapTop) {
ball.y = b.y - ball.r;
ball.vy = -Math.abs(ball.vy);
} else {
ball.y = b.y + b.h + ball.r;
ball.vy = Math.abs(ball.vy);
}
hitBrick(b, i);
break;
}
if (ball.y > H + ball.r) {
loseLife();
break;
}
}
}
function update(dt) {
clock += dt;
if (state === "playing" || state === "ready") {
const direction = (keys.has("arrowright") || keys.has("d") ? 1 : 0) -
(keys.has("arrowleft") || keys.has("a") ? 1 : 0);
if (direction) paddle.target += direction * 760 * dt;
paddle.target = Math.max(paddle.w / 2, Math.min(W - paddle.w / 2, paddle.target));
const distance = paddle.target - paddle.x;
paddle.x += Math.max(-950 * dt, Math.min(950 * dt, distance));
if (state === "ready") {
ball.x = paddle.x;
ball.y = paddle.y - ball.r - 2;
} else {
updateBall(dt);
trail.push({ x: ball.x, y: ball.y, life: .18 });
if (trail.length > 15) trail.shift();
}
}
if (state !== "paused") {
for (const t of trail) t.life -= dt;
trail = trail.filter(t => t.life > 0);
for (const p of particles) {
p.life -= dt;
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vy += 180 * dt;
p.vx *= Math.pow(.18, dt);
}
particles = particles.filter(p => p.life > 0);
for (const p of popups) {
p.life -= dt;
p.y -= 45 * dt;
}
popups = popups.filter(p => p.life > 0);
shake = Math.max(0, shake - 25 * dt);
flash = Math.max(0, flash - dt);
}
}
function roundedRect(x, y, w, h, r) {
r = Math.min(r, w / 2, 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 draw() {
ctx.clearRect(0, 0, W, H);
const bg = ctx.createLinearGradient(0, 0, 0, H);
bg.addColorStop(0, "#151a35");
bg.addColorStop(1, "#090e1d");
ctx.fillStyle = bg;
ctx.fillRect(0, 0, W, H);
for (const star of stars) {
ctx.globalAlpha = star.a * (.7 + .3 * Math.sin(clock * 1.5 + star.x));
ctx.fillStyle = "#a8caff";
ctx.fillRect(star.x, star.y, star.r, star.r);
}
ctx.globalAlpha = 1;
ctx.save();
if (shake > 0) ctx.translate((Math.random() - .5) * shake, (Math.random() - .5) * shake);
ctx.strokeStyle = "#ffffff0a";
ctx.lineWidth = 1;
for (let x = 0; x < W; x += 60) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
}
for (let y = 0; y < H; y += 60) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
}
for (const b of bricks) {
ctx.shadowColor = b.color;
ctx.shadowBlur = 10;
ctx.fillStyle = b.color;
roundedRect(b.x, b.y, b.w, b.h, 5);
ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = "#ffffff55";
roundedRect(b.x + 3, b.y + 3, b.w - 6, 3, 2);
ctx.fill();
if (b.hp > 1) {
ctx.fillStyle = "#11172c99";
for (let i = 0; i < b.hp; i++) {
ctx.beginPath();
ctx.arc(b.x + b.w / 2 + (i - (b.hp - 1) / 2) * 8, b.y + 14, 2, 0, Math.PI * 2);
ctx.fill();
}
}
}
for (const t of trail) {
ctx.globalAlpha = Math.max(0, t.life / .18) * .35;
ctx.fillStyle = "#a7ffff";
ctx.beginPath(); ctx.arc(t.x, t.y, ball.r * (t.life / .18), 0, Math.PI * 2); ctx.fill();
}
ctx.globalAlpha = 1;
ctx.shadowColor = "#71f4ee";
ctx.shadowBlur = 22;
ctx.fillStyle = "#71f4ee";
roundedRect(paddle.x - paddle.w / 2, paddle.y, paddle.w, paddle.h, 8);
ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = "#e6ffff";
roundedRect(paddle.x - paddle.w / 2 + 9, paddle.y + 3, paddle.w - 18, 3, 2);
ctx.fill();
ctx.shadowColor = "#9affff";
ctx.shadowBlur = 20;
ctx.fillStyle = "#f1ffff";
ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2); ctx.fill();
ctx.shadowBlur = 0;
for (const p of particles) {
ctx.globalAlpha = Math.max(0, p.life / p.maxLife);
ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, p.size, p.size);
}
ctx.globalAlpha = 1;
ctx.textAlign = "center";
ctx.font = "bold 15px system-ui, sans-serif";
for (const p of popups) {
ctx.globalAlpha = Math.min(1, p.life * 2);
ctx.fillStyle = "#ffffff";
ctx.fillText(p.text, p.x, p.y);
}
ctx.globalAlpha = 1;
ctx.restore();
if (flash > 0) {
ctx.fillStyle = "rgba(255, 75, 120, " + (flash * .5) + ")";
ctx.fillRect(0, 0, W, H);
}
if (state === "ready" || state === "paused") {
ctx.textAlign = "center";
ctx.font = "700 19px system-ui, sans-serif";
ctx.fillStyle = "#d8e6ff";
ctx.fillText(state === "paused" ? "PAUSED" : "CLICK OR PRESS SPACE TO LAUNCH", W / 2, H - 72);
}
}
function frame(time) {
const dt = lastTime ? Math.min((time - lastTime) / 1000, .033) : 0;
lastTime = time;
update(dt);
draw();
requestAnimationFrame(frame);
}
function movePointer(event) {
const rect = canvas.getBoundingClientRect();
paddle.target = (event.clientX - rect.left) * W / rect.width;
}
canvas.tabIndex = 0;
canvas.addEventListener("pointermove", movePointer);
canvas.addEventListener("pointerdown", event => {
movePointer(event);
if (event.pointerType === "touch") canvas.setPointerCapture(event.pointerId);
if (state === "ready") launch();
});
window.addEventListener("keydown", event => {
const key = event.key.toLowerCase();
if (["arrowleft", "arrowright", " ", "a", "d"].includes(key)) {
if (overlay.hidden) event.preventDefault();
keys.add(key);
}
if (event.repeat) return;
if (key === " " && overlay.hidden) launch();
if ((key === "p" || key === "escape") && overlay.hidden) {
event.preventDefault();
togglePause();
}
});
window.addEventListener("keyup", event => keys.delete(event.key.toLowerCase()));
window.addEventListener("blur", () => {
keys.clear();
if (state === "playing") togglePause();
});
pauseButton.addEventListener("click", togglePause);
soundButton.addEventListener("click", () => {
soundOn = !soundOn;
soundButton.textContent = "Sound: " + (soundOn ? "on" : "off");
soundButton.setAttribute("aria-pressed", String(soundOn));
if (soundOn) sound(520, .08);
});
actionButton.onclick = newGame;
updateHUD();
resetBall();
state = "menu";
requestAnimationFrame(frame);
})();
</script>
</body>
</html>

