← back to Model Arena

data/artifacts/035b7009e366/kimi.html

934 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>Designer Wallcoverings - The Roll</title>
    <style>
        :root {
            /* Luxury Design Tokens */
            --bg: #0d0d0f;
            --surface: #17171c;
            --surface-elevated: #1e1e26;
            --ink: #f5f2ea;
            --muted: #8a8578;
            --accent: #c9a961;
            --accent-glow: rgba(201, 169, 97, 0.3);
            --line: rgba(245, 242, 234, 0.12);
            --shadow: rgba(0, 0, 0, 0.6);
            --shadow-soft: 0 8px 32px rgba(0, 0, 0, 0.4);
            
            /* Typography */
            --font-display: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
            --font-body: 'Avenir Next', 'Century Gothic', Futura, 'Segoe UI', sans-serif;
            
            /* Motion */
            --ease-swift: cubic-bezier(.22,.9,.24,1);
            --ease-overshoot: cubic-bezier(.34,1.56,.64,1);
            --ease-quint: cubic-bezier(.22,1,.36,1);
            
            /* Spacing */
            --space-xs: 4px;
            --space-sm: 8px;
            --space-md: 16px;
            --space-lg: 24px;
            --space-xl: 40px;
        }
        
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: var(--font-body);
            background: var(--bg);
            color: var(--ink);
            overflow: hidden;
            touch-action: none;
            user-select: none;
            -webkit-user-select: none;
        }
        
        /* Fixed Top Bar - Logo Zone */
        .top-bar {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            height: 72px;
            background: linear-gradient(180deg, var(--bg) 0%, rgba(13,13,15,0.95) 100%);
            border-bottom: 1px solid var(--line);
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 var(--space-lg);
            z-index: 100;
            backdrop-filter: blur(8px);
        }
        
        .wordmark {
            font-family: var(--font-display);
            font-size: 22px;
            letter-spacing: 0.18em;
            text-transform: uppercase;
            color: var(--ink);
            font-weight: 400;
            line-height: 1.1;
        }
        
        .score-board {
            display: flex;
            gap: var(--space-lg);
            align-items: center;
        }
        
        .score-item {
            font-family: var(--font-body);
            font-size: 11px;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--muted);
            display: flex;
            flex-direction: column;
            align-items: flex-end;
        }
        
        .score-value {
            font-size: 18px;
            color: var(--accent);
            font-weight: 600;
            letter-spacing: 0.05em;
        }
        
        /* Game Container */
        #game-container {
            position: fixed;
            top: 72px;
            left: 0;
            right: 0;
            bottom: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }
        
        canvas {
            display: block;
            box-shadow: var(--shadow-soft);
            border: 1px solid var(--line);
            background: var(--surface);
        }
        
        /* Overlays */
        .overlay {
            position: fixed;
            top: 72px;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(13, 13, 15, 0.92);
            backdrop-filter: blur(12px);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 50;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.4s var(--ease-quint);
        }
        
        .overlay.active {
            opacity: 1;
            pointer-events: all;
        }
        
        .overlay-content {
            text-align: center;
            max-width: 480px;
            padding: var(--space-xl);
            animation: slideUp 0.6s var(--ease-overshoot) forwards;
        }
        
        @keyframes slideUp {
            from { transform: translateY(40px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }
        
        .overlay h2 {
            font-family: var(--font-display);
            font-size: 42px;
            letter-spacing: -0.02em;
            color: var(--ink);
            margin-bottom: var(--space-md);
            font-weight: 400;
        }
        
        .overlay p {
            font-size: 14px;
            color: var(--muted);
            line-height: 1.6;
            margin-bottom: var(--space-xl);
            letter-spacing: 0.02em;
        }
        
        .btn {
            font-family: var(--font-body);
            font-size: 12px;
            letter-spacing: 0.18em;
            text-transform: uppercase;
            color: var(--bg);
            background: var(--accent);
            border: none;
            padding: 16px 40px;
            cursor: pointer;
            transition: all 0.3s var(--ease-swift);
            box-shadow: 0 4px 24px var(--accent-glow);
        }
        
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 32px var(--accent-glow);
        }
        
        .btn:active {
            transform: translateY(0);
        }
        
        .final-score {
            font-family: var(--font-display);
            font-size: 64px;
            color: var(--accent);
            margin: var(--space-md) 0;
            letter-spacing: -0.02em;
        }
        
        .best-score {
            font-size: 12px;
            letter-spacing: 0.12em;
            text-transform: uppercase;
            color: var(--muted);
            margin-bottom: var(--space-xl);
        }
        
        /* Instructions */
        .instructions {
            position: absolute;
            bottom: var(--space-xl);
            left: 50%;
            transform: translateX(-50%);
            font-size: 11px;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--muted);
            opacity: 0.7;
        }
        
        /* Visual indicator for controls */
        .controls-hint {
            display: flex;
            gap: var(--space-md);
            margin-top: var(--space-lg);
            justify-content: center;
        }
        
        .key {
            width: 44px;
            height: 44px;
            border: 1px solid var(--line);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 10px;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            color: var(--muted);
            background: var(--surface-elevated);
        }
        
        /* Responsive */
        @media (max-width: 640px) {
            .wordmark {
                font-size: 16px;
                letter-spacing: 0.14em;
                line-height: 1.2;
            }
            
            .top-bar {
                padding: 0 var(--space-md);
                height: 64px;
            }
            
            #game-container {
                top: 64px;
            }
            
            .overlay {
                top: 64px;
            }
            
            .overlay h2 {
                font-size: 32px;
            }
        }
    </style>
</head>
<body>
    <!-- Top Bar -->
    <header class="top-bar">
        <div class="wordmark">Designer Wallcoverings</div>
        <div class="score-board">
            <div class="score-item">
                <span>Current</span>
                <span class="score-value" id="current-score">0</span>
            </div>
            <div class="score-item">
                <span>Best</span>
                <span class="score-value" id="best-score">0</span>
            </div>
        </div>
    </header>
    
    <!-- Game Container -->
    <div id="game-container">
        <canvas id="game"></canvas>
        <div class="instructions">Tap or press space to jump</div>
    </div>
    
    <!-- Start Overlay -->
    <div class="overlay active" id="start-overlay">
        <div class="overlay-content">
            <h2>The Roll</h2>
            <p>Guide the roller through the atelier. Collect rolls of distinguished wallcoverings while avoiding obstacles. Speed increases with each pattern collected.</p>
            <button class="btn" onclick="startGame()">Begin Journey</button>
            <div class="controls-hint">
                <div class="key">Space</div>
                <div class="key">Tap</div>
            </div>
        </div>
    </div>
    
    <!-- Game Over Overlay -->
    <div class="overlay" id="game-over-overlay">
        <div class="overlay-content">
            <h2>Atelier Closed</h2>
            <div class="final-score" id="final-score">0</div>
            <div class="best-score" id="final-best">Best: 0</div>
            <button class="btn" onclick="restartGame()">New Collection</button>
        </div>
    </div>
    
    <script>
        // Game Setup
        const canvas = document.getElementById('game');
        const ctx = canvas.getContext('2d');
        const startOverlay = document.getElementById('start-overlay');
        const gameOverOverlay = document.getElementById('game-over-overlay');
        const currentScoreEl = document.getElementById('current-score');
        const bestScoreEl = document.getElementById('best-score');
        const finalScoreEl = document.getElementById('final-score');
        const finalBestEl = document.getElementById('final-best');
        
        // Game Constants
        const CANVAS_WIDTH = 800;
        const CANVAS_HEIGHT = 450;
        const GROUND_Y = 380;
        const GRAVITY = 0.6;
        const JUMP_FORCE = -12;
        const BASE_SPEED = 5;
        const MAX_SPEED = 12;
        
        // Set canvas size
        function resizeCanvas() {
            const container = document.getElementById('game-container');
            const maxWidth = Math.min(container.clientWidth - 32, CANVAS_WIDTH);
            const scale = maxWidth / CANVAS_WIDTH;
            canvas.style.width = maxWidth + 'px';
            canvas.style.height = (CANVAS_HEIGHT * scale) + 'px';
            canvas.width = CANVAS_WIDTH;
            canvas.height = CANVAS_HEIGHT;
        }
        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);
        
        // Game State
        let gameState = 'start'; // start, playing, gameover
        let score = 0;
        let bestScore = localStorage.getItem('dw_bestScore') || 0;
        bestScoreEl.textContent = bestScore;
        
        let speed = BASE_SPEED;
        let frame = 0;
        let distance = 0;
        
        // Player (Paint Roller)
        const player = {
            x: 100,
            y: GROUND_Y - 35,
            width: 50,
            height: 50,
            vy: 0,
            jumping: false,
            rotation: 0,
            color: '#c9a961'
        };
        
        // Arrays for game objects
        let collectibles = [];
        let obstacles = [];
        let particles = [];
        let wallPatterns = [];
        
        // Wallpaper pattern definitions (luxury motifs)
        const patterns = [
            { name: 'herringbone', color: '#c9a961', type: 'geometric' },
            { name: 'stripe', color: '#8a8578', type: 'classic' },
            { name: 'damask', color: '#7d6a45', type: 'ornate' },
            { name: 'geo', color: '#f5f2ea', type: 'modern' }
        ];
        
        // Initialize wall patterns
        function initWallPatterns() {
            wallPatterns = [];
            for (let i = 0; i < 20; i++) {
                wallPatterns.push({
                    x: i * 100,
                    pattern: patterns[Math.floor(Math.random() * patterns.length)]
                });
            }
        }
        
        // Input Handling
        function jump() {
            if (gameState !== 'playing') return;
            if (!player.jumping) {
                player.vy = JUMP_FORCE;
                player.jumping = true;
                createJumpParticles();
            }
        }
        
        document.addEventListener('keydown', (e) => {
            if (e.code === 'Space') {
                e.preventDefault();
                if (gameState === 'start') startGame();
                else if (gameState === 'gameover') restartGame();
                else jump();
            }
        });
        
        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            if (gameState === 'start') startGame();
            else if (gameState === 'gameover') restartGame();
            else jump();
        });
        
        canvas.addEventListener('mousedown', (e) => {
            if (gameState === 'start') startGame();
            else if (gameState === 'gameover') restartGame();
            else jump();
        });
        
        // Particle System
        class Particle {
            constructor(x, y, color, speed) {
                this.x = x;
                this.y = y;
                this.vx = (Math.random() - 0.5) * speed;
                this.vy = (Math.random() - 0.5) * speed;
                this.life = 1;
                this.decay = 0.02 + Math.random() * 0.02;
                this.color = color;
                this.size = 2 + Math.random() * 4;
            }
            
            update() {
                this.x += this.vx;
                this.y += this.vy;
                this.life -= this.decay;
                this.size *= 0.98;
            }
            
            draw() {
                ctx.save();
                ctx.globalAlpha = this.life;
                ctx.fillStyle = this.color;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
                ctx.fill();
                ctx.restore();
            }
        }
        
        function createJumpParticles() {
            for (let i = 0; i < 8; i++) {
                particles.push(new Particle(player.x + 25, player.y + 50, '#c9a961', 3));
            }
        }
        
        function createCollectParticles(x, y, color) {
            for (let i = 0; i < 12; i++) {
                particles.push(new Particle(x, y, color, 5));
            }
        }
        
        function createCrashParticles(x, y) {
            for (let i = 0; i < 20; i++) {
                particles.push(new Particle(x, y, '#8a8578', 6));
            }
        }
        
        // Collectible (Wallpaper Roll)
        class WallpaperRoll {
            constructor(x) {
                this.x = x;
                this.y = GROUND_Y - 30 - (Math.random() > 0.5 ? 0 : 80);
                this.width = 30;
                this.height = 40;
                this.pattern = patterns[Math.floor(Math.random() * patterns.length)];
                this.collected = false;
                this.bobOffset = Math.random() * Math.PI * 2;
            }
            
            update() {
                this.x -= speed;
                this.bobOffset += 0.05;
            }
            
            draw() {
                if (this.collected) return;
                
                const bob = Math.sin(this.bobOffset) * 3;
                const y = this.y + bob;
                
                ctx.save();
                
                // Roll shadow
                ctx.fillStyle = 'rgba(0,0,0,0.3)';
                ctx.beginPath();
                ctx.ellipse(this.x + 15, y + 38, 12, 4, 0, 0, Math.PI * 2);
                ctx.fill();
                
                // Roll body
                ctx.fillStyle = '#1e1e26';
                ctx.fillRect(this.x + 5, y, 20, 35);
                
                // Pattern preview on roll
                ctx.fillStyle = this.pattern.color;
                if (this.pattern.type === 'geometric') {
                    // Herringbone pattern
                    ctx.beginPath();
                    ctx.moveTo(this.x + 8, y + 5);
                    ctx.lineTo(this.x + 12, y + 10);
                    ctx.lineTo(this.x + 16, y + 5);
                    ctx.lineTo(this.x + 20, y + 10);
                    ctx.stroke();
                } else if (this.pattern.type === 'classic') {
                    // Stripe pattern
                    ctx.fillRect(this.x + 8, y + 5, 2, 25);
                    ctx.fillRect(this.x + 14, y + 5, 2, 25);
                    ctx.fillRect(this.x + 20, y + 5, 2, 25);
                } else if (this.pattern.type === 'ornate') {
                    // Damask approximation
                    ctx.beginPath();
                    ctx.arc(this.x + 15, y + 17, 6, 0, Math.PI * 2);
                    ctx.fill();
                } else {
                    // Modern geometric
                    ctx.fillRect(this.x + 8, y + 8, 14, 14);
                }
                
                // Roll ends
                ctx.fillStyle = '#f5f2ea';
                ctx.beginPath();
                ctx.ellipse(this.x + 15, y, 10, 4, 0, 0, Math.PI * 2);
                ctx.fill();
                ctx.fillStyle = '#8a8578';
                ctx.beginPath();
                ctx.ellipse(this.x + 15, y + 35, 10, 4, 0, 0, Math.PI * 2);
                ctx.fill();
                
                // Glow for visibility
                ctx.shadowColor = this.pattern.color;
                ctx.shadowBlur = 10;
                ctx.strokeStyle = this.pattern.color;
                ctx.lineWidth = 1;
                ctx.strokeRect(this.x + 5, y, 20, 35);
                ctx.shadowBlur = 0;
                
                ctx.restore();
            }
        }
        
        // Obstacle
        class Obstacle {
            constructor(x) {
                this.x = x;
                this.y = GROUND_Y - 35;
                this.width = 40;
                this.height = 35;
                this.type = Math.random() > 0.5 ? 'spill' : 'ladder';
            }
            
            update() {
                this.x -= speed;
            }
            
            draw() {
                ctx.save();
                
                if (this.type === 'spill') {
                    // Paint spill obstacle
                    ctx.fillStyle = '#17171c';
                    ctx.beginPath();
                    ctx.ellipse(this.x + 20, this.y + 30, 20, 10, 0, 0, Math.PI * 2);
                    ctx.fill();
                    
                    // Spill droplets
                    ctx.fillStyle = '#8a8578';
                    for (let i = 0; i < 5; i++) {
                        const dx = Math.sin(i * 1.5) * 12;
                        const dy = Math.cos(i * 2) * 5;
                        ctx.beginPath();
                        ctx.arc(this.x + 20 + dx, this.y + 25 + dy, 3 + i, 0, Math.PI * 2);
                        ctx.fill();
                    }
                } else {
                    // Fallen ladder/pole
                    ctx.fillStyle = '#7d6a45';
                    ctx.fillRect(this.x, this.y + 20, 40, 6);
                    // Rungs
                    ctx.fillStyle = '#5a4a32';
                    for (let i = 0; i < 4; i++) {
                        ctx.fillRect(this.x + 5 + i * 10, this.y + 15, 4, 16);
                    }
                }
                
                ctx.restore();
            }
        }
        
        // Draw Functions
        function drawBackground() {
            // Wall gradient
            const gradient = ctx.createLinearGradient(0, 0, 0, CANVAS_HEIGHT);
            gradient.addColorStop(0, '#0d0d0f');
            gradient.addColorStop(1, '#17171c');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
            
            // Moving wall patterns
            ctx.save();
            ctx.globalAlpha = 0.08;
            wallPatterns.forEach((wp, i) => {
                wp.x -= speed * 0.3;
                if (wp.x < -100) {
                    wp.x = CANVAS_WIDTH + 50;
                    wp.pattern = patterns[Math.floor(Math.random() * patterns.length)];
                }
                
                ctx.fillStyle = wp.pattern.color;
                if (wp.pattern.type === 'geometric') {
                    // Chevron pattern
                    ctx.beginPath();
                    for (let j = 0; j < 3; j++) {
                        const px = wp.x + j * 30;
                        ctx.moveTo(px, 50);
                        ctx.lineTo(px + 15, 80);
                        ctx.lineTo(px + 30, 50);
                    }
                    ctx.stroke();
                } else if (wp.pattern.type === 'classic') {
                    // Vertical stripes
                    for (let j = 0; j < 3; j++) {
                        ctx.fillRect(wp.x + j * 25, 0, 4, CANVAS_HEIGHT);
                    }
                } else if (wp.pattern.type === 'ornate') {
                    // Ornamental circles
                    ctx.beginPath();
                    ctx.arc(wp.x + 40, 100, 30, 0, Math.PI * 2);
                    ctx.stroke();
                } else {
                    // Grid
                    ctx.strokeRect(wp.x, 60, 60, 80);
                    ctx.beginPath();
                    ctx.moveTo(wp.x, 60);
                    ctx.lineTo(wp.x + 60, 140);
                    ctx.moveTo(wp.x + 60, 60);
                    ctx.lineTo(wp.x, 140);
                    ctx.stroke();
                }
            });
            ctx.restore();
            
            // Baseboard
            ctx.fillStyle = '#1e1e26';
            ctx.fillRect(0, GROUND_Y, CANVAS_WIDTH, CANVAS_HEIGHT - GROUND_Y);
            
            // Floor detail
            ctx.strokeStyle = 'rgba(245, 242, 234, 0.05)';
            ctx.lineWidth = 1;
            for (let i = 0; i < CANVAS_WIDTH; i += 60) {
                const offset = (distance * 0.5) % 60;
                ctx.beginPath();
                ctx.moveTo(i - offset, GROUND_Y);
                ctx.lineTo(i - offset - 30, CANVAS_HEIGHT);
                ctx.stroke();
            }
        }
        
        function drawPlayer() {
            ctx.save();
            
            // Shadow
            const shadowScale = 1 - (GROUND_Y - player.y) / 200;
            ctx.fillStyle = 'rgba(0,0,0,0.4)';
            ctx.beginPath();
            ctx.ellipse(player.x + 25, GROUND_Y - 5, 20 * shadowScale, 6 * shadowScale, 0, 0, Math.PI * 2);
            ctx.fill();
            
            ctx.translate(player.x + 25, player.y + 25);
            ctx.rotate(player.rotation);
            
            // Roller handle (metal)
            ctx.fillStyle = '#8a8578';
            ctx.fillRect(-5, -20, 10, 25);
            
            // Handle grip
            ctx.fillStyle = '#7d6a45';
            ctx.fillRect(-6, -25, 12, 8);
            
            // Roller frame
            ctx.strokeStyle = '#c9a961';
            ctx.lineWidth = 3;
            ctx.beginPath();
            ctx.moveTo(0, 5);
            ctx.lineTo(0, 15);
            ctx.lineTo(-20, 20);
            ctx.stroke();
            
            // Roller cylinder
            const rollerGradient = ctx.createLinearGradient(-20, -10, 20, 10);
            rollerGradient.addColorStop(0, '#1e1e26');
            rollerGradient.addColorStop(0.3, '#c9a961');
            rollerGradient.addColorStop(0.7, '#c9a961');
            rollerGradient.addColorStop(1, '#1e1e26');
            
            ctx.fillStyle = rollerGradient;
            ctx.beginPath();
            ctx.ellipse(0, 20, 22, 14, 0, 0, Math.PI * 2);
            ctx.fill();
            
            // Roller texture lines (rotate with speed)
            ctx.strokeStyle = 'rgba(13,13,15,0.5)';
            ctx.lineWidth = 1;
            for (let i = -15; i < 15; i += 5) {
                const lineX = i + (distance * 2) % 5;
                ctx.beginPath();
                ctx.moveTo(lineX, 12);
                ctx.lineTo(lineX, 28);
                ctx.stroke();
            }
            
            ctx.restore();
        }
        
        function updatePlayer() {
            // Apply gravity
            player.vy += GRAVITY;
            player.y += player.vy;
            
            // Ground collision
            if (player.y >= GROUND_Y - 35) {
                player.y = GROUND_Y - 35;
                player.vy = 0;
                player.jumping = false;
            }
            
            // Rotate based on movement
            if (player.jumping) {
                player.rotation += 0.1;
            } else {
                player.rotation += speed * 0.03;
            }
        }
        
        function checkCollisions() {
            const playerRect = {
                left: player.x + 10,
                right: player.x + 40,
                top: player.y + 10,
                bottom: player.y + 45
            };
            
            // Check collectibles
            collectibles.forEach(c => {
                if (c.collected) return;
                
                const cRect = {
                    left: c.x + 5,
                    right: c.x + 25,
                    top: c.y,
                    bottom: c.y + 35
                };
                
                if (playerRect.left < cRect.right &&
                    playerRect.right > cRect.left &&
                    playerRect.top < cRect.bottom &&
                    playerRect.bottom > cRect.top) {
                    
                    c.collected = true;
                    score += 10;
                    distance += 50;
                    speed = Math.min(speed + 0.1, MAX_SPEED);
                    createCollectParticles(c.x + 15, c.y + 20, c.pattern.color);
                    currentScoreEl.textContent = score;
                }
            });
            
            // Check obstacles
            obstacles.forEach(o => {
                const oRect = {
                    left: o.x + 5,
                    right: o.x + 35,
                    top: o.y + 5,
                    bottom: o.y + 30
                };
                
                if (playerRect.left < oRect.right &&
                    playerRect.right > oRect.left &&
                    playerRect.top < oRect.bottom &&
                    playerRect.bottom > oRect.top) {
                    
                    gameOver();
                }
            });
        }
        
        function spawnObjects() {
            // Spawn collectibles
            if (frame % 90 === 0) {
                collectibles.push(new WallpaperRoll(CANVAS_WIDTH + 50));
            }
            
            // Spawn obstacles
            if (frame % 140 === 0 && Math.random() > 0.3) {
                obstacles.push(new Obstacle(CANVAS_WIDTH + 50));
            }
        }
        
        function updateObjects() {
            // Update collectibles
            collectibles = collectibles.filter(c => {
                c.update();
                return c.x > -50 && !c.collected;
            });
            
            // Update obstacles
            obstacles = obstacles.filter(o => {
                o.update();
                return o.x > -50;
            });
            
            // Update particles
            particles = particles.filter(p => {
                p.update();
                return p.life > 0;
            });
        }
        
        function drawObjects() {
            // Draw obstacles behind player
            obstacles.forEach(o => o.draw());
            
            // Draw collectibles
            collectibles.forEach(c => c.draw());
            
            // Draw particles
            particles.forEach(p => p.draw());
        }
        
        function update() {
            if (gameState !== 'playing') return;
            
            frame++;
            distance += speed;
            
            // Passive score for distance
            if (frame % 10 === 0) {
                score++;
                currentScoreEl.textContent = score;
            }
            
            updatePlayer();
            spawnObjects();
            updateObjects();
            checkCollisions();
        }
        
        function draw() {
            ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
            
            drawBackground();
            drawObjects();
            drawPlayer();
        }
        
        function loop() {
            update();
            draw();
            requestAnimationFrame(loop);
        }
        
        function startGame() {
            gameState = 'playing';
            score = 0;
            speed = BASE_SPEED;
            frame = 0;
            distance = 0;
            player.y = GROUND_Y - 35;
            player.vy = 0;
            player.rotation = 0;
            collectibles = [];
            obstacles = [];
            particles = [];
            
            currentScoreEl.textContent = '0';
            startOverlay.classList.remove('active');
            gameOverOverlay.classList.remove('active');
            
            initWallPatterns();
        }
        
        function gameOver() {
            gameState = 'gameover';
            createCrashParticles(player.x + 25, player.y + 25);
            
            if (score > bestScore) {
                bestScore = score;
                localStorage.setItem('dw_bestScore', bestScore);
                bestScoreEl.textContent = bestScore;
            }
            
            finalScoreEl.textContent = score;
            finalBestEl.textContent = 'Best: ' + bestScore;
            
            setTimeout(() => {
                gameOverOverlay.classList.add('active');
            }, 500);
        }
        
        function restartGame() {
            startGame();
        }
        
        // Initialize
        initWallPatterns();
        loop();
    </script>
</body>
</html>