Models 44

Xing4.0 29B A4B (free)

by XingChenReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
2 min 32 s
Cost
$0
Output tokens
11,412
Spent thinking
457 tokens
Speed
75.8 tok/s
Console errors
0
First token after
1.7 s
First visible text after
13.7 s
Prompt tokens
537
Code size
38.5 KB
Finish reason
stop
Served by
Phala
Model id
xingchen-agi/xing4.0-29b-a4b:free
Reasoning sent
model default
Max output
32,000 tokens
Generated
Nov 9, 2026

What it was asked

The prompt
Build a polished Breakout (brick breaker) game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A paddle controlled with the mouse and with the left/right arrow keys. - The bounce angle off the paddle depends on where the ball hits it (center = straight up, edges = sharp angle). - One level with at least 6 rows of bricks, mixing normal bricks and tougher bricks that need 2 hits and visibly crack after the first one. - Some bricks visibly carry a power-up icon; when broken they drop it, and catching it with the paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls). - The ball must never pass through bricks or the paddle. - Particles when a brick breaks. - 3 lives, score displayed. Game over and victory screens with a restart option.
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 · 1063 lines

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Breakout</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            overflow: hidden;
        }
        #gameContainer {
            position: relative;
            width: 100%;
            max-width: 1280px;
            aspect-ratio: 16/9;
            max-height: 100vh;
            border: 3px solid rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            box-shadow: 0 0 30px rgba(0, 200, 255, 0.3);
            background: #0a1421;
        }
        canvas {
            display: block;
            width: 100%;
            height: 100%;
            image-rendering: pixelated;
        }
        #overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background: rgba(10, 20, 33, 0.85);
            z-index: 10;
            color: white;
            text-align: center;
            transition: opacity 0.3s ease;
        }
        #overlay h1 {
            font-size: clamp(2rem, 5vw, 4rem);
            margin-bottom: 1rem;
            background: linear-gradient(to right, #00dbde, #fc00ff);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            text-shadow: 0 0 20px rgba(0, 219, 222, 0.5);
        }
        #overlay p {
            font-size: clamp(1rem, 2.5vw, 1.5rem);
            margin: 0.5rem 0;
            opacity: 0.9;
        }
        #overlay button {
            margin-top: 1.5rem;
            padding: 0.8rem 2.5rem;
            font-size: 1.2rem;
            background: linear-gradient(to right, #00c6ff, #0072ff);
            border: none;
            border-radius: 50px;
            color: white;
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
            box-shadow: 0 4px 15px rgba(0, 114, 255, 0.4);
        }
        #overlay button:hover {
            transform: translateY(-2px) scale(1.05);
            box-shadow: 0 6px 20px rgba(0, 114, 255, 0.6);
        }
        .hidden {
            opacity: 0;
            pointer-events: none;
        }
        .instructions {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            color: rgba(255, 255, 255, 0.5);
            font-size: clamp(0.8rem, 1.5vw, 1rem);
            text-align: center;
            pointer-events: none;
        }
        .controls-hint {
            position: absolute;
            top: 20px;
            right: 20px;
            color: rgba(255, 255, 255, 0.5);
            font-size: clamp(0.7rem, 1.2vw, 0.9rem);
            text-align: right;
        }
    </style>
</head>
<body>
    <div id="gameContainer">
        <canvas id="gameCanvas"></canvas>
        <div id="overlay" class="hidden">
            <h1>BRICK BREAKER</h1>
            <p class="instructions">Use MOUSE or ARROW KEYS to move the paddle</p>
            <p>Catch power-ups to gain advantages!</p>
            <button onclick="startGame()">START GAME</button>
        </div>
        <div class="controls-hint">
            <p>🖱️ Mouse | ⬅️➡️ Keys</p>
        </div>
    </div>

    <script>
        // ===================== GAME CONSTANTS =====================
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const overlay = document.getElementById('overlay');
        let centerX, centerY;
        let targetCanvasSize = { width: 800, height: 600 };
        const difficulty = {
            ballSpeed: 4.5,
            paddleWidth: 100,
            brickRows: 6,
            brickCols: 10,
            brickWidth: 70,
            brickHeight: 25,
            brickPadding: 8,
            brickOffsetTop: 60,
            brickOffsetLeft: 40
        };
        const colors = {
            walls: '#0d4f5c',
            paddle: '#00b4d8',
            ball: '#ffffff',
            normalBrick: '#ff9e00',
            toughBrick: '#7c3aed',
            crackedBrick: '#a78bfa',
            particleColors: ['#ffeb3b', '#ff5722', '#9c27b0', '#4caf50', '#2196f3'],
            powerUpWiden: '#00e5ff',
            powerUpMulti: '#ff00e6'
        };

        // ===================== GAME STATE =====================
        let game = {
            state: 'MENU', // MENU, PLAYING, GAMEOVER, VICTORY
            score: 0,
            lives: 3,
            keys: {},
            mouseX: 0,
            time: 0
        };

        let paddle = {
            x: 0,
            y: 0,
            width: difficulty.paddleWidth,
            height: 15,
            baseWidth: difficulty.paddleWidth,
            targetWidth: difficulty.paddleWidth,
            speed: 0.15
        };

        let balls = [];
        let bricks = [];
        let particles = [];
        let powerUps = [];
        let activeEffects = {
            widePaddle: null, // stores expiry time
            multiBall: null    // stores expiry time
        };

        // ===================== INITIALIZATION =====================
        function init() {
            // Canvas scaling for responsiveness
            function resizeCanvas() {
                const container = document.getElementById('gameContainer');
                const rect = container.getBoundingClientRect();
                const aspectRatio = rect.width / rect.height;
                targetCanvasSize.width = rect.width - 6; // account for border
                targetCanvasSize.height = rect.width / aspectRatio;
                if (targetCanvasSize.height > 800) {
                    targetCanvasSize.height = 800;
                    targetCanvasSize.width = targetCanvasSize.height * aspectRatio;
                }
                canvas.width = Math.floor(targetCanvasSize.width);
                canvas.height = Math.floor(targetCanvasSize.height);
                centerX = canvas.width / 2;
                centerY = canvas.height / 2;
                
                // Recalculate positions
                resetPaddle();
                createBricks();
                if (game.state === 'PLAYING') {
                    spawnBall();
                }
            }
            window.addEventListener('resize', resizeCanvas);
            resizeCanvas();

            // Mouse control
            canvas.addEventListener('mousemove', (e) => {
                const rect = canvas.getBoundingClientRect();
                const scaleX = canvas.width / rect.width;
                game.mouseX = (e.clientX - rect.left) * scaleX;
            });

            // Keyboard controls
            document.addEventListener('keydown', (e) => {
                game.keys[e.key] = true;
                if (game.state === 'PLAYING') {
                    // Prevent default scrolling
                    if (['ArrowLeft', 'ArrowRight'].includes(e.key)) {
                        e.preventDefault();
                    }
                }
            });
            document.addEventListener('keyup', (e) => {
                game.keys[e.key] = false;
            });

            // Initial setup
            overlay.classList.remove('hidden');
            drawMenuBackground();
        }

        function resetPaddle() {
            paddle.x = canvas.width / 2 - paddle.width / 2;
            paddle.y = canvas.height - 40;
        }

        function createBricks() {
            bricks = [];
            const rows = difficulty.brickRows;
            const cols = difficulty.brickCols;
            const brickWidth = difficulty.brickWidth;
            const brickHeight = difficulty.brickHeight;
            const padding = difficulty.brickPadding;
            const offsetTop = difficulty.brickOffsetTop;
            const offsetLeft = difficulty.brickOffsetLeft;

            for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                    // Determine brick type based on position
                    const isTough = (r < 2 && (r % 2 === 0 || r % 3 === 0));
                    const isCrackedAfter = (r >= 3 && r < 5 && c % 2 === 0);
                    
                    const x = c * (brickWidth + padding) + offsetLeft;
                    const y = r * (brickHeight + padding) + offsetTop;
                    
                    bricks.push({
                        x, y, width: brickWidth, height: brickHeight,
                        row: r, col: c,
                        health: isTough ? 2 : 1,
                        broken: false,
                        cracked: false,
                        hasPowerUp: Math.random() < 0.15, // 15% chance per brick
                        type: isTough ? 'tough' : (isCrackedAfter ? 'mixed' : 'normal')
                    });
                }
            }
        }

        function spawnBall() {
            balls = [{
                x: centerX,
                y: paddle.y - 10,
                dx: 0,
                dy: -difficulty.ballSpeed,
                radius: 8,
                active: true
            }];
        }

        function startGame() {
            game.state = 'PLAYING';
            game.score = 0;
            game.lives = 3;
            game.time = 0;
            particles = [];
            powerUps = [];
            bricks = [];
            createBricks();
            resetPaddle();
            paddle.width = difficulty.paddleWidth;
            paddle.baseWidth = difficulty.paddleWidth;
            paddle.targetWidth = difficulty.paddleWidth;
            activeEffects.widePaddle = null;
            activeEffects.multiBall = null;
            spawnBall();
            overlay.classList.add('hidden');
            requestAnimationFrame(gameLoop);
        }

        // ===================== GAME LOOP =====================
        function gameLoop(timestamp) {
            const dt = Math.min((timestamp - (game.lastTime || timestamp)) / 1000, 0.05);
            game.lastTime = timestamp;
            game.time += dt;
            
            update(dt);
            render();
            
            if (game.state !== 'MENU') {
                requestAnimationFrame(gameLoop);
            }
        }

        // ===================== UPDATE =====================
        function update(dt) {
            if (game.state !== 'PLAYING') return;

            // Update paddle
            updatePaddle(dt);

            // Update balls
            for (let ball of balls) {
                if (!ball.active) continue;
                ball.x += ball.dx * dt * 60;
                ball.y += ball.dy * dt * 60;

                // Wall collisions
                if (ball.x - ball.radius <= 0 || ball.x + ball.radius >= canvas.width) {
                    ball.dx = -ball.dx;
                    ball.x = ball.x - ball.radius <= 0 ? ball.radius : canvas.width - ball.radius;
                    createWallParticles(ball.x, ball.y);
                }
                if (ball.y - ball.radius <= 0) {
                    ball.dy = -ball.dy;
                    ball.y = ball.radius;
                    createWallParticles(ball.x, ball.y);
                }
                // Floor - lose life
                if (ball.y + ball.radius >= canvas.height) {
                    ball.active = false;
                    game.lives--;
                    createDeathParticles(centerX, canvas.height - 20);
                    if (game.lives <= 0) {
                        setTimeout(() => endGame(), 300);
                        return;
                    } else {
                        // Reset ball after short delay
                        setTimeout(() => {
                            if (game.state === 'PLAYING') {
                                spawnBall();
                            }
                        }, 500);
                    }
                }

                // Paddle collision
                if (ball.y + ball.radius >= paddle.y &&
                    ball.y - ball.radius <= paddle.y + paddle.height &&
                    ball.x >= paddle.x &&
                    ball.x <= paddle.x + paddle.width) {
                    
                    // Calculate bounce angle based on hit position
                    const hitPos = (ball.x - (paddle.x + paddle.width/2)) / (paddle.width/2);
                    const angle = hitPos * (Math.PI/3); // Max 60 degrees from vertical
                    const speed = Math.hypot(Math.abs(ball.dx), Math.abs(ball.dy));
                    ball.dx = speed * Math.sin(angle);
                    ball.dy = -Math.abs(speed * Math.cos(angle));
                    
                    // Ensure ball goes upward
                    if (ball.dy > 0) ball.dy = -ball.dy;
                    
                    createPaddleParticles(ball.x, paddle.y);
                }

                // Brick collisions
                for (let brick of bricks) {
                    if (brick.broken) continue;
                    
                    if (ball.x + ball.radius >= brick.x &&
                        ball.x - ball.radius <= brick.x + brick.width &&
                        ball.y + ball.radius >= brick.y &&
                        ball.y - ball.radius <= brick.y + brick.height) {
                        
                        // Determine collision side
                        const ballCenterX = ball.x;
                        const ballCenterY = ball.y;
                        const brickCenterX = brick.x + brick.width/2;
                        const brickCenterY = brick.y + brick.height/2;
                        
                        const dx = ballCenterX - brickCenterX;
                        const dy = ballCenterY - brickCenterY;
                        
                        if (Math.abs(dx/brick.width) > Math.abs(dy/brick.height)) {
                            ball.dx = -ball.dx;
                        } else {
                            ball.dy = -ball.dy;
                        }
                        
                        brickHit(brick, dx, dy);
                        break; // Only handle one brick per frame
                    }
                }
            }

            // Remove inactive balls
            balls = balls.filter(ball => ball.active);

            // Update power-ups
            updatePowerUps(dt);
            updateEffects(dt);
            
            // Update particles
            updateParticles(dt);

            // Check win condition
            const allBricksDestroyed = bricks.every(brick => brick.broken);
            if (allBricksDestroyed && bricks.length > 0) {
                game.state = 'VICTORY';
                showVictoryScreen();
            }
        }

        function updatePaddle(dt) {
            let moveDir = 0;
            
            // Keyboard movement
            if (game.keys['ArrowLeft'] || game.keys['a']) moveDir = -1;
            if (game.keys['ArrowRight'] || game.keys['d']) moveDir = 1;
            
            // Mouse control (always takes priority)
            moveDir = 0; // Mouse is primary, but we allow keyboard to work
            
            if (game.mouseX !== undefined && game.mouseX !== null) {
                // Smooth mouse movement
                const mouseTargetX = game.mouseX - paddle.width / 2;
                const diff = mouseTargetX - paddle.x;
                paddle.x += diff * paddle.speed;
            } else if (moveDir !== 0) {
                // Keyboard movement
                const moveSpeed = 8;
                paddle.x += moveDir * moveSpeed;
            }
            
            // Clamp paddle to screen
            paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, paddle.x));
        }

        function brickHit(brick, dx, dy) {
            brick.health--;
            
            // Create crack effect if needed
            if (brick.type === 'mixed' && brick.health === 1) {
                brick.cracked = true;
                createCrackEffect(brick);
            }
            
            if (brick.health <= 0) {
                brick.broken = true;
                game.score += brick.col + 1;
                createBrickExplosion(brick);
                
                // Chance to drop power-up
                if (brick.hasPowerUp) {
                    const powerType = Math.random() < 0.5 ? 'widen' : 'multi';
                    powerUps.push({
                        x: brick.x + brick.width/2,
                        y: brick.y + brick.height/2,
                        width: 20,
                        height: 20,
                        type: powerType,
                        vy: 2,
                        rot: 0
                    });
                }
            } else {
                // Hit tough brick - add score
                game.score += 5;
                createHitEffect(brick);
            }
        }

        function updatePowerUps(dt) {
            for (let i = powerUps.length - 1; i >= 0; i--) {
                const powerUp = powerUps[i];
                powerUp.y += powerUp.vy;
                powerUp.rot += 0.05;
                
                // Check paddle collision
                if (powerUp.y + 10 >= paddle.y &&
                    powerUp.y - 10 <= paddle.y + paddle.height &&
                    powerUp.x + 10 >= paddle.x &&
                    powerUp.x - 10 <= paddle.x + paddle.width) {
                    
                    applyPowerUp(powerUp.type);
                    powerUps.splice(i, 1);
                } else if (powerUp.y > canvas.height) {
                    powerUps.splice(i, 1);
                }
            }
        }

        function applyPowerUp(type) {
            if (type === 'widen') {
                activeEffects.widePaddle = game.time + 10; // 10 seconds
                createPowerUpNotification('WIDE PADDLE!', colors.powerUpWiden);
            } else if (type === 'multi') {
                // Create 3 new balls from existing one
                if (balls.length > 0) {
                    const originalBall = balls[0];
                    for (let i = 0; i < 3; i++) {
                        const angle = (i - 1) * 0.3; // Spread angles
                        const speed = Math.hypot(originalBall.dx, originalBall.dy);
                        balls.push({
                            x: originalBall.x,
                            y: originalBall.y,
                            dx: speed * Math.sin(angle),
                            dy: -speed * Math.cos(angle),
                            radius: originalBall.radius,
                            active: true
                        });
                    }
                }
                activeEffects.multiBall = game.time + 5; // Multi-ball lasts 5 seconds
                createPowerUpNotification('MULTI-BALL!', colors.powerUpMulti);
            }
        }

        function updateEffects(dt) {
            // Widen paddle effect
            if (activeEffects.widePaddle && game.time > activeEffects.widePaddle) {
                activeEffects.widePaddle = null;
                paddle.width = paddle.baseWidth;
            } else if (!activeEffects.widePaddle) {
                paddle.width += (paddle.targetWidth - paddle.width) * 0.1;
            }
            
            // Multi-ball effect (just ensures there are multiple balls)
            if (activeEffects.multiBall && game.time > activeEffects.multiBall) {
                activeEffects.multiBall = null;
            }
        }

        function createBrickExplosion(brick) {
            for (let i = 0; i < 15; i++) {
                const angle = Math.random() * Math.PI * 2;
                const speed = 2 + Math.random() * 4;
                particles.push({
                    x: brick.x + brick.width/2,
                    y: brick.y + brick.height/2,
                    dx: Math.cos(angle) * speed,
                    dy: Math.sin(angle) * speed,
                    life: 1,
                    decay: 0.02 + Math.random() * 0.03,
                    size: 3 + Math.random() * 4,
                    color: brick.type === 'tough' ? colors.toughBrick : 
                           brick.cracked ? colors.crackedBrick : colors.normalBrick
                });
            }
        }

        function createHitEffect(brick) {
            for (let i = 0; i < 5; i++) {
                const angle = Math.random() * Math.PI * 2;
                const speed = 1 + Math.random() * 2;
                particles.push({
                    x: brick.x + brick.width/2,
                    y: brick.y + brick.height/2,
                    dx: Math.cos(angle) * speed,
                    dy: Math.sin(angle) * speed,
                    life: 0.7,
                    decay: 0.03,
                    size: 2 + Math.random() * 3,
                    color: '#ffffff'
                });
            }
        }

        function createCrackEffect(brick) {
            for (let i = 0; i < 8; i++) {
                const angle = Math.random() * Math.PI * 2;
                const speed = 1 + Math.random() * 2;
                particles.push({
                    x: brick.x + brick.width/2,
                    y: brick.y + brick.height/2,
                    dx: Math.cos(angle) * speed,
                    dy: Math.sin(angle) * speed,
                    life: 0.9,
                    decay: 0.04,
                    size: 2 + Math.random() * 2,
                    color: '#94a3b8'
                });
            }
        }

        function createWallParticles(x, y) {
            for (let i = 0; i < 5; i++) {
                particles.push({
                    x, y,
                    dx: (Math.random() - 0.5) * 4,
                    dy: (Math.random() - 0.5) * 4,
                    life: 0.5,
                    decay: 0.05,
                    size: 2 + Math.random() * 2,
                    color: '#00ffff'
                });
            }
        }

        function createPaddleParticles(x, y) {
            for (let i = 0; i < 8; i++) {
                const angle = (Math.random() - 0.5) * Math.PI;
                const speed = 1 + Math.random() * 3;
                particles.push({
                    x, y,
                    dx: Math.cos(angle) * speed,
                    dy: Math.sin(angle) * speed,
                    life: 0.8,
                    decay: 0.03,
                    size: 2 + Math.random() * 3,
                    color: '#00b4d8'
                });
            }
        }

        function createDeathParticles(x, y) {
            for (let i = 0; i < 20; i++) {
                const angle = Math.random() * Math.PI * 2;
                const speed = 2 + Math.random() * 5;
                particles.push({
                    x, y,
                    dx: Math.cos(angle) * speed,
                    dy: Math.sin(angle) * speed,
                    life: 1.2,
                    decay: 0.02,
                    size: 3 + Math.random() * 5,
                    color: ['#ff0000', '#ff5722', '#ff9e00'][Math.floor(Math.random() * 3)]
                });
            }
        }

        function createPowerUpNotification(text, color) {
            // Visual effect could be added here
        }

        function updateParticles(dt) {
            for (let i = particles.length - 1; i >= 0; i--) {
                const p = particles[i];
                p.x += p.dx;
                p.y += p.dy;
                p.life -= p.decay;
                p.dx *= 0.98;
                p.dy *= 0.98;
                
                if (p.life <= 0) {
                    particles.splice(i, 1);
                }
            }
        }

        // ===================== RENDER =====================
        function render() {
            // Clear canvas
            ctx.fillStyle = '#0a1421';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // Draw grid background
            drawGrid();
            
            // Draw bricks
            drawBricks();
            
            // Draw paddle
            drawPaddle();
            
            // Draw balls
            drawBalls();
            
            // Draw power-ups
            drawPowerUps();
            
            // Draw particles
            drawParticles();
            
            // Draw HUD
            drawHUD();
        }

        function drawGrid() {
            ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
            ctx.lineWidth = 1;
            const gridSize = 40;
            
            for (let x = 0; x < canvas.width; x += gridSize) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, canvas.height);
                ctx.stroke();
            }
            
            for (let y = 0; y < canvas.height; y += gridSize) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(canvas.width, y);
                ctx.stroke();
            }
        }

        function drawBricks() {
            for (let brick of bricks) {
                if (brick.broken) continue;
                
                // Base color
                let baseColor = colors.normalBrick;
                if (brick.type === 'tough') baseColor = colors.toughBrick;
                else if (brick.cracked) baseColor = colors.crackedBrick;
                
                // Fill brick
                ctx.fillStyle = baseColor;
                ctx.fillRect(brick.x, brick.y, brick.width, brick.height);
                
                // Add gradient for 3D effect
                const gradient = ctx.createLinearGradient(
                    brick.x, brick.y,
                    brick.x, brick.y + brick.height
                );
                gradient.addColorStop(0, 'rgba(255,255,255,0.3)');
                gradient.addColorStop(0.5, 'rgba(255,255,255,0)');
                ctx.fillStyle = gradient;
                ctx.fillRect(brick.x, brick.y, brick.width, brick.height);
                
                // Border
                ctx.strokeStyle = 'rgba(0,0,0,0.3)';
                ctx.lineWidth = 2;
                ctx.strokeRect(brick.x, brick.y, brick.width, brick.height);
                
                // Crack pattern if cracked
                if (brick.cracked) {
                    ctx.strokeStyle = 'rgba(0,0,0,0.4)';
                    ctx.lineWidth = 1.5;
                    ctx.beginPath();
                    ctx.moveTo(brick.x + 10, brick.y + 5);
                    ctx.lineTo(brick.x + 18, brick.y + 15);
                    ctx.lineTo(brick.x + 12, brick.y + 20);
                    ctx.stroke();
                    
                    ctx.beginPath();
                    ctx.moveTo(brick.x + brick.width - 15, brick.y + 8);
                    ctx.lineTo(brick.x + brick.width - 10, brick.y + 18);
                    ctx.lineTo(brick.x + brick.width - 20, brick.y + 12);
                    ctx.stroke();
                }
                
                // Power-up icon
                if (brick.hasPowerUp) {
                    ctx.fillStyle = 'rgba(255,255,255,0.9)';
                    ctx.beginPath();
                    ctx.arc(brick.x + brick.width/2, brick.y + brick.height/2, 5, 0, Math.PI*2);
                    ctx.fill();
                    
                    // Star shape
                    ctx.fillStyle = '#ffffff';
                    ctx.beginPath();
                    ctx.moveTo(brick.x + brick.width/2, brick.y + 3);
                    ctx.lineTo(brick.x + brick.width/2 - 2, brick.y + 7);
                    ctx.lineTo(brick.x + brick.width/2 - 3, brick.y + 10);
                    ctx.lineTo(brick.x + brick.width/2, brick.y + 13);
                    ctx.lineTo(brick.x + brick.width/2 + 3, brick.y + 10);
                    ctx.lineTo(brick.x + brick.width/2 + 2, brick.y + 7);
                    ctx.closePath();
                    ctx.fill();
                }
            }
        }

        function drawPaddle() {
            // Gradient
            const gradient = ctx.createLinearGradient(
                paddle.x, paddle.y,
                paddle.x, paddle.y + paddle.height
            );
            gradient.addColorStop(0, '#4deeea');
            gradient.addColorStop(0.5, '#00b4d8');
            gradient.addColorStop(1, '#0077b6');
            ctx.fillStyle = gradient;
            ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);
            
            // Highlight
            ctx.fillStyle = 'rgba(255,255,255,0.3)';
            ctx.fillRect(paddle.x, paddle.y, paddle.width, 4);
            
            // Glow effect if wide paddle active
            if (activeEffects.widePaddle) {
                ctx.shadowColor = colors.powerUpWiden;
                ctx.shadowBlur = 20;
                ctx.strokeStyle = colors.powerUpWiden;
                ctx.lineWidth = 2;
                ctx.strokeRect(paddle.x, paddle.y, paddle.width, paddle.height);
                ctx.shadowBlur = 0;
            }
        }

        function drawBalls() {
            for (let ball of balls) {
                if (!ball.active) continue;
                
                // Ball glow
                ctx.shadowColor = '#ffffff';
                ctx.shadowBlur = 15;
                ctx.fillStyle = colors.ball;
                ctx.beginPath();
                ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
                ctx.fill();
                ctx.shadowBlur = 0;
                
                // Inner highlight
                ctx.fillStyle = 'rgba(255,255,255,0.8)';
                ctx.beginPath();
                ctx.arc(ball.x - 2, ball.y - 2, ball.radius/3, 0, Math.PI * 2);
                ctx.fill();
            }
        }

        function drawPowerUps() {
            for (let powerUp of powerUps) {
                ctx.save();
                ctx.translate(powerUp.x, powerUp.y);
                ctx.rotate(powerUp.rot);
                
                // Different shapes for different power-ups
                if (powerUp.type === 'widen') {
                    // Diamond shape
                    ctx.fillStyle = colors.powerUpWiden;
                    ctx.beginPath();
                    ctx.moveTo(0, -10);
                    ctx.lineTo(10, 0);
                    ctx.lineTo(0, 10);
                    ctx.lineTo(-10, 0);
                    ctx.closePath();
                    ctx.fill();
                    
                    // Glow
                    ctx.shadowColor = colors.powerUpWiden;
                    ctx.shadowBlur = 10;
                    ctx.strokeStyle = '#ffffff';
                    ctx.lineWidth = 1;
                    ctx.stroke();
                    ctx.shadowBlur = 0;
                } else {
                    // Star shape
                    ctx.fillStyle = colors.powerUpMulti;
                    ctx.beginPath();
                    for (let i = 0; i < 5; i++) {
                        const angle = (i * 2 * Math.PI / 5) - Math.PI/2;
                        const x = Math.cos(angle) * 10;
                        const y = Math.sin(angle) * 10;
                        if (i === 0) ctx.moveTo(x, y);
                        else ctx.lineTo(x, y);
                        
                        const innerAngle = angle + Math.PI/5;
                        const innerX = Math.cos(innerAngle) * 4;
                        const innerY = Math.sin(innerAngle) * 4;
                        ctx.lineTo(innerX, innerY);
                    }
                    ctx.closePath();
                    ctx.fill();
                    
                    ctx.shadowColor = colors.powerUpMulti;
                    ctx.shadowBlur = 10;
                    ctx.strokeStyle = '#ffffff';
                    ctx.lineWidth = 1;
                    ctx.stroke();
                    ctx.shadowBlur = 0;
                }
                
                ctx.restore();
            }
        }

        function drawParticles() {
            for (let particle of particles) {
                ctx.globalAlpha = particle.life;
                ctx.fillStyle = particle.color;
                ctx.fillRect(
                    particle.x - particle.size/2,
                    particle.y - particle.size/2,
                    particle.size,
                    particle.size
                );
            }
            ctx.globalAlpha = 1;
        }

        function drawHUD() {
            // Score
            ctx.fillStyle = 'rgba(0,0,0,0.6)';
            ctx.fillRect(10, 10, 200, 50);
            ctx.fillStyle = '#ffffff';
            ctx.font = 'bold 20px monospace';
            ctx.textAlign = 'left';
            ctx.fillText(`SCORE: ${game.score}`, 20, 35);
            
            // Lives
            ctx.textAlign = 'right';
            ctx.fillText('LIVES:', canvas.width - 50, 30);
            
            // Draw hearts
            for (let i = 0; i < game.lives; i++) {
                drawHeart(canvas.width - 20 - (i * 30), 10, 12);
            }
            
            // Active effects timer
            if (activeEffects.widePaddle) {
                const remainingTime = Math.ceil(activeEffects.widePaddle - game.time);
                ctx.fillStyle = 'rgba(0,0,0,0.6)';
                ctx.fillRect(10, 65, 150, 30);
                ctx.fillStyle = colors.powerUpWiden;
                ctx.font = 'bold 14px monospace';
                ctx.fillText(`WIDE PADDLE: ${remainingTime}s`, 20, 85);
            }
            
            // Multi-ball count indicator
            if (balls.length > 1) {
                ctx.fillStyle = 'rgba(0,0,0,0.6)';
                ctx.fillRect(canvas.width - 160, 65, 150, 30);
                ctx.fillStyle = colors.powerUpMulti;
                ctx.font = 'bold 14px monospace';
                ctx.textAlign = 'right';
                ctx.fillText(`BALLS: ${balls.length}`, canvas.width - 20, 85);
            }
        }

        function drawHeart(x, y, size) {
            ctx.fillStyle = '#ff5722';
            ctx.beginPath();
            const width = size;
            const height = size * 1.2;
            ctx.moveTo(x + width/2, y + height);
            ctx.bezierCurveTo(
                x + width, y + height - height/3,
                x + width + width/4, y,
                x + width/2, y + height/4
            );
            ctx.bezierCurveTo(
                x, y,
                x - width/4, y,
                x + width/2, y + height/4
            );
            ctx.bezierCurveTo(
                x - width/4, y,
                x - width, y + height - height/3,
                x + width/2, y + height
            );
            ctx.fill();
            
            // Glow
            ctx.shadowColor = '#ff5722';
            ctx.shadowBlur = 10;
            ctx.fill();
            ctx.shadowBlur = 0;
        }

        // ===================== SCREENS =====================
        function drawMenuBackground() {
            // Animated background for menu
            ctx.fillStyle = '#0a1421';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // Moving stars
            const time = Date.now() * 0.001;
            for (let i = 0; i < 50; i++) {
                const x = (Math.sin(time + i) * 0.5 + 0.5) * canvas.width;
                const y = (Math.cos(time * 0.7 + i) * 0.5 + 0.5) * canvas.height;
                const size = Math.random() * 2 + 1;
                ctx.fillStyle = `rgba(255,255,255,${Math.random() * 0.5})`;
                ctx.fillRect(x, y, size, size);
            }
        }

        function showVictoryScreen() {
            ctx.fillStyle = 'rgba(0,0,0,0.8)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            ctx.fillStyle = '#ffeb3b';
            ctx.font = `bold ${Math.min(48, canvas.width/15)}px Arial`;
            ctx.textAlign = 'center';
            ctx.fillText('VICTORY!', centerX, canvas.height/2 - 50);
            
            ctx.fillStyle = '#ffffff';
            ctx.font = `${Math.min(24, canvas.width/30)}px Arial`;
            ctx.fillText(`Final Score: ${game.score}`, centerX, canvas.height/2);
            
            ctx.fillText('Press START to play again', centerX, canvas.height/2 + 50);
            
            const button = document.createElement('button');
            button.textContent = 'PLAY AGAIN';
            button.style.cssText = `
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                padding: 1rem 2.5rem;
                font-size: 1.2rem;
                background: linear-gradient(to right, #00c6ff, #0072ff);
                border: none;
                border-radius: 50px;
                color: white;
                cursor: pointer;
                z-index: 100;
            `;
            button.onclick = () => {
                document.body.removeChild(document.querySelector('.controls-hint'));
                document.body.removeChild(button);
                startGame();
            };
            document.body.appendChild(button);
        }

        function endGame() {
            game.state = 'GAMEOVER';
            ctx.fillStyle = 'rgba(0,0,0,0.85)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            ctx.fillStyle = '#ff5722';
            ctx.font = `bold ${Math.min(48, canvas.width/15)}px Arial`;
            ctx.textAlign = 'center';
            ctx.fillText('GAME OVER', centerX, canvas.height/2 - 50);
            
            ctx.fillStyle = '#ffffff';
            ctx.font = `${Math.min(24, canvas.width/30)}px Arial`;
            ctx.fillText(`Score: ${game.score}`, centerX, canvas.height/2);
            
            // Show restart button
            const button = document.createElement('button');
            button.textContent = 'RESTART';
            button.style.cssText = `
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                padding: 1rem 2.5rem;
                font-size: 1.2rem;
                background: linear-gradient(to right, #00c6ff, #0072ff);
                border: none;
                border-radius: 50px;
                color: white;
                cursor: pointer;
                z-index: 100;
            `;
            button.onclick = () => startGame();
            document.body.appendChild(button);
        }

        // ===================== START =====================
        init();
        game.lastTime = performance.now();
        requestAnimationFrame(gameLoop);
    </script>
</body>
</html>
Reasoning trace (457 tokens)
We are going to build a polished Breakout game in a single HTML file using vanilla JavaScript and a <canvas>. The game will include:

1. A paddle controlled by mouse and left/right arrow keys.
2. Ball bounce angle based on hit position (center = straight, edges = sharper).
3. One level with at least 6 rows of bricks (mix of normal and tougher bricks that need 2 hits and crack after first hit).
4. Power-ups that drop when bricks break (wider paddle and multi-ball). Catch with paddle to activate for 10 seconds or until ball count changes.
5. Particles when a brick breaks.
6. 3 lives, score display. Game over and victory screens with restart option.

We'll structure the code as follows:

- HTML structure: a canvas element inside a container.
- CSS: basic styling to center the game, but we'll keep it minimal because we are going to use a fixed size for the game area and scale it to fit the viewport. We want it to look good at 1280x800 and adapt down to 375px.
- JavaScript: 
  - Define game objects (paddle, ball, bricks, particles, power-ups).
  - Game loop (update and render).
  - Input handling (mouse and keyboard).
  - Collision detection (ball vs walls, paddle, bricks, power-ups).
  - Game states (menu, playing, game over, victory).
  - Helper functions for drawing, updating, collision checks.

Let's start by writing the HTML and CSS, then the JavaScript.

Note: We are not allowed to use external resources except allowed ones. We are allowed to use CDN URLs if needed, but we don't have any required fonts or images. So we'll draw everything with canvas.

We'll design the game with a fixed internal resolution (e.g., 800x600) and scale the canvas to fit the viewport while maintaining aspect ratio.

Let's code step by step.