Run details
- Model
- z-ai/glm-5.3
- Route
- OpenRouter API, one user message
- Served by
- Wafer
- Effort
- max, OpenRouter's default for this model
- Limits
- 60 min · 943,718 output tokens (the model's limit)
- Date
- 24 Oct 2026
- Time
- 31 s
- Size
- 18 kB
- Tokens
- 88 in, 6,709 out (1,896 of them thinking)
- List price
- $0.024 at today's price ($0.08 in / $3.6 out per M)
- Paid
- $0.011
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
18 kB 556 lines Raw
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEON BREAKER</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
height: 100%;
background: #0a0a12;
overflow: hidden;
font-family: 'Courier New', monospace;
user-select: none;
}
#wrap {
position: relative;
width: 100vw; height: 100vh;
display: flex; align-items: center; justify-content: center;
}
canvas { display: block; }
#hud {
position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
display: flex; gap: 42px;
color: #e8e8f0; font-size: 15px; letter-spacing: 3px;
font-weight: bold; pointer-events: none; text-transform: uppercase;
text-shadow: 0 0 12px rgba(120,140,255,.35);
}
#hud span b { color: #ffd166; }
#msg {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%,-50%);
color: #fff; text-align: center; pointer-events: none;
font-size: 34px; letter-spacing: 8px; font-weight: bold;
text-shadow: 0 0 24px rgba(150,120,255,.7), 0 0 60px rgba(150,120,255,.4);
transition: opacity .4s;
white-space: pre-line; line-height: 1.7;
}
#msg small { font-size: 14px; letter-spacing: 4px; color: #a9a9c8; display:block; }
</style>
</head>
<body>
<div id="wrap">
<canvas id="c"></canvas>
<div id="hud">
<span>Score <b id="score">0</b></span>
<span>Level <b id="level">1</b></span>
<span>Lives <b id="lives">3</b></span>
<span>Best <b id="best">0</b></span>
</div>
<div id="msg"></div>
</div>
<script>
(() => {
"use strict";
// ---------- Setup ----------
const cvs = document.getElementById('c');
const ctx = cvs.getContext('2d');
const W = 900, H = 640;
cvs.width = W; cvs.height = H;
function fit() {
const s = Math.min(innerWidth / W, innerHeight / H) * 0.96;
cvs.style.width = W * s + 'px';
cvs.style.height = H * s + 'px';
}
addEventListener('resize', fit); fit();
const scoreEl = document.getElementById('score');
const levelEl = document.getElementById('level');
const livesEl = document.getElementById('lives');
const bestEl = document.getElementById('best');
const msgEl = document.getElementById('msg');
let best = +localStorage.getItem('nb_best') || 0;
bestEl.textContent = best;
// ---------- Audio (WebAudio, no assets) ----------
let AC = null;
function audio() { if (!AC) AC = new (window.AudioContext || window.webkitAudioContext)(); return AC; }
function beep(freq, dur = .08, type = 'square', vol = .12, slide = 0) {
try {
const a = audio(), t = a.currentTime;
const o = a.createOscillator(), g = a.createGain();
o.type = type; o.frequency.setValueAtTime(freq, t);
if (slide) o.frequency.exponentialRampToValueAtTime(Math.max(30, freq + slide), t + dur);
g.gain.setValueAtTime(vol, t);
g.gain.exponentialRampToValueAtTime(.0001, t + dur);
o.connect(g); g.connect(a.destination);
o.start(t); o.stop(t + dur + .02);
} catch(e){}
}
const SFX = {
hit: () => beep(520 + Math.random()*160, .05, 'square', .09),
paddle:() => beep(220, .07, 'triangle', .14, 60),
wall: () => beep(180, .04, 'square', .06),
brick: (n) => beep(300 + Math.min(n,10)*45, .07, 'square', .1),
die: () => beep(300, .5, 'sawtooth', .16, -240),
level: () => { beep(440,.12,'square',.1); setTimeout(()=>beep(660,.12,'square',.1),120); setTimeout(()=>beep(880,.2,'square',.1),240); },
power: () => beep(700,.15,'sine',.14, 400),
};
// ---------- Level data ----------
const PALETTE = ['#ff5c7a','#ffb86c','#ffe66d','#5ef3ab','#59c3ff','#c17bff'];
// rows bottom-up per level; digits = hits needed (0 = empty)
const LEVELS = [
["00001111111100000000",
"00001111111100000000",
"00000111111100000000",
"00000001111100000000"],
["01010101010101010101",
"10101010101010101010",
"01010101010101010101"],
["00000011111100000000",
"00111111111111110000",
"11111111111111111111",
"00111111111111110000",
"00000011111100000000"],
["11111100000000111111",
"11110000111100001111",
"11100011111100001111",
"11000111111100011111",
"10001111111111001111"],
["00200200200200200200",
"01210121012102101210",
"00200200200200200200"],
["00000111111100000000",
"00011122222111000000",
"00112222222221100000",
"01112222222221110000",
"11111222222211111000",
"00000111111100000000"],
];
const COLS = 20, BW = W / COLS, BH = 26, TOP = 70;
let state, score, lives, level, combo, shake, flashT, particles, powerups, floats;
function makeLevel(n) {
const pat = LEVELS[n % LEVELS.length];
const bricks = [];
pat.forEach((row, ri) => {
for (let ci = 0; ci < COLS; ci++) {
const v = +row[ci];
if (v > 0) bricks.push({
x: ci * BW, y: TOP + ri * (BH + 4),
w: BW - 4, h: BH,
hp: v, max: v,
color: PALETTE[(ri + n) % PALETTE.length],
alive: true, wobble: 0
});
}
});
return bricks;
}
function reset() {
score = 0; lives = 3; level = 0;
newBoard();
}
function newBoard() {
state.bricks = makeLevel(level);
state.balls = [];
state.paddle = { x: W/2, w: 130, targetW: 130, h: 16 };
powerups = [];
addBall(true);
state.stuck = true;
combo = 0;
}
function addBall(held) {
state.balls.push({ x: state.paddle.x, y: H - 60, vx: 0, vy: 0, r: 8, held: !!held, trail: [] });
}
// ---------- Input ----------
const keys = {};
addEventListener('keydown', e => {
keys[e.code] = true;
if (e.code === 'Space') { launch(); e.preventDefault(); }
if (e.code === 'KeyP') paused = !paused;
});
addEventListener('keyup', e => keys[e.code] = false);
cvs.addEventListener('mousemove', e => {
const r = cvs.getBoundingClientRect();
mouse.x = (e.clientX - r.left) / r.width * W;
mouse.active = true;
});
cvs.addEventListener('mousedown', launch);
cvs.addEventListener('touchmove', e => {
const r = cvs.getBoundingClientRect();
mouse.x = (e.touches[0].clientX - r.left) / r.width * W;
mouse.active = true; e.preventDefault();
}, {passive:false});
cvs.addEventListener('touchstart', e => { cvs.dispatchEvent(new MouseEvent('mousemove',{clientX:e.touches[0].clientX,clientY:e.touches[0].clientY})); launch(); });
const mouse = { x: W/2, active: false };
let paused = false;
function launch() {
audio();
if (msgMode !== 'play') { if (msgMode !== 'start') { reset(); startLevel(); } return; }
state.balls.forEach(b => {
if (b.held) {
b.held = false;
const ang = -Math.PI/2 + (Math.random()-.5) * .5;
const sp = baseSpeed();
b.vx = Math.cos(ang)*sp; b.vy = Math.sin(ang)*sp;
SFX.paddle();
}
});
}
function baseSpeed() { return 6.2 + level * 0.45; }
function showMessage(title, sub, mode) {
msgEl.innerHTML = title + (sub ? `\n<small>${sub}</small>` : '');
msgEl.style.opacity = 1;
msgMode = mode;
}
function hideMsg() { msgEl.style.opacity = 0; msgMode = 'play'; }
let msgMode = 'start';
// ---------- Particles / juice ----------
function burst(x, y, color, n = 14, spd = 5) {
for (let i = 0; i < n; i++) {
const a = Math.random() * Math.PI * 2, s = Math.random()*spd + 1;
particles.push({ x, y, vx: Math.cos(a)*s, vy: Math.sin(a)*s - 1.5,
life: 1, size: Math.random()*4+2, color });
}
}
function floatText(x, y, txt, color) {
floats.push({ x, y, txt, color, life: 1 });
}
function addShake(n) { shake = Math.min(shake + n, 18); }
// ---------- Game loop ----------
reset();
particles = []; floats = []; shake = 0; flashT = 0;
state.mode = 'play';
showMessage('NEON BREAKER', 'move: mouse / ← → • launch: click / space', 'start');
let last = performance.now();
function frame(now) {
let dt = Math.min((now - last) / 16.667, 2.5); last = now;
if (!paused && msgMode === 'play') update(dt);
draw(dt);
requestAnimationFrame(frame);
}
function update(dt) {
const p = state.paddle;
// paddle control
if (keys.ArrowLeft || keys.KeyA) { p.x -= 9 * dt; mouse.active = false; }
if (keys.ArrowRight || keys.KeyD) { p.x += 9 * dt; mouse.active = false; }
if (mouse.active) p.x += (mouse.x - p.x) * Math.min(1, .35 * dt);
p.x = Math.max(p.w/2 + 8, Math.min(W - p.w/2 - 8, p.x));
p.w += (p.targetW - p.w) * .1;
// balls
for (const b of state.balls) {
if (b.held) { b.x = p.x; b.y = H - 60 - p.h/2 - b.r; continue; }
b.trail.push({x: b.x, y: b.y});
if (b.trail.length > 12) b.trail.shift();
const steps = Math.ceil(Math.hypot(b.vx, b.vy) * dt / 6) || 1;
for (let s = 0; s < steps; s++) {
const f = dt / steps;
b.x += b.vx * f; b.y += b.vy * f;
// walls
if (b.x < b.r + 4) { b.x = b.r + 4; b.vx = Math.abs(b.vx); SFX.wall(); burst(b.x, b.y, '#59c3ff', 4, 3); }
if (b.x > W - b.r - 4) { b.x = W - b.r - 4; b.vx = -Math.abs(b.vx); SFX.wall(); burst(b.x, b.y, '#59c3ff', 4, 3); }
if (b.y < b.r + 4) { b.y = b.r + 4; b.vy = Math.abs(b.vy); SFX.wall(); }
// paddle
const py = H - 60;
if (b.vy > 0 && b.y + b.r > py && b.y - b.r < py + p.h &&
b.x > p.x - p.w/2 - b.r && b.x < p.x + p.w/2 + b.r) {
b.y = py - b.r;
const hit = (b.x - p.x) / (p.w/2);
const ang = hit * 1.05 - Math.PI/2 * 0; // reflect angle
const speed = Math.min(Math.hypot(b.vx, b.vy) * 1.015, baseSpeed() + 3.5);
const theta = -Math.PI/2 + hit * 1.05;
b.vx = Math.cos(theta) * speed * Math.max(.35, Math.abs(Math.cos(theta)) * 0 + 1) * (hit < 0 ? -1 : 1) * Math.abs(Math.cos(theta));
b.vy = Math.sin(theta) * speed;
if (Math.abs(b.vy) < speed * .4) b.vy = -speed * .4;
b.vx = Math.cos(theta) * speed;
SFX.paddle();
combo = 0;
burst(b.x, py, '#c17bff', 8, 4);
addShake(2);
}
// bricks
for (const br of state.bricks) {
if (!br.alive) continue;
if (b.x + b.r > br.x && b.x - b.r < br.x + br.w &&
b.y + b.r > br.y && b.y - b.r < br.y + br.h) {
// resolve side
const ox = Math.min(b.x + b.r - br.x, br.x + br.w - (b.x - b.r));
const oy = Math.min(b.y + b.r - br.y, br.y + br.h - (b.y - b.r));
if (ox < oy) b.vx = -b.vx; else b.vy = -b.vy;
br.hp--; br.wobble = 1;
combo++;
const gain = 10 * Math.max(1, combo);
score += gain;
SFX.brick(combo);
burst(b.x, b.y, br.color, br.hp <= 0 ? 16 : 8, br.hp <= 0 ? 6 : 3.5);
if (combo >= 3) floatText(b.x, b.y - 10, '+' + gain, '#ffe66d');
addShake(br.hp <= 0 ? 3 : 1.5);
if (br.hp <= 0) {
br.alive = false;
if (Math.random() < .11) spawnPower(br.x + br.w/2, br.y + br.h/2);
} else {
br.color2 = '#fff';
}
updateHUD();
break;
}
}
// lose
if (b.y > H + 40) { b.dead = true; break; }
}
}
state.balls = state.balls.filter(b => !b.dead);
if (state.balls.length === 0) {
lives--;
SFX.die(); addShake(10); flashT = .5;
updateHUD();
if (lives <= 0) {
if (score > best) { best = score; localStorage.setItem('nb_best', best); }
showMessage('GAME OVER', `score ${score} — press / click to restart`, 'over');
state.over = true;
} else {
addBall(true); state.stuck = true; state.paddle.targetW = 130;
}
}
// bricks cleared?
if (state.bricks.every(b => !b.alive)) {
level++;
score += 250;
SFX.level();
flashT = .6; addShake(6);
updateHUD();
newBoard();
setTimeout(() => { if (msgMode === 'play') {} }, 0);
}
// powerups
for (const pu of powerups) {
pu.y += 2.6 * dt; pu.rot += .08 * dt;
if (pu.y > H) pu.dead = true;
if (pu.y > H - 60 - 8 && pu.y < H - 60 + 20 && Math.abs(pu.x - p.x) < p.w/2 + 12) {
pu.dead = true;
applyPower(pu.kind);
}
}
powerups = powerups.filter(p => !p.dead);
// wobble decay
for (const br of state.bricks) if (br.wobble > 0) br.wobble -= .1 * dt;
// particles / floats
for (const pt of particles) {
pt.x += pt.vx * dt; pt.y += pt.vy * dt; pt.vy += .18 * dt;
pt.life -= .03 * dt;
}
particles = particles.filter(p => p.life > 0);
for (const f of floats) { f.y -= 1 * dt; f.life -= .025 * dt; }
floats = floats.filter(f => f.life > 0);
shake *= Math.pow(.85, dt);
flashT = Math.max(0, flashT - .03 * dt);
}
function spawnPower(x, y) {
const kinds = ['wide','multi','slow','life'];
const r = Math.random();
const kind = r < .38 ? 'wide' : r < .68 ? 'multi' : r < .9 ? 'slow' : 'life';
powerups.push({ x, y, kind, rot: 0 });
}
function applyPower(kind) {
SFX.power();
const px = state.paddle.x;
if (kind === 'wide') { state.paddle.targetW = Math.min(210, state.paddle.targetW + 40); floatText(px, H - 90, 'WIDE!', '#59c3ff'); }
if (kind === 'multi') {
const src = state.balls[0];
if (src) for (let i = 0; i < 2; i++) {
const a = Math.atan2(src.vy || -4, src.vx || 0) + (i ? .6 : -.6);
const sp = baseSpeed();
state.balls.push({ x: src.x, y: src.y, vx: Math.cos(a)*sp, vy: Math.sin(a)*sp, r: 8, held: false, trail: [] });
}
floatText(px, H - 90, 'MULTIBALL!', '#ff5c7a');
}
if (kind === 'slow') { state.balls.forEach(b => { b.vx *= .72; b.vy *= .72; }); floatText(px, H - 90, 'SLOW!', '#5ef3ab'); }
if (kind === 'life') { lives++; updateHUD(); floatText(px, H - 90, '+1 LIFE', '#ffe66d'); }
burst(px, H - 90, '#fff', 20, 5);
}
function updateHUD() {
scoreEl.textContent = score;
levelEl.textContent = level + 1;
livesEl.textContent = lives >= 0 ? '♥'.repeat(Math.min(lives, 5)) || '0' : 0;
bestEl.textContent = Math.max(best, score);
}
// ---------- Drawing ----------
let bgT = 0;
function draw(dt) {
bgT += dt;
ctx.save();
if (shake > .4) ctx.translate((Math.random()-.5)*shake, (Math.random()-.5)*shake);
// background
const g = ctx.createLinearGradient(0, 0, 0, H);
g.addColorStop(0, '#0d0d1c'); g.addColorStop(1, '#08080f');
ctx.fillStyle = g; ctx.fillRect(-20, -20, W + 40, H + 40);
// subtle grid
ctx.strokeStyle = 'rgba(90,90,160,.07)'; ctx.lineWidth = 1;
ctx.beginPath();
for (let x = 0; x <= W; x += 45) { ctx.moveTo(x, 0); ctx.lineTo(x, H); }
for (let y = 0; y <= H; y += 45) { ctx.moveTo(0, y); ctx.lineTo(W, y); }
ctx.stroke();
// frame walls
ctx.fillStyle = 'rgba(140,150,255,.15)';
ctx.fillRect(0, 0, 4, H); ctx.fillRect(W-4, 0, 4, H); ctx.fillRect(0, 0, W, 4);
// bricks
for (const br of state.bricks) {
if (!br.alive) continue;
const wob = br.wobble > 0 ? Math.sin(br.wobble * 30) * br.wobble * 3 : 0;
ctx.save();
ctx.translate(br.x + br.w/2 + wob, br.y + br.h/2);
const dmg = br.hp / br.max;
ctx.fillStyle = br.color;
ctx.globalAlpha = br.hp > 1 ? .95 : .8;
ctx.shadowColor = br.color; ctx.shadowBlur = 10;
roundRect(-br.w/2, -br.h/2, br.w, br.h, 4); ctx.fill();
if (br.hp > 1) {
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,255,.75)';
roundRect(-br.w/2 + 4, -br.h/2 + 4, br.w - 8, br.h - 8, 3); ctx.fill();
ctx.fillStyle = br.color;
ctx.font = 'bold 12px Courier New'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
ctx.fillText(br.hp, 0, 1);
}
ctx.restore();
}
// powerups
for (const pu of powerups) {
ctx.save();
ctx.translate(pu.x, pu.y); ctx.rotate(pu.rot);
const col = { wide:'#59c3ff', multi:'#ff5c7a', slow:'#5ef3ab', life:'#ffe66d' }[pu.kind];
ctx.fillStyle = col; ctx.shadowColor = col; ctx.shadowBlur = 14;
roundRect(-10, -10, 20, 20, 5); ctx.fill();
ctx.shadowBlur = 0; ctx.fillStyle = '#0a0a12';
ctx.font = 'bold 13px Courier New'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
ctx.fillText({wide:'W',multi:'3',slow:'S',life:'♥'}[pu.kind], 0, 1);
ctx.restore();
}
// particles
for (const pt of particles) {
ctx.globalAlpha = Math.max(0, pt.life);
ctx.fillStyle = pt.color;
ctx.fillRect(pt.x - pt.size/2, pt.y - pt.size/2, pt.size, pt.size);
}
ctx.globalAlpha = 1;
// floats
ctx.font = 'bold 16px Courier New'; ctx.textAlign = 'center';
for (const f of floats) {
ctx.globalAlpha = Math.max(0, Math.min(1, f.life));
ctx.fillStyle = f.color;
ctx.fillText(f.txt, f.x, f.y);
}
ctx.globalAlpha = 1;
// paddle
const p = state.paddle;
const py = H - 60;
ctx.save();
const pg = ctx.createLinearGradient(p.x - p.w/2, 0, p.x + p.w/2, 0);
pg.addColorStop(0, '#c17bff'); pg.addColorStop(1, '#59c3ff');
ctx.fillStyle = pg;
ctx.shadowColor = '#8a6cff'; ctx.shadowBlur = 18;
roundRect(p.x - p.w/2, py, p.w, p.h, 8); ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,255,.5)';
roundRect(p.x - p.w/2 + 5, py + 4, p.w - 10, 4, 2); ctx.fill();
ctx.restore();
// balls + trails
for (const b of state.balls) {
for (let i = 0; i < b.trail.length; i++) {
const t = b.trail[i];
ctx.globalAlpha = (i / b.trail.length) * .35;
ctx.fillStyle = '#fff';
ctx.beginPath(); ctx.arc(t.x, t.y, b.r * (i / b.trail.length) * .8, 0, 7); ctx.fill();
}
ctx.globalAlpha = 1;
ctx.fillStyle = '#fff';
ctx.shadowColor = '#bfe8ff'; ctx.shadowBlur = 16;
ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, 7); ctx.fill();
ctx.shadowBlur = 0;
if (b.held) {
ctx.globalAlpha = .4 + Math.sin(bgT * .12) * .3;
ctx.strokeStyle = '#fff'; ctx.lineWidth = 1;
ctx.setLineDash([4, 6]);
ctx.beginPath(); ctx.moveTo(b.x, b.y - b.r);
ctx.lineTo(b.x, b.y - 120); ctx.stroke();
ctx.setLineDash([]);
ctx.globalAlpha = 1;
}
}
// flash
if (flashT > 0) {
ctx.fillStyle = `rgba(255,255,255,${flashT * .25})`;
ctx.fillRect(0, 0, W, H);
}
// scanlines
ctx.fillStyle = 'rgba(0,0,0,.12)';
for (let y = 0; y < H; y += 3) ctx.fillRect(0, y, W, 1);
if (paused && msgMode === 'play') {
ctx.fillStyle = 'rgba(5,5,15,.6)'; ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff'; ctx.font = 'bold 30px Courier New';
ctx.textAlign = 'center';
ctx.fillText('PAUSED', W/2, H/2);
}
ctx.restore();
}
function roundRect(x, y, w, h, r) {
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();
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>

