Hamburger breakoutOne-line
Gemini 4 Hydrogen
by Googlereasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 33 s
- Cost
- $0.114
- Output tokens
- 15,068
- Spent thinking
- 1,285 tokens
- Speed
- 164.3 tok/s
- Console errors
- 0
- First token after
- 1.5 s
- First visible text after
- 8.6 s
- Prompt tokens
- 402
- Code size
- 38.3 KB
- Finish reason
- stop
- Served by
- Model id
google/gemini-4-hydrogen- Reasoning sent
model default- Max output
- 40,340 tokens
- Generated
- Nov 13, 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 · 1256 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Burger Love Breakout</title>
<style>
*, *::before, *::after {
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: radial-gradient(circle at 50% 25%, #381845 0%, #1c0d26 55%, #100717 100%);
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
color: #fff5f8;
}
.app {
width: 100%;
height: 100dvh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px 12px;
gap: 10px;
}
.hud {
width: 100%;
max-width: 820px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 14px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 160, 200, 0.24);
border-radius: 14px;
backdrop-filter: blur(8px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
flex-shrink: 0;
}
.brand {
display: flex;
align-items: center;
gap: 8px;
font-weight: 800;
font-size: clamp(14px, 2.2vw, 18px);
letter-spacing: 0.3px;
color: #ffd480;
text-shadow: 0 2px 8px rgba(255, 110, 160, 0.4);
white-space: nowrap;
}
.stats {
display: flex;
align-items: center;
gap: clamp(10px, 2.5vw, 22px);
font-size: clamp(12px, 1.9vw, 15px);
font-weight: 700;
}
.stat {
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
.stat-label {
color: #e8b8d0;
font-weight: 600;
font-size: 0.85em;
}
.stat-val {
color: #ffffff;
font-variant-numeric: tabular-nums;
}
.stat-hearts .stat-val {
color: #ff77aa;
}
.controls {
display: flex;
align-items: center;
gap: 6px;
}
.btn {
appearance: none;
border: 1px solid rgba(255, 170, 210, 0.35);
background: rgba(255, 110, 165, 0.18);
color: #fff5f8;
font-weight: 700;
font-size: clamp(11px, 1.6vw, 13px);
padding: 5px 10px;
border-radius: 9px;
cursor: pointer;
transition: transform 0.12s ease, background 0.15s ease;
}
.btn:hover {
background: rgba(255, 110, 165, 0.32);
}
.btn:active {
transform: scale(0.95);
}
.stage-wrap {
position: relative;
width: 100%;
max-width: 820px;
flex: 1 1 auto;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
display: block;
width: 100%;
height: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 18px;
border: 2px solid rgba(255, 155, 200, 0.3);
box-shadow:
0 18px 50px rgba(0, 0, 0, 0.55),
inset 0 0 40px rgba(255, 90, 160, 0.08);
background: linear-gradient(180deg, #241033 0%, #1a0b26 60%, #2a102b 100%);
touch-action: none;
cursor: pointer;
}
.overlay-prompt {
position: absolute;
left: 50%;
bottom: 20%;
transform: translateX(-50%);
pointer-events: none;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 12px 22px;
border-radius: 999px;
background: rgba(20, 8, 28, 0.78);
border: 1.5px solid rgba(255, 140, 195, 0.65);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), 0 0 24px rgba(255, 90, 160, 0.35);
backdrop-filter: blur(6px);
text-align: center;
transition: opacity 0.25s ease, transform 0.25s ease;
}
.overlay-prompt.hidden {
opacity: 0;
transform: translateX(-50%) translateY(8px);
}
.overlay-title {
font-size: clamp(13px, 2.2vw, 18px);
font-weight: 800;
color: #fff;
letter-spacing: 0.4px;
animation: pulseGlow 1.8s ease-in-out infinite;
}
.overlay-sub {
font-size: clamp(10px, 1.5vw, 12px);
color: #ffc2dc;
font-weight: 600;
}
.modal-card {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: rgba(24, 10, 34, 0.9);
border: 2px solid rgba(255, 140, 195, 0.6);
border-radius: 20px;
padding: 22px 28px;
text-align: center;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.65);
backdrop-filter: blur(10px);
display: none;
flex-direction: column;
align-items: center;
gap: 10px;
z-index: 5;
min-width: 240px;
}
.modal-card.visible {
display: flex;
}
.modal-card h2 {
margin: 0;
font-size: clamp(20px, 3vw, 26px);
color: #ffd480;
}
.modal-card p {
margin: 0;
font-size: clamp(13px, 2vw, 15px);
color: #ffd4e5;
}
.modal-card .btn-primary {
margin-top: 6px;
padding: 9px 20px;
font-size: 14px;
border-radius: 12px;
background: linear-gradient(135deg, #ff5d9e, #ff8566);
border: none;
color: #fff;
box-shadow: 0 6px 18px rgba(255, 93, 158, 0.45);
}
@keyframes pulseGlow {
0%, 100% {
text-shadow: 0 0 10px rgba(255, 120, 180, 0.6);
transform: scale(1);
}
50% {
text-shadow: 0 0 18px rgba(255, 180, 210, 0.95);
transform: scale(1.03);
}
}
@media (max-width: 560px) {
.app {
padding: 6px 8px;
gap: 6px;
}
.hud {
padding: 6px 10px;
flex-wrap: wrap;
justify-content: center;
}
.brand {
display: none;
}
}
</style>
</head>
<body>
<div class="app">
<header class="hud">
<div class="brand">
<span>🍔</span>
<span>BURGER LOVE BREAKOUT</span>
</div>
<div class="stats">
<div class="stat">
<span class="stat-label">SCORE</span>
<span class="stat-val" id="scoreVal">0</span>
</div>
<div class="stat stat-hearts">
<span class="stat-label">💖 HEARTS</span>
<span class="stat-val" id="heartsVal">0</span>
</div>
<div class="stat">
<span class="stat-label">LVL</span>
<span class="stat-val" id="levelVal">1</span>
</div>
<div class="stat">
<span class="stat-label">LIVES</span>
<span class="stat-val" id="livesVal">🍔🍔🍔</span>
</div>
</div>
<div class="controls">
<button class="btn" id="soundBtn" type="button" aria-label="Toggle sound">🔊 SFX</button>
<button class="btn" id="resetBtn" type="button">↻ Demo</button>
</div>
</header>
<div class="stage-wrap" id="stageWrap">
<canvas id="gameCanvas"></canvas>
<div class="overlay-prompt" id="demoPrompt">
<div class="overlay-title">Click or press a key to start</div>
<div class="overlay-sub">Move the Hamburger with Mouse, Touch, or ← → / A D</div>
</div>
<div class="modal-card" id="endModal">
<h2 id="modalTitle">Sweet Victory!</h2>
<p id="modalMsg">You burst 0 hearts!</p>
<button class="btn btn-primary" id="modalBtn" type="button">Play Again</button>
</div>
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const stageWrap = document.getElementById('stageWrap');
const demoPrompt = document.getElementById('demoPrompt');
const endModal = document.getElementById('endModal');
const modalTitle = document.getElementById('modalTitle');
const modalMsg = document.getElementById('modalMsg');
const modalBtn = document.getElementById('modalBtn');
const scoreVal = document.getElementById('scoreVal');
const heartsVal = document.getElementById('heartsVal');
const levelVal = document.getElementById('levelVal');
const livesVal = document.getElementById('livesVal');
const soundBtn = document.getElementById('soundBtn');
const resetBtn = document.getElementById('resetBtn');
// Logical game coordinate space
const W = 800;
const H = 600;
// Resize canvas cleanly to fit container while maintaining 4:3 aspect ratio
function resizeCanvas() {
const rect = stageWrap.getBoundingClientRect();
const aspect = W / H;
let cw = rect.width;
let ch = rect.height;
if (cw / ch > aspect) {
cw = ch * aspect;
} else {
ch = cw / aspect;
}
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.style.width = `${Math.floor(cw)}px`;
canvas.style.height = `${Math.floor(ch)}px`;
canvas.width = Math.floor(W * dpr);
canvas.height = Math.floor(H * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Web Audio synthesized SFX (no external files)
let audioCtx = null;
let muted = false;
function ensureAudio() {
if (!audioCtx) {
const AC = window.AudioContext || window.webkitAudioContext;
if (AC) audioCtx = new AC();
}
if (audioCtx && audioCtx.state === 'suspended') {
audioCtx.resume().catch(() => {});
}
}
function playTone(freq, endFreq, duration, type = 'sine', vol = 0.12) {
if (muted || !audioCtx || audioCtx.state !== 'running') return;
const now = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, now);
osc.frequency.exponentialRampToValueAtTime(Math.max(30, endFreq), now + duration);
gain.gain.setValueAtTime(vol, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(now);
osc.stop(now + duration + 0.01);
}
function playBurgerBounce() {
playTone(190, 110, 0.09, 'triangle', 0.15);
}
function playWallBounce() {
playTone(320, 260, 0.05, 'sine', 0.07);
}
function playHeartBurst(combo = 1) {
const base = 440 + Math.min(combo, 12) * 45;
playTone(base, base * 1.45, 0.14, 'sine', 0.11);
setTimeout(() => playTone(base * 1.25, base * 1.85, 0.12, 'triangle', 0.07), 25);
}
function playBrickChip() {
playTone(360, 240, 0.07, 'square', 0.05);
}
// Game State
let mode = 'demo'; // 'demo' | 'playing' | 'paused' | 'gameover' | 'won'
let score = 0;
let totalHeartsBurst = 0;
let level = 1;
let lives = 3;
let combo = 0;
let shakeTimer = 0;
let shakeIntensity = 0;
// Ambient background floating hearts
const bgHearts = Array.from({ length: 22 }, (_, i) => ({
x: Math.random() * W,
y: Math.random() * H,
size: 6 + Math.random() * 14,
speedY: 12 + Math.random() * 20,
wobblePhase: Math.random() * Math.PI * 2,
alpha: 0.04 + Math.random() * 0.08,
hue: i % 2 === 0 ? 335 : 350
}));
// Hamburger Paddle
const paddle = {
x: W / 2,
y: H - 46,
width: 126,
height: 36,
vx: 0,
targetX: W / 2,
squash: 0, // spring value for juicy bounce animation
squashVel: 0,
tilt: 0
};
// Ball
const ball = {
x: W / 2,
y: H - 84,
vx: 210,
vy: -370,
radius: 10,
speed: 430,
attached: false,
trail: []
};
// Bricks
let bricks = [];
const brickPalettes = [
{ fill: '#ff4f87', light: '#ff8eb3', dark: '#c9285d', heartColors: ['#ff3b7f', '#ff7aa8', '#ffd1e3', '#ff1f5a'] },
{ fill: '#ff784f', light: '#ffa88c', dark: '#cf4821', heartColors: ['#ff6b4a', '#ff477e', '#ffb3a7', '#ffe0eb'] },
{ fill: '#ffbe3b', light: '#ffda85', dark: '#cc8814', heartColors: ['#ff598f', '#ff94b8', '#ffe38f', '#ff3d68'] },
{ fill: '#57d188', light: '#95ebb7', dark: '#2d9959', heartColors: ['#ff4f8b', '#ff85b3', '#b8ffd6', '#ffffff'] },
{ fill: '#63b8ff', light: '#a3d6ff', dark: '#2b7dc4', heartColors: ['#ff5996', '#ff9cc4', '#a8dbff', '#ffffff'] },
{ fill: '#c66bff', light: '#e0abff', dark: '#8d36c7', heartColors: ['#ff4da6', '#e68aff', '#ffc4e1', '#ffffff'] }
];
// Heart Explosion Particles & Rings & Floating Text
let heartParticles = [];
let shockwaves = [];
let floatingTexts = [];
function buildLevel(lvl) {
bricks = [];
const cols = 10;
const rows = Math.min(6, 5 + Math.floor((lvl - 1) / 2));
const padX = 36;
const topY = 58;
const gap = 8;
const totalGap = gap * (cols - 1);
const bw = (W - padX * 2 - totalGap) / cols;
const bh = 26;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
// Leave a cute heart-ish or scalloped spacing on higher levels, full rich wall on lvl 1
const palette = brickPalettes[r % brickPalettes.length];
const isTough = (r === 0 && lvl >= 2); // Bottom rows always 1-hit so hearts explode immediately!
bricks.push({
x: padX + c * (bw + gap),
y: topY + r * (bh + gap),
w: bw,
h: bh,
hp: isTough ? 2 : 1,
maxHp: isTough ? 2 : 1,
palette,
hitFlash: 0,
wobble: 0
});
}
}
}
function resetBall(launchImmediately = true) {
ball.radius = 10;
ball.speed = Math.min(540, 420 + (level - 1) * 28);
ball.trail = [];
if (launchImmediately) {
ball.attached = false;
ball.x = paddle.x;
ball.y = paddle.y - paddle.height / 2 - ball.radius - 2;
const angle = -Math.PI / 2 + (Math.random() * 0.55 - 0.275);
ball.vx = Math.cos(angle) * ball.speed;
ball.vy = Math.sin(angle) * ball.speed;
} else {
ball.attached = true;
ball.x = paddle.x;
ball.y = paddle.y - paddle.height / 2 - ball.radius - 2;
ball.vx = 0;
ball.vy = 0;
}
}
function syncHud() {
scoreVal.textContent = score.toLocaleString();
heartsVal.textContent = totalHeartsBurst.toLocaleString();
levelVal.textContent = level;
livesVal.textContent = mode === 'demo' ? '♾️' : '🍔'.repeat(Math.max(0, lives));
}
function startDemo() {
mode = 'demo';
score = 0;
totalHeartsBurst = 0;
level = 1;
lives = 3;
combo = 0;
heartParticles = [];
shockwaves = [];
floatingTexts = [];
paddle.x = W / 2;
paddle.targetX = W / 2;
buildLevel(1);
resetBall(true);
// Aim demo shot straight toward a brick cluster for quick heart explosions!
ball.vx = 135;
ball.vy = -415;
demoPrompt.classList.remove('hidden');
endModal.classList.remove('visible');
syncHud();
}
function startGame() {
ensureAudio();
if (mode === 'playing') return;
mode = 'playing';
score = 0;
totalHeartsBurst = 0;
level = 1;
lives = 3;
combo = 0;
heartParticles = [];
shockwaves = [];
floatingTexts = [];
paddle.x = W / 2;
paddle.targetX = W / 2;
buildLevel(1);
resetBall(true);
demoPrompt.classList.add('hidden');
endModal.classList.remove('visible');
syncHud();
}
// Input handling
const keys = { left: false, right: false };
function pointerToGameX(clientX) {
const rect = canvas.getBoundingClientRect();
return ((clientX - rect.left) / rect.width) * W;
}
canvas.addEventListener('pointerdown', (e) => {
ensureAudio();
if (mode === 'demo') {
startGame();
}
if (mode === 'playing') {
paddle.targetX = pointerToGameX(e.clientX);
if (ball.attached) {
resetBall(true);
}
}
});
canvas.addEventListener('pointermove', (e) => {
if (mode === 'playing') {
paddle.targetX = pointerToGameX(e.clientX);
}
});
window.addEventListener('keydown', (e) => {
if (['ArrowLeft', 'ArrowRight', 'Space', ' '].includes(e.key)) {
e.preventDefault();
}
ensureAudio();
if (mode === 'demo') {
startGame();
return;
}
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = true;
if (e.key === ' ' && mode === 'playing' && ball.attached) {
resetBall(true);
}
});
window.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = false;
});
soundBtn.addEventListener('click', () => {
ensureAudio();
muted = !muted;
soundBtn.textContent = muted ? '🔇 Muted' : '🔊 SFX';
});
resetBtn.addEventListener('click', () => {
startDemo();
});
modalBtn.addEventListener('click', () => {
startGame();
});
// Spawn TONS of little hearts when a brick breaks!
function explodeBrickIntoHearts(brick) {
const cx = brick.x + brick.w / 2;
const cy = brick.y + brick.h / 2;
const colors = brick.palette.heartColors;
// 1. Bursting bouncing/floating mini-hearts (30-38 per brick!)
const count = 34;
totalHeartsBurst += count;
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 45 + Math.random() * 240;
const floaty = i % 3 === 0; // Some float gently up like love bubbles!
heartParticles.push({
x: cx + (Math.random() - 0.5) * brick.w * 0.7,
y: cy + (Math.random() - 0.5) * brick.h * 0.7,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - (floaty ? 70 : 40),
size: 5 + Math.random() * 10,
rot: (Math.random() - 0.5) * 1.2,
vRot: (Math.random() - 0.5) * 7,
gravity: floaty ? -55 : 290,
drag: floaty ? 0.96 : 0.985,
life: 0,
maxLife: 0.75 + Math.random() * 0.95,
color: colors[i % colors.length],
wobbleFreq: 4 + Math.random() * 6,
wobblePhase: Math.random() * Math.PI * 2,
floaty
});
}
// 2. Heart-shaped shockwave outline
shockwaves.push({
x: cx,
y: cy,
scale: 0.4,
maxScale: 2.6,
alpha: 0.9,
color: colors[0]
});
// 3. Cute floating score / combo callout
const label = combo >= 3 ? `💖 x${combo}!` : `+${100 * Math.max(1, combo)}`;
floatingTexts.push({
x: cx,
y: cy,
text: label,
life: 0,
maxLife: 0.75,
color: '#fff2f7'
});
shakeTimer = 0.08;
shakeIntensity = Math.min(4.5, 1.8 + combo * 0.35);
}
function chipBrickHearts(brick) {
const cx = brick.x + brick.w / 2;
const cy = brick.y + brick.h / 2;
const count = 8;
totalHeartsBurst += count;
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 40 + Math.random() * 130;
heartParticles.push({
x: cx,
y: cy,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - 30,
size: 4 + Math.random() * 5,
rot: 0,
vRot: (Math.random() - 0.5) * 6,
gravity: 250,
drag: 0.98,
life: 0,
maxLife: 0.5 + Math.random() * 0.4,
color: brick.palette.heartColors[i % 4],
wobbleFreq: 5,
wobblePhase: 0,
floaty: false
});
}
}
// Draw a crisp symmetric heart centered at (0, 0)
function drawHeartPath(size) {
const s = size * 0.55;
ctx.beginPath();
ctx.moveTo(0, s * 0.9);
ctx.bezierCurveTo(-s * 1.4, s * 0.1, -s * 1.4, -s * 1.1, 0, -s * 0.35);
ctx.bezierCurveTo(s * 1.4, -s * 1.1, s * 1.4, s * 0.1, 0, s * 0.9);
ctx.closePath();
}
// Draw the delicious Hamburger Paddle!
function drawHamburgerPaddle() {
const halfW = paddle.width / 2;
const sq = paddle.squash;
const scaleX = 1 + sq * 0.14;
const scaleY = 1 - sq * 0.24;
ctx.save();
ctx.translate(paddle.x, paddle.y + sq * 5);
ctx.rotate(paddle.tilt);
ctx.scale(scaleX, scaleY);
// Drop shadow
ctx.fillStyle = 'rgba(0, 0, 0, 0.38)';
ctx.beginPath();
ctx.ellipse(0, 20, halfW * 0.95, 7, 0, 0, Math.PI * 2);
ctx.fill();
// 1. Bottom Bun
ctx.fillStyle = '#d98b36';
ctx.beginPath();
ctx.roundRect(-halfW + 4, 8, paddle.width - 8, 12, [4, 4, 10, 10]);
ctx.fill();
ctx.fillStyle = '#f0ab54';
ctx.beginPath();
ctx.roundRect(-halfW + 7, 8, paddle.width - 14, 5, 3);
ctx.fill();
// 2. Juicy Grilled Patty
ctx.fillStyle = '#63281b';
ctx.beginPath();
ctx.roundRect(-halfW + 1, 0, paddle.width - 2, 10, 5);
ctx.fill();
// Grill highlight
ctx.fillStyle = '#853c2b';
ctx.beginPath();
ctx.roundRect(-halfW + 5, 1, paddle.width - 10, 3, 2);
ctx.fill();
// 3. Melted Cheddar Cheese (with appetizing drips!)
ctx.fillStyle = '#ffc928';
ctx.beginPath();
ctx.moveTo(-halfW + 5, -2);
ctx.lineTo(halfW - 5, -2);
ctx.lineTo(halfW - 4, 3);
ctx.lineTo(halfW - 18, 3);
ctx.lineTo(halfW - 26, 9);
ctx.lineTo(halfW - 35, 3);
ctx.lineTo(-10, 3);
ctx.lineTo(-20, 10);
ctx.lineTo(-30, 3);
ctx.lineTo(-halfW + 4, 3);
ctx.closePath();
ctx.fill();
// 4. Tomato Slices peeking out
ctx.fillStyle = '#f03a3a';
ctx.beginPath();
ctx.roundRect(-halfW + 8, -6, 34, 5, 2.5);
ctx.roundRect(halfW - 42, -6, 34, 5, 2.5);
ctx.fill();
ctx.fillStyle = '#ff7b7b';
ctx.fillRect(-halfW + 14, -5, 16, 1.5);
ctx.fillRect(halfW - 36, -5, 16, 1.5);
// 5. Crisp Wavy Green Lettuce
ctx.fillStyle = '#4ad156';
ctx.beginPath();
const scallops = 9;
const step = (paddle.width - 4) / scallops;
ctx.moveTo(-halfW + 2, -6);
for (let i = 0; i < scallops; i++) {
const sx = -halfW + 2 + i * step;
ctx.quadraticCurveTo(sx + step * 0.5, -12, sx + step, -6);
}
for (let i = scallops - 1; i >= 0; i--) {
const sx = -halfW + 2 + i * step;
ctx.quadraticCurveTo(sx + step * 0.5, -2, sx, -6);
}
ctx.closePath();
ctx.fill();
// 6. Golden Domed Top Bun
const bunGrad = ctx.createLinearGradient(0, -24, 0, -6);
bunGrad.addColorStop(0, '#f7ba63');
bunGrad.addColorStop(0.65, '#e89b3c');
bunGrad.addColorStop(1, '#c97a24');
ctx.fillStyle = bunGrad;
ctx.beginPath();
ctx.moveTo(-halfW + 4, -7);
ctx.bezierCurveTo(-halfW + 6, -26, halfW - 6, -26, halfW - 4, -7);
ctx.closePath();
ctx.fill();
// Glossy bun highlight
ctx.fillStyle = 'rgba(255, 244, 214, 0.35)';
ctx.beginPath();
ctx.ellipse(-halfW * 0.2, -16, halfW * 0.42, 3.2, -0.05, 0, Math.PI * 2);
ctx.fill();
// Sesame seeds
ctx.fillStyle = '#fff5d6';
const seeds = [
[-38, -13, -0.3], [-20, -17, 0.2], [-4, -14, -0.15],
[14, -17, 0.35], [32, -13, -0.25], [-12, -11, 0.4], [22, -11, -0.4]
];
for (const [sx, sy, rot] of seeds) {
ctx.save();
ctx.translate(sx, sy);
ctx.rotate(rot);
ctx.beginPath();
ctx.ellipse(0, 0, 3.2, 1.6, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
ctx.restore();
}
// Physics & Update Loop
let lastTime = performance.now();
let demoAimOffset = 0;
let demoAimTimer = 0;
function update(dt) {
// Ambient background hearts
for (const bh of bgHearts) {
bh.y -= bh.speedY * dt;
bh.wobblePhase += dt * 1.4;
if (bh.y < -30) {
bh.y = H + 30;
bh.x = Math.random() * W;
}
}
// Paddle squash spring
const springForce = -paddle.squash * 220 - paddle.squashVel * 18;
paddle.squashVel += springForce * dt;
paddle.squash += paddle.squashVel * dt;
// Paddle movement
const prevPaddleX = paddle.x;
if (mode === 'demo') {
demoAimTimer -= dt;
if (demoAimTimer <= 0) {
demoAimTimer = 0.7 + Math.random() * 0.8;
demoAimOffset = (Math.random() - 0.5) * 44; // varied angles for exciting brick breaks
}
const desired = ball.vy > 0 ? ball.x + demoAimOffset : W / 2 + Math.sin(performance.now() * 0.0012) * 80;
const maxSpeed = 620;
const diff = desired - paddle.x;
paddle.x += Math.max(-maxSpeed * dt, Math.min(maxSpeed * dt, diff * 10 * dt));
} else if (mode === 'playing') {
const keySpeed = 640;
if (keys.left) paddle.targetX -= keySpeed * dt;
if (keys.right) paddle.targetX += keySpeed * dt;
paddle.targetX = Math.max(paddle.width / 2 + 10, Math.min(W - paddle.width / 2 - 10, paddle.targetX));
paddle.x += (paddle.targetX - paddle.x) * Math.min(1, 22 * dt);
}
paddle.x = Math.max(paddle.width / 2 + 10, Math.min(W - paddle.width / 2 - 10, paddle.x));
paddle.vx = (paddle.x - prevPaddleX) / Math.max(dt, 0.001);
paddle.tilt = Math.max(-0.08, Math.min(0.08, paddle.vx * 0.00014));
// Ball motion
if (ball.attached) {
ball.x = paddle.x;
ball.y = paddle.y - paddle.height / 2 - ball.radius - 2;
} else if (mode === 'demo' || mode === 'playing') {
ball.trail.push({ x: ball.x, y: ball.y });
if (ball.trail.length > 10) ball.trail.shift();
ball.x += ball.vx * dt;
ball.y += ball.vy * dt;
// Wall collisions
if (ball.x - ball.radius < 10) {
ball.x = 10 + ball.radius;
ball.vx = Math.abs(ball.vx);
playWallBounce();
} else if (ball.x + ball.radius > W - 10) {
ball.x = W - 10 - ball.radius;
ball.vx = -Math.abs(ball.vx);
playWallBounce();
}
if (ball.y - ball.radius < 10) {
ball.y = 10 + ball.radius;
ball.vy = Math.abs(ball.vy);
playWallBounce();
}
// Hamburger Paddle collision
const pTop = paddle.y - 18;
const pLeft = paddle.x - paddle.width / 2;
const pRight = paddle.x + paddle.width / 2;
if (
ball.vy > 0 &&
ball.y + ball.radius >= pTop &&
ball.y - ball.radius <= paddle.y + 12 &&
ball.x + ball.radius >= pLeft &&
ball.x - ball.radius <= pRight
) {
ball.y = pTop - ball.radius;
const hitNorm = Math.max(-1, Math.min(1, (ball.x - paddle.x) / (paddle.width / 2)));
const bounceAngle = -Math.PI / 2 + hitNorm * (Math.PI * 0.36);
ball.vx = Math.cos(bounceAngle) * ball.speed;
ball.vy = Math.sin(bounceAngle) * ball.speed;
paddle.squash = 1.0;
combo = 0;
playBurgerBounce();
// Little sesame/love sparks on burger bounce!
for (let i = 0; i < 6; i++) {
heartParticles.push({
x: ball.x,
y: pTop,
vx: (Math.random() - 0.5) * 140,
vy: -40 - Math.random() * 110,
size: 4 + Math.random() * 4,
rot: 0,
vRot: (Math.random() - 0.5) * 6,
gravity: 200,
drag: 0.98,
life: 0,
maxLife: 0.45,
color: i % 2 === 0 ? '#ffd480' : '#ff77aa',
wobbleFreq: 4,
wobblePhase: 0,
floaty: false
});
}
}
// Brick collisions
for (let i = bricks.length - 1; i >= 0; i--) {
const b = bricks[i];
if (b.hitFlash > 0) b.hitFlash = Math.max(0, b.hitFlash - dt * 5);
const closestX = Math.max(b.x, Math.min(ball.x, b.x + b.w));
const closestY = Math.max(b.y, Math.min(ball.y, b.y + b.h));
const dx = ball.x - closestX;
const dy = ball.y - closestY;
if (dx * dx + dy * dy <= ball.radius * ball.radius) {
// Reflect along shallowest overlap axis
const overlapLeft = (ball.x + ball.radius) - b.x;
const overlapRight = (b.x + b.w) - (ball.x - ball.radius);
const overlapTop = (ball.y + ball.radius) - b.y;
const overlapBottom = (b.y + b.h) - (ball.y - ball.radius);
const minOverlapX = Math.min(overlapLeft, overlapRight);
const minOverlapY = Math.min(overlapTop, overlapBottom);
if (minOverlapX < minOverlapY) {
ball.vx = -ball.vx;
} else {
ball.vy = -ball.vy;
}
// Prevent nearly-horizontal infinite loops
if (Math.abs(ball.vy) < 110) {
ball.vy = ball.vy >= 0 ? 110 : -110;
const len = Math.hypot(ball.vx, ball.vy);
ball.vx = (ball.vx / len) * ball.speed;
ball.vy = (ball.vy / len) * ball.speed;
}
b.hp -= 1;
b.hitFlash = 1;
if (b.hp <= 0) {
combo += 1;
score += 100 * combo;
explodeBrickIntoHearts(b);
playHeartBurst(combo);
bricks.splice(i, 1);
} else {
score += 35;
chipBrickHearts(b);
playBrickChip();
}
syncHud();
break; // one brick per step
}
}
// Level cleared!
if (bricks.length === 0) {
level += 1;
if (mode === 'demo') {
buildLevel(level);
resetBall(true);
} else {
buildLevel(level);
resetBall(false);
floatingTexts.push({
x: W / 2,
y: H / 2,
text: `LEVEL ${level}! 💖`,
life: 0,
maxLife: 1.4,
color: '#ffd480'
});
}
syncHud();
}
// Ball fell below bottom
if (ball.y - ball.radius > H + 12) {
if (mode === 'demo') {
resetBall(true);
} else {
lives -= 1;
combo = 0;
syncHud();
if (lives <= 0) {
mode = 'gameover';
modalTitle.textContent = 'Burger Dropped!';
modalMsg.textContent = `Score: ${score.toLocaleString()} • Burst ${totalHeartsBurst.toLocaleString()} 💖 hearts!`;
endModal.classList.add('visible');
} else {
resetBall(false);
}
}
}
}
// Update Heart Particles
for (let i = heartParticles.length - 1; i >= 0; i--) {
const p = heartParticles[i];
p.life += dt;
if (p.life >= p.maxLife) {
heartParticles.splice(i, 1);
continue;
}
p.vy += p.gravity * dt;
p.vx *= Math.pow(p.drag, dt * 60);
p.vy *= Math.pow(p.drag, dt * 60);
if (p.floaty) {
p.x += Math.sin(p.life * p.wobbleFreq + p.wobblePhase) * 28 * dt;
}
p.x += p.vx * dt;
p.y += p.vy * dt;
p.rot += p.vRot * dt;
}
// Update Shockwaves
for (let i = shockwaves.length - 1; i >= 0; i--) {
const sw = shockwaves[i];
sw.scale += dt * 7.2;
sw.alpha -= dt * 2.2;
if (sw.alpha <= 0) shockwaves.splice(i, 1);
}
// Update Floating Texts
for (let i = floatingTexts.length - 1; i >= 0; i--) {
const ft = floatingTexts[i];
ft.life += dt;
ft.y -= 36 * dt;
if (ft.life >= ft.maxLife) floatingTexts.splice(i, 1);
}
if (shakeTimer > 0) {
shakeTimer = Math.max(0, shakeTimer - dt);
}
}
function draw() {
ctx.save();
// Screen shake on big heart bursts
if (shakeTimer > 0) {
const mag = shakeIntensity * (shakeTimer / 0.08);
ctx.translate((Math.random() - 0.5) * mag * 2, (Math.random() - 0.5) * mag * 2);
}
// Background
const bgGrad = ctx.createLinearGradient(0, 0, 0, H);
bgGrad.addColorStop(0, '#230f33');
bgGrad.addColorStop(0.6, '#1a0a26');
bgGrad.addColorStop(1, '#2b102d');
ctx.fillStyle = bgGrad;
ctx.fillRect(0, 0, W, H);
// Soft ambient floating hearts in backdrop
for (const bh of bgHearts) {
ctx.save();
ctx.translate(bh.x + Math.sin(bh.wobblePhase) * 8, bh.y);
ctx.fillStyle = `hsla(${bh.hue}, 90%, 72%, ${bh.alpha})`;
drawHeartPath(bh.size);
ctx.fill();
ctx.restore();
}
// Playfield inner border frame
ctx.strokeStyle = 'rgba(255, 160, 205, 0.18)';
ctx.lineWidth = 2;
ctx.strokeRect(10, 10, W - 20, H - 10);
// Draw Bricks
for (const b of bricks) {
const pal = b.palette;
ctx.save();
ctx.beginPath();
ctx.roundRect(b.x, b.y, b.w, b.h, 7);
ctx.fillStyle = b.hitFlash > 0 ? '#ffffff' : pal.fill;
ctx.fill();
// Glossy top highlight
ctx.fillStyle = pal.light;
ctx.beginPath();
ctx.roundRect(b.x + 3, b.y + 3, b.w - 6, 6, 4);
ctx.fill();
// Bottom shade
ctx.fillStyle = pal.dark;
ctx.beginPath();
ctx.roundRect(b.x + 2, b.y + b.h - 6, b.w - 4, 4, 3);
ctx.fill();
// Cute tiny heart emblem etched in the center of each brick
ctx.save();
ctx.translate(b.x + b.w / 2, b.y + b.h / 2 + 1);
ctx.fillStyle = b.hp > 1 ? 'rgba(255, 255, 255, 0.85)' : 'rgba(255, 255, 255, 0.35)';
drawHeartPath(b.hp > 1 ? 10 : 7.5);
ctx.fill();
ctx.restore();
if (b.hp > 1) {
ctx.strokeStyle = 'rgba(255,255,255,0.65)';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.roundRect(b.x + 1, b.y + 1, b.w - 2, b.h - 2, 6);
ctx.stroke();
}
ctx.restore();
}
// Heart-shaped Shockwaves
for (const sw of shockwaves) {
ctx.save();
ctx.translate(sw.x, sw.y);
ctx.scale(sw.scale, sw.scale);
ctx.globalAlpha = Math.max(0, sw.alpha);
ctx.strokeStyle = sw.color;
ctx.lineWidth = 2.5 / sw.scale;
drawHeartPath(26);
ctx.stroke();
ctx.restore();
}
// Ball Trail
for (let i = 0; i < ball.trail.length; i++) {
const t = ball.trail[i];
const frac = (i + 1) / ball.trail.length;
ctx.beginPath();
ctx.arc(t.x, t.y, ball.radius * frac * 0.78, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 130, 185, ${frac * 0.28})`;
ctx.fill();
}
// Ball
ctx.save();
ctx.shadowColor = '#ff70aa';
ctx.shadowBlur = 14;
const ballGrad = ctx.createRadialGradient(
ball.x - 3, ball.y - 3, 2,
ball.x, ball.y, ball.radius
);
ballGrad.addColorStop(0, '#ffffff');
ballGrad.addColorStop(0.55, '#ffe0ec');
ballGrad.addColorStop(1, '#ff5c9e');
ctx.fillStyle = ballGrad;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// Hamburger Paddle
drawHamburgerPaddle();
// TONS OF LITTLE HEARTS!
for (const p of heartParticles) {
const t = p.life / p.maxLife;
const alpha = t < 0.15 ? t / 0.15 : 1 - Math.pow((t - 0.15) / 0.85, 1.5);
const popScale = t < 0.12 ? 0.5 + (t / 0.12) * 0.75 : 1.0;
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.rot);
ctx.scale(popScale, popScale);
ctx.globalAlpha = Math.max(0, alpha);
ctx.fillStyle = p.color;
drawHeartPath(p.size);
ctx.fill();
// Tiny glossy glint on larger hearts
if (p.size > 8) {
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.beginPath();
ctx.arc(-p.size * 0.2, -p.size * 0.12, p.size * 0.1, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
// Floating Score / Combo Text
for (const ft of floatingTexts) {
const alpha = 1 - ft.life / ft.maxLife;
ctx.save();
ctx.globalAlpha = Math.max(0, alpha);
ctx.font = '800 16px "Segoe UI", sans-serif';
ctx.textAlign = 'center';
ctx.fillStyle = ft.color;
ctx.shadowColor = 'rgba(255, 70, 140, 0.8)';
ctx.shadowBlur = 8;
ctx.fillText(ft.text, ft.x, ft.y);
ctx.restore();
}
ctx.restore();
}
function loop(now) {
const dt = Math.min(0.033, (now - lastTime) / 1000);
lastTime = now;
update(dt);
draw();
requestAnimationFrame(loop);
}
// Start immediately in live self-playing demo mode!
startDemo();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Reasoning trace (1,285 tokens)
**Designing Delightful Breakout** I'm envisioning a single HTML file to host a Breakout game. The core focus is on crafting a visually appealing and fluid experience, particularly with the animated hamburger paddle and dynamic brick destruction. **Visualizing Breakout Enhancements** I'm now focusing on the finer visual details. Specifically, I'm planning to implement a dynamic, particle-based brick destruction effect that showers the screen with colorful hearts. The background will be a warm, pastel-sunset gradient with subtle bokeh. **Crafting Animated Elements** I'm meticulously detailing the hamburger paddle's procedural drawing, from its crusty buns to its juicy patty and melted cheese drips. I'm also planning for its spring-like squash and stretch animation, and a subtle tilt to enhance its game feel. **Detailing Heart Explosions** I'm now refining the mechanics for brick destruction, aiming for around 24-36 heart particles per brick. These will include fast-bursting mini-hearts with physics, floating, wobbling hearts, and expanding shockwave rings, all colored to match the brick's hue with Valentine tones. I'm prioritizing immediate, abundant heart effects from the start by making lower bricks one-hit.