Chotto.app
Login
Sign Up
ES
App Name
kuyawa / breakout
App Icon in SVG
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"> <!-- Definitions for Gradients and Shadows --> <defs> <!-- Background Gradient (Dark Theme) --> <linearGradient id="bgGradient" x1="0%" y1="0%" x2="0%" y2="100%"> <stop offset="0%" stop-color="#1A1A2E" /> <stop offset="100%" stop-color="#16213E" /> </linearGradient> <!-- Inner Shadow for Bricks/Paddle Depth --> <filter id="depthFilter" x="-10%" y="-10%" width="120%" height="120%"> <feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/> <feOffset dx="0" dy="4" result="offsetBlur"/> <feComposite in="SourceGraphic" in2="offsetBlur" operator="over"/> </filter> </defs> <!-- App Icon Background (Rounded Square Base) --> <rect width="512" height="512" rx="100" fill="url(#bgGradient)" /> <!-- Bricks (Rows at the top, showing some destroyed) --> <!-- Row 1: Red --> <g fill="#E94560" filter="url(#depthFilter)"> <rect x="66" y="80" width="70" height="40" rx="8"/> <rect x="156" y="80" width="70" height="40" rx="8"/> <rect x="246" y="80" width="70" height="40" rx="8"/> <rect x="336" y="80" width="70" height="40" rx="8"/> <rect x="416" y="80" width="70" height="40" rx="8"/> </g> <!-- Row 2: Yellow - Gaps created by gameplay --> <g fill="#F6C453" filter="url(#depthFilter)"> <rect x="66" y="140" width="70" height="40" rx="8"/> <!-- Destroyed Brick Missing at 156 --> <!-- Destroyed Brick Missing at 246 --> <rect x="336" y="140" width="70" height="40" rx="8"/> <rect x="416" y="140" width="70" height="40" rx="8"/> </g> <!-- Row 3: Green - Showing a brick just being hit --> <g filter="url(#depthFilter)"> <rect x="66" y="200" width="70" height="40" rx="8" fill="#4ADE80"/> <rect x="156" y="200" width="70" height="40" rx="8" fill="#4ADE80"/> <!-- The targeted brick that just broke (represented by faded path) --> <path d="M246 208 C 246 203, 252 200, 256 200 L 290 200 C 295 200, 300 203, 300 208 L 300 232 C 300 237, 295 240, 290 240 L 256 240 C 251 240, 246 237, 246 232 Z" fill="#4ADE80" opacity="0.4"/> <rect x="336" y="200" width="70" height="40" rx="8" fill="#4ADE80"/> <rect x="416" y="200" width="70" height="40" rx="8" fill="#4ADE80"/> </g> <!-- Paddle (Bottom Center, Cyan) --> <rect x="131" y="430" width="250" height="30" rx="15" fill="#00FFFF" filter="url(#depthFilter)" /> <!-- Ball (Heading up toward the gap, White) --> <circle cx="266" cy="270" r="24" fill="#F8F9FA" /> <!-- Subtle motion trail for the ball --> <g fill="#F8F9FA" opacity="0.3"> <circle cx="226" cy="310" r="18" /> <circle cx="186" cy="350" r="10" /> </g> <!-- "Broken" effect details around the just-hit brick --> <g stroke="#4ADE80" stroke-width="4" stroke-linecap="round"> <line x1="240" y1="190" x2="220" y2="170" opacity="0.8"/> <line x1="306" y1="190" x2="326" y2="170" opacity="0.8"/> <line x1="273" y1="230" x2="273" y2="250" opacity="0.8"/> </g> </svg>
HTML Content
<!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>Breakout</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: radial-gradient(circle at 20% 20%, #0f172a, #020617); min-height: 100vh; display: flex; justify-content: center; align-items: center; font-family: 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', sans-serif; padding: 1rem; } .game-wrapper { background: rgba(15, 23, 42, 0.75); backdrop-filter: blur(8px); border-radius: 48px; padding: 2rem 2rem 2.2rem; border: 1px solid rgba(255, 255, 255, 0.05); box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.9), inset 0 1px 2px rgba(255, 255, 255, 0.04); max-width: 780px; width: 100%; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.2rem; flex-wrap: wrap; gap: 0.6rem 1rem; } .title { font-size: 1.8rem; font-weight: 700; background: linear-gradient(135deg, #fcd34d, #f59e0b, #fcd34d); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; letter-spacing: -0.5px; text-shadow: 0 0 30px rgba(251, 191, 36, 0.15); } .stats { display: flex; align-items: center; gap: 1.2rem; background: rgba(0, 0, 0, 0.4); padding: 0.3rem 1.2rem 0.3rem 1.5rem; border-radius: 40px; border: 1px solid rgba(255, 255, 255, 0.04); } .stat-item { display: flex; align-items: baseline; gap: 0.3rem; } .stat-label { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 1.2px; color: #94a3b8; font-weight: 600; } .stat-value { font-size: 1.5rem; font-weight: 700; color: #f1f5f9; min-width: 1.6rem; text-align: center; } .stat-value.lives { color: #fb7185; } .stat-value.score { color: #fcd34d; } .canvas-wrapper { background: rgba(2, 6, 23, 0.6); border-radius: 28px; padding: 0.5rem; box-shadow: inset 0 8px 20px rgba(0, 0, 0, 0.8), 0 2px 8px rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.02); position: relative; } canvas { display: block; width: 100%; height: auto; aspect-ratio: 700 / 500; border-radius: 22px; background: #0b1120; cursor: none; touch-action: none; box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.5); } .controls { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.8rem; margin-top: 1rem; } .btn-group { display: flex; gap: 0.6rem; flex-wrap: wrap; } .btn { background: rgba(30, 41, 59, 0.8); backdrop-filter: blur(2px); border: 1px solid rgba(255, 255, 255, 0.06); color: #e2e8f0; font-weight: 600; font-size: 0.85rem; padding: 0.5rem 1.2rem; border-radius: 40px; cursor: pointer; transition: 0.2s ease; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); display: inline-flex; align-items: center; gap: 6px; letter-spacing: 0.3px; } .btn:hover { background: rgba(51, 65, 85, 0.9); color: white; transform: scale(0.96); border-color: rgba(251, 191, 36, 0.2); } .btn:active { transform: scale(0.92); } .btn-primary { background: linear-gradient(135deg, #f59e0b, #d97706); color: #0f172a; border-color: #fbbf24; font-weight: 700; box-shadow: 0 4px 16px rgba(251, 191, 36, 0.25); } .btn-primary:hover { background: linear-gradient(135deg, #fbbf24, #f59e0b); color: #020617; border-color: #fcd34d; } .btn-icon { font-size: 1.1rem; line-height: 1; } .footer-tip { margin-top: 0.8rem; text-align: center; color: #475569; font-size: 0.7rem; letter-spacing: 0.5px; opacity: 0.6; } @media (max-width: 600px) { .game-wrapper { padding: 1rem; border-radius: 32px; } .title { font-size: 1.3rem; } .stats { padding: 0.2rem 0.8rem 0.2rem 1rem; gap: 0.8rem; } .stat-value { font-size: 1.2rem; min-width: 1.2rem; } .btn { padding: 0.3rem 0.9rem; font-size: 0.75rem; } .controls { flex-direction: column; align-items: stretch; } .btn-group { justify-content: center; } } @media (max-width: 400px) { .stat-label { font-size: 0.5rem; } .stat-value { font-size: 1rem; } } </style> </head> <body> <div class="game-wrapper"> <div class="header"> <span class="title">🧱 BREAKOUT</span> <div class="stats"> <div class="stat-item"> <span class="stat-label">Score</span> <span class="stat-value score" id="scoreDisplay">0</span> </div> <div class="stat-item"> <span class="stat-label">Lives</span> <span class="stat-value lives" id="livesDisplay">❤️❤️❤️</span> </div> </div> </div> <div class="canvas-wrapper"> <canvas id="gameCanvas" width="700" height="500"></canvas> </div> <div class="controls"> <div class="btn-group"> <button class="btn" id="resetBtn"><span class="btn-icon">↻</span> New</button> <button class="btn" id="pauseBtn"><span class="btn-icon">⏸</span> Pause</button> </div> <button class="btn btn-primary" id="powerBtn">⚡ Power</button> </div> <div class="footer-tip">← → or drag · space to launch</div> </div> <script> (function() { const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const scoreDisplay = document.getElementById('scoreDisplay'); const livesDisplay = document.getElementById('livesDisplay'); // ---- dimensions ---- const W = 700, H = 500; const BALL_RADIUS = 8; const PADDLE_WIDTH = 100; const PADDLE_HEIGHT = 14; const BRICK_ROWS = 7; const BRICK_COLS = 10; const BRICK_W = 58; const BRICK_H = 22; const BRICK_GAP = 4; const BRICKS_TOP = 50; // ---- state ---- let ball = { x: W / 2, y: 380, dx: 3.2, dy: -3.8, speed: 4.5 }; let paddle = { x: (W - PADDLE_WIDTH) / 2, y: H - 36 }; let bricks = []; let score = 0; let lives = 3; let gameState = 'idle'; // 'idle', 'running', 'paused', 'gameover', 'win' let animationId = null; let powerActive = false; let powerTimer = null; let comboCount = 0; // ---- colors ---- const BRICK_COLORS = [ ['#ef4444', '#dc2626'], // red ['#f97316', '#ea580c'], // orange ['#eab308', '#ca8a04'], // yellow ['#22c55e', '#16a34a'], // green ['#3b82f6', '#2563eb'], // blue ['#8b5cf6', '#7c3aed'], // purple ['#ec4899', '#db2777'], // pink ]; // ---- helpers ---- function resetBall() { ball.x = W / 2; ball.y = 380; const angle = -Math.PI / 2 + (Math.random() - 0.5) * 0.8; const speed = 4.5 + (powerActive ? 1.2 : 0); ball.dx = Math.cos(angle) * speed; ball.dy = Math.sin(angle) * speed; if (Math.abs(ball.dx) < 1.2) ball.dx = 1.8 * (ball.dx > 0 ? 1 : -1); } function resetPaddle() { paddle.x = (W - PADDLE_WIDTH) / 2; } function initBricks() { bricks = []; for (let r = 0; r < BRICK_ROWS; r++) { for (let c = 0; c < BRICK_COLS; c++) { const color = BRICK_COLORS[r % BRICK_COLORS.length]; bricks.push({ x: c * (BRICK_W + BRICK_GAP) + BRICK_GAP / 2 + 8, y: r * (BRICK_H + BRICK_GAP) + BRICKS_TOP, w: BRICK_W, h: BRICK_H, alive: true, color: color, row: r, col: c, }); } } } function resetGame() { if (animationId) cancelAnimationFrame(animationId); if (powerTimer) clearTimeout(powerTimer); powerActive = false; powerTimer = null; comboCount = 0; score = 0; lives = 3; gameState = 'idle'; initBricks(); resetPaddle(); resetBall(); updateUI(); document.getElementById('pauseBtn').innerHTML = '<span class="btn-icon">⏸</span> Pause'; draw(); } // ---- UI update ---- function updateUI() { scoreDisplay.textContent = score; const hearts = '❤️'.repeat(Math.max(0, lives)) + '🖤'.repeat(Math.max(0, 3 - lives)); livesDisplay.textContent = hearts || '💀'; } // ---- collision helpers ---- function ballRectCollision(rect) { const cx = ball.x, cy = ball.y, r = BALL_RADIUS; const rx = rect.x, ry = rect.y, rw = rect.w, rh = rect.h; const nearestX = Math.max(rx, Math.min(cx, rx + rw)); const nearestY = Math.max(ry, Math.min(cy, ry + rh)); const distX = cx - nearestX; const distY = cy - nearestY; return (distX * distX + distY * distY) < (r * r); } function reflectBall(rect) { const cx = ball.x, cy = ball.y, r = BALL_RADIUS; const rx = rect.x, ry = rect.y, rw = rect.w, rh = rect.h; const nearestX = Math.max(rx, Math.min(cx, rx + rw)); const nearestY = Math.max(ry, Math.min(cy, ry + rh)); const dx = cx - nearestX; const dy = cy - nearestY; if (Math.abs(dx) > Math.abs(dy)) { ball.dx = -ball.dx; } else { ball.dy = -ball.dy; } // push ball out const overlap = r - Math.sqrt(dx * dx + dy * dy); if (overlap > 0) { const angle = Math.atan2(dy, dx); ball.x += Math.cos(angle) * overlap; ball.y += Math.sin(angle) * overlap; } } // ---- game logic ---- function update() { if (gameState !== 'running') return; // move ball ball.x += ball.dx; ball.y += ball.dy; // wall collision (left/right) if (ball.x - BALL_RADIUS < 0 || ball.x + BALL_RADIUS > W) { ball.dx = -ball.dx; ball.x = Math.max(BALL_RADIUS, Math.min(W - BALL_RADIUS, ball.x)); } // top wall if (ball.y - BALL_RADIUS < 0) { ball.dy = -ball.dy; ball.y = BALL_RADIUS; } // bottom => lose life if (ball.y + BALL_RADIUS > H) { lives--; updateUI(); if (lives <= 0) { gameState = 'gameover'; draw(); return; } resetBall(); resetPaddle(); gameState = 'idle'; draw(); return; } // paddle collision const paddleRect = { x: paddle.x, y: paddle.y, w: PADDLE_WIDTH, h: PADDLE_HEIGHT }; if (ballRectCollision(paddleRect)) { reflectBall(paddleRect); // angle depends on where ball hits paddle const hitPos = (ball.x - paddle.x) / PADDLE_WIDTH; // 0..1 const angle = (hitPos - 0.5) * 1.2; // -0.6 to 0.6 rad const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy); ball.dx = Math.sin(angle) * speed; ball.dy = -Math.cos(angle) * speed; if (Math.abs(ball.dx) < 0.8) ball.dx = 1.2 * (ball.dx > 0 ? 1 : -1); // speed boost in power mode if (powerActive) { const currentSpeed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy); const newSpeed = Math.min(currentSpeed * 1.03, 8.5); const ratio = newSpeed / currentSpeed; ball.dx *= ratio; ball.dy *= ratio; } comboCount = 0; } // bricks collision for (let i = 0; i < bricks.length; i++) { const brick = bricks[i]; if (!brick.alive) continue; const rect = { x: brick.x, y: brick.y, w: brick.w, h: brick.h }; if (ballRectCollision(rect)) { brick.alive = false; reflectBall(rect); comboCount++; const bonus = Math.min(comboCount, 10); const points = 10 + bonus * 2; score += points; updateUI(); draw(); // partial update // power-up: every 15 bricks if (score % 150 < 20 && !powerActive) { activatePower(); } break; } } // check win const remaining = bricks.filter(b => b.alive).length; if (remaining === 0) { gameState = 'win'; draw(); return; } draw(); } // ---- power-up ---- function activatePower() { if (powerActive) return; powerActive = true; if (powerTimer) clearTimeout(powerTimer); // visual feedback: paddle glows document.querySelector('.canvas-wrapper').style.boxShadow = 'inset 0 8px 30px rgba(251, 191, 36, 0.3), 0 0 40px rgba(251, 191, 36, 0.15)'; powerTimer = setTimeout(() => { powerActive = false; document.querySelector('.canvas-wrapper').style.boxShadow = 'inset 0 8px 20px rgba(0, 0, 0, 0.8), 0 2px 8px rgba(0, 0, 0, 0.3)'; }, 6000); } // ---- drawing ---- function draw() { ctx.clearRect(0, 0, W, H); // draw bricks for (const brick of bricks) { if (!brick.alive) continue; const grad = ctx.createLinearGradient(brick.x, brick.y, brick.x, brick.y + brick.h); grad.addColorStop(0, brick.color[0]); grad.addColorStop(1, brick.color[1]); ctx.fillStyle = grad; ctx.shadowColor = brick.color[0]; ctx.shadowBlur = 8; ctx.beginPath(); ctx.roundRect(brick.x, brick.y, brick.w, brick.h, 4); ctx.fill(); // highlight ctx.shadowBlur = 0; ctx.fillStyle = 'rgba(255,255,255,0.15)'; ctx.beginPath(); ctx.roundRect(brick.x + 4, brick.y + 2, brick.w - 8, 6, 2); ctx.fill(); } ctx.shadowBlur = 0; // paddle const paddleGrad = ctx.createLinearGradient(paddle.x, paddle.y, paddle.x, paddle.y + PADDLE_HEIGHT); if (powerActive) { paddleGrad.addColorStop(0, '#fcd34d'); paddleGrad.addColorStop(0.5, '#f59e0b'); paddleGrad.addColorStop(1, '#d97706'); ctx.shadowColor = '#fbbf24'; ctx.shadowBlur = 25; } else { paddleGrad.addColorStop(0, '#94a3b8'); paddleGrad.addColorStop(1, '#64748b'); ctx.shadowColor = 'rgba(148,163,184,0.3)'; ctx.shadowBlur = 12; } ctx.fillStyle = paddleGrad; ctx.beginPath(); ctx.roundRect(paddle.x, paddle.y, PADDLE_WIDTH, PADDLE_HEIGHT, 10); ctx.fill(); ctx.shadowBlur = 0; // paddle shine ctx.fillStyle = 'rgba(255,255,255,0.2)'; ctx.beginPath(); ctx.roundRect(paddle.x + 12, paddle.y + 3, PADDLE_WIDTH - 24, 5, 3); ctx.fill(); // ball ctx.shadowColor = powerActive ? '#fbbf24' : '#60a5fa'; ctx.shadowBlur = powerActive ? 30 : 18; const ballGrad = ctx.createRadialGradient( ball.x - 3, ball.y - 3, 2, ball.x, ball.y, BALL_RADIUS ); if (powerActive) { ballGrad.addColorStop(0, '#fef9c3'); ballGrad.addColorStop(0.7, '#f59e0b'); ballGrad.addColorStop(1, '#b45309'); } else { ballGrad.addColorStop(0, '#e0f2fe'); ballGrad.addColorStop(0.7, '#60a5fa'); ballGrad.addColorStop(1, '#1d4ed8'); } ctx.fillStyle = ballGrad; ctx.beginPath(); ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; // draw game messages if (gameState === 'idle') { ctx.fillStyle = 'rgba(255,255,255,0.7)'; ctx.font = 'bold 20px "Segoe UI", sans-serif'; ctx.textAlign = 'center'; ctx.fillText('🖱️ click or press space to launch', W / 2, 170); ctx.font = '14px "Segoe UI", sans-serif'; ctx.fillStyle = 'rgba(255,255,255,0.3)'; ctx.fillText('← → or drag paddle', W / 2, 205); } if (gameState === 'gameover') { ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(0, 0, W, H); ctx.fillStyle = '#fca5a5'; ctx.font = 'bold 48px "Segoe UI", sans-serif'; ctx.textAlign = 'center'; ctx.fillText('💔 GAME OVER', W / 2, H / 2 - 20); ctx.font = '20px "Segoe UI", sans-serif'; ctx.fillStyle = '#e2e8f0'; ctx.fillText(`Score: ${score}`, W / 2, H / 2 + 40); } if (gameState === 'win') { ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(0, 0, W, H); ctx.fillStyle = '#fcd34d'; ctx.font = 'bold 48px "Segoe UI", sans-serif'; ctx.textAlign = 'center'; ctx.fillText('🎉 YOU WIN!', W / 2, H / 2 - 20); ctx.font = '20px "Segoe UI", sans-serif'; ctx.fillStyle = '#e2e8f0'; ctx.fillText(`Score: ${score}`, W / 2, H / 2 + 40); } if (gameState === 'paused') { ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(0, 0, W, H); ctx.fillStyle = '#e2e8f0'; ctx.font = 'bold 40px "Segoe UI", sans-serif'; ctx.textAlign = 'center'; ctx.fillText('⏸ PAUSED', W / 2, H / 2); } // power indicator if (powerActive) { ctx.fillStyle = 'rgba(251,191,36,0.15)'; ctx.fillRect(0, 0, W, H); ctx.fillStyle = '#fcd34d'; ctx.font = '12px "Segoe UI", sans-serif'; ctx.textAlign = 'right'; ctx.fillText('⚡ POWER', W - 16, 28); } } // ---- roundRect polyfill for canvas ---- if (!CanvasRenderingContext2D.prototype.roundRect) { CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) { if (r > w / 2) r = w / 2; if (r > h / 2) r = h / 2; this.moveTo(x + r, y); this.lineTo(x + w - r, y); this.quadraticCurveTo(x + w, y, x + w, y + r); this.lineTo(x + w, y + h - r); this.quadraticCurveTo(x + w, y + h, x + w - r, y + h); this.lineTo(x + r, y + h); this.quadraticCurveTo(x, y + h, x, y + h - r); this.lineTo(x, y + r); this.quadraticCurveTo(x, y, x + r, y); return this; }; } // ---- game loop ---- function gameLoop() { update(); if (gameState === 'running' || gameState === 'idle' || gameState === 'paused') { animationId = requestAnimationFrame(gameLoop); } else { // gameover or win: keep drawing but stop loop draw(); } } // ---- input ---- function handleMouseMove(e) { const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; let clientX = e.clientX || (e.touches && e.touches[0].clientX); if (clientX === undefined) return; const canvasX = (clientX - rect.left) * scaleX; const newX = canvasX - PADDLE_WIDTH / 2; paddle.x = Math.max(0, Math.min(W - PADDLE_WIDTH, newX)); if (gameState === 'idle') { ball.x = paddle.x + PADDLE_WIDTH / 2; } draw(); } function handleLaunch(e) { if (gameState === 'idle') { gameState = 'running'; document.getElementById('pauseBtn').innerHTML = '<span class="btn-icon">⏸</span> Pause'; // ensure ball is on paddle ball.x = paddle.x + PADDLE_WIDTH / 2; resetBall(); draw(); if (!animationId) { gameLoop(); } } if (gameState === 'gameover' || gameState === 'win') { resetGame(); } } // ---- event listeners ---- canvas.addEventListener('mousemove', handleMouseMove); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); handleMouseMove(e); }, { passive: false }); canvas.addEventListener('click', handleLaunch); canvas.addEventListener('touchstart', (e) => { e.preventDefault(); handleLaunch(e); }, { passive: false }); document.addEventListener('keydown', (e) => { if (e.key === ' ') { e.preventDefault(); handleLaunch(e); } if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { e.preventDefault(); const step = e.key === 'ArrowLeft' ? -22 : 22; paddle.x = Math.max(0, Math.min(W - PADDLE_WIDTH, paddle.x + step)); if (gameState === 'idle') { ball.x = paddle.x + PADDLE_WIDTH / 2; } draw(); } if (e.key === 'p' || e.key === 'P') { togglePause(); } }); // pause button function togglePause() { if (gameState === 'running') { gameState = 'paused'; document.getElementById('pauseBtn').innerHTML = '<span class="btn-icon">▶</span> Resume'; draw(); } else if (gameState === 'paused') { gameState = 'running'; document.getElementById('pauseBtn').innerHTML = '<span class="btn-icon">⏸</span> Pause'; if (!animationId) gameLoop(); draw(); } } document.getElementById('pauseBtn').addEventListener('click', togglePause); document.getElementById('resetBtn').addEventListener('click', () => { resetGame(); if (!animationId) gameLoop(); }); document.getElementById('powerBtn').addEventListener('click', () => { if (!powerActive) activatePower(); }); // ---- start ---- initBricks(); resetPaddle(); resetBall(); updateUI(); draw(); gameLoop(); // handle window resize / orientation window.addEventListener('resize', () => { draw(); }); })(); </script> </body> </html>
Cancel