{"sessionId":"d6240424-f688-4248-94b0-656d587edc5b","webSocketDebuggerUrl":"wss://api-dev.rendering.cfdata.org/api/v1/accounts/085b01cc6003ce9844e13100925d8913/devtools/browser/d6240424-f688-4248-94b0-656d587edc5b","target":"http://worker/hello","status":200,"body":"<!DOCTYPE html><html lang=\"en\"><head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Browser Run 3D</title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n        body {\n            background: #0a0a1a;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            min-height: 100vh;\n            font-family: 'Courier New', monospace;\n            overflow: hidden;\n        }\n        #gameContainer {\n            position: relative;\n            border: 4px solid #c9a227;\n            box-shadow: 0 0 20px rgba(201, 162, 39, 0.5);\n        }\n        #gameCanvas {\n            display: block;\n        }\n        #ui {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            padding: 8px 15px;\n            display: flex;\n            justify-content: space-between;\n            align-items: flex-start;\n            pointer-events: none;\n        }\n        #title {\n            font-size: 22px;\n            font-weight: bold;\n            color: #4ade80;\n            text-shadow: 2px 2px 0 #166534, 3px 3px 6px rgba(0,0,0,0.5);\n            letter-spacing: 2px;\n        }\n        #stats {\n            text-align: right;\n        }\n        #score, #coins {\n            font-size: 16px;\n            color: #facc15;\n            text-shadow: 1px 1px 0 #854d0e;\n            letter-spacing: 1px;\n        }\n        #lives {\n            margin-top: 3px;\n        }\n        .life {\n            display: inline-block;\n            width: 14px;\n            height: 14px;\n            border-radius: 50%;\n            margin-left: 3px;\n            background: conic-gradient(#ef4444 0deg 90deg, #f97316 90deg 180deg, #22c55e 180deg 270deg, #3b82f6 270deg 360deg);\n            box-shadow: 0 1px 3px rgba(0,0,0,0.3);\n        }\n        .life.lost {\n            opacity: 0.3;\n            filter: grayscale(1);\n        }\n        #startScreen, #gameOverScreen {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n            align-items: center;\n            background: rgba(10, 10, 26, 0.92);\n            z-index: 10;\n        }\n        #startScreen h1, #gameOverScreen h1 {\n            font-size: 36px;\n            color: #4ade80;\n            text-shadow: 3px 3px 0 #166534, 4px 4px 8px rgba(0,0,0,0.5);\n            margin-bottom: 12px;\n            letter-spacing: 3px;\n        }\n        #startScreen p, #gameOverScreen p {\n            font-size: 14px;\n            color: #facc15;\n            margin-bottom: 20px;\n        }\n        #finalScore {\n            font-size: 20px;\n            color: #facc15;\n            margin-bottom: 15px;\n        }\n        .btn {\n            padding: 10px 35px;\n            font-size: 18px;\n            font-family: 'Courier New', monospace;\n            background: linear-gradient(180deg, #4ade80, #22c55e);\n            border: 2px solid #166534;\n            color: #0a0a1a;\n            cursor: pointer;\n            text-shadow: 1px 1px 0 #86efac;\n            box-shadow: 0 3px 0 #166534, 0 4px 8px rgba(0,0,0,0.3);\n            transition: transform 0.1s, box-shadow 0.1s;\n            letter-spacing: 2px;\n        }\n        .btn:hover {\n            transform: translateY(2px);\n            box-shadow: 0 1px 0 #166534, 0 2px 4px rgba(0,0,0,0.3);\n        }\n        .btn:active {\n            transform: translateY(3px);\n            box-shadow: 0 0 0 #166534;\n        }\n        .hidden {\n            display: none !important;\n        }\n        #instructions {\n            margin-top: 20px;\n            color: #64748b;\n            font-size: 11px;\n        }\n    </style>\n</head>\n<body>\n    <div id=\"gameContainer\">\n        <canvas id=\"gameCanvas\" width=\"800\" height=\"600\"></canvas>\n        <div id=\"ui\">\n            <div id=\"title\">BROWSER RUN</div>\n            <div id=\"stats\">\n                <div id=\"score\">SCORE: 000000</div>\n                <div id=\"coins\">COINS: 0</div>\n                <div id=\"lives\">\n                    <span class=\"life\"></span>\n                    <span class=\"life\"></span>\n                    <span class=\"life\"></span>\n                    <span class=\"life\"></span>\n                </div>\n            </div>\n        </div>\n        <div id=\"startScreen\">\n            <h1>BROWSER RUN</h1>\n            <p>Run through the internet, avoid 404 errors!</p>\n            <button class=\"btn\" onclick=\"startGame()\">START</button>\n            <div id=\"instructions\">\n                SPACE / UP to Jump | LEFT / RIGHT to Switch Lanes\n            </div>\n        </div>\n        <div id=\"gameOverScreen\" class=\"hidden\">\n            <h1>GAME OVER</h1>\n            <div id=\"finalScore\">SCORE: 0</div>\n            <button class=\"btn\" onclick=\"restartGame()\">TRY AGAIN</button>\n        </div>\n    </div>\n\n    <script>\n        // ============================================\n        // BROWSER RUN 3D - WebGL Endless Runner Game\n        // ============================================\n\n        const canvas = document.getElementById('gameCanvas');\n        const gl = canvas.getContext('webgl', { antialias: false, alpha: false });\n        \n        if (!gl) {\n            alert('WebGL not supported!');\n        }\n\n        // Vertex Shader\n        const vsSource = `\n            attribute vec3 aPosition;\n            attribute vec4 aColor;\n            \n            uniform mat4 uProjection;\n            uniform mat4 uView;\n            \n            varying vec4 vColor;\n            varying float vDepth;\n            \n            void main() {\n                vec4 viewPos = uView * vec4(aPosition, 1.0);\n                gl_Position = uProjection * viewPos;\n                vColor = aColor;\n                vDepth = -viewPos.z;\n            }\n        `;\n\n        // Fragment Shader\n        const fsSource = `\n            precision mediump float;\n            \n            varying vec4 vColor;\n            varying float vDepth;\n            \n            uniform vec3 uFogColor;\n            \n            void main() {\n                float fogFactor = smoothstep(5.0, 80.0, vDepth);\n                vec3 finalColor = mix(vColor.rgb, uFogColor, fogFactor);\n                gl_FragColor = vec4(finalColor, vColor.a);\n            }\n        `;\n\n        // Compile shader\n        function compileShader(type, source) {\n            const shader = gl.createShader(type);\n            gl.shaderSource(shader, source);\n            gl.compileShader(shader);\n            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n                console.error('Shader error:', gl.getShaderInfoLog(shader));\n                return null;\n            }\n            return shader;\n        }\n\n        // Create program\n        const vs = compileShader(gl.VERTEX_SHADER, vsSource);\n        const fs = compileShader(gl.FRAGMENT_SHADER, fsSource);\n        const program = gl.createProgram();\n        gl.attachShader(program, vs);\n        gl.attachShader(program, fs);\n        gl.linkProgram(program);\n\n        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n            console.error('Program error:', gl.getProgramInfoLog(program));\n        }\n\n        // Get locations\n        const aPosition = gl.getAttribLocation(program, 'aPosition');\n        const aColor = gl.getAttribLocation(program, 'aColor');\n        const uProjection = gl.getUniformLocation(program, 'uProjection');\n        const uView = gl.getUniformLocation(program, 'uView');\n        const uFogColor = gl.getUniformLocation(program, 'uFogColor');\n\n        // Create buffers\n        const posBuffer = gl.createBuffer();\n        const colorBuffer = gl.createBuffer();\n\n        // Game constants\n        const LANE_WIDTH = 3;\n        const LANES = [-LANE_WIDTH, 0, LANE_WIDTH];\n\n        // Game state\n        let gameRunning = false;\n        let score = 0;\n        let coins = 0;\n        let lives = 4;\n        let gameSpeed = 0.4;\n        let distance = 0;\n        let lastTime = 0;\n        let animFrame = 0;\n\n        // Player\n        const player = {\n            x: 0,\n            y: 0,\n            z: 0,\n            targetLane: 1,\n            velocityY: 0,\n            isJumping: false,\n            invincible: 0\n        };\n\n        // Objects\n        let obstacles = [];\n        let collectibles = [];\n\n        // ============================================\n        // Matrix Math\n        // ============================================\n\n        function createMat4() {\n            return new Float32Array([\n                1, 0, 0, 0,\n                0, 1, 0, 0,\n                0, 0, 1, 0,\n                0, 0, 0, 1\n            ]);\n        }\n\n        function perspective(fov, aspect, near, far) {\n            const f = 1.0 / Math.tan(fov / 2);\n            const nf = 1 / (near - far);\n            return new Float32Array([\n                f / aspect, 0, 0, 0,\n                0, f, 0, 0,\n                0, 0, (far + near) * nf, -1,\n                0, 0, 2 * far * near * nf, 0\n            ]);\n        }\n\n        function lookAt(eye, target, up) {\n            const zAxis = normalize([eye[0] - target[0], eye[1] - target[1], eye[2] - target[2]]);\n            const xAxis = normalize(cross(up, zAxis));\n            const yAxis = cross(zAxis, xAxis);\n            \n            return new Float32Array([\n                xAxis[0], yAxis[0], zAxis[0], 0,\n                xAxis[1], yAxis[1], zAxis[1], 0,\n                xAxis[2], yAxis[2], zAxis[2], 0,\n                -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1\n            ]);\n        }\n\n        function normalize(v) {\n            const len = Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);\n            return len > 0 ? [v[0]/len, v[1]/len, v[2]/len] : [0, 0, 1];\n        }\n\n        function cross(a, b) {\n            return [\n                a[1]*b[2] - a[2]*b[1],\n                a[2]*b[0] - a[0]*b[2],\n                a[0]*b[1] - a[1]*b[0]\n            ];\n        }\n\n        function dot(a, b) {\n            return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];\n        }\n\n        // ============================================\n        // Geometry Builders\n        // ============================================\n\n        let positions = [];\n        let colors = [];\n\n        function addQuad(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, r, g, b, a) {\n            if (a === undefined) a = 1;\n            // Triangle 1\n            positions.push(x1, y1, z1, x2, y2, z2, x3, y3, z3);\n            // Triangle 2\n            positions.push(x1, y1, z1, x3, y3, z3, x4, y4, z4);\n            // Colors for 6 vertices\n            for (let i = 0; i < 6; i++) {\n                colors.push(r, g, b, a);\n            }\n        }\n\n        function addBox(x, y, z, w, h, d, faceColors) {\n            const hw = w/2, hd = d/2;\n            const x1 = x - hw, x2 = x + hw;\n            const y1 = y, y2 = y + h;\n            const z1 = z - hd, z2 = z + hd;\n\n            // Front face (facing +Z)\n            addQuad(x1,y1,z2, x2,y1,z2, x2,y2,z2, x1,y2,z2, ...faceColors.front);\n            // Back face (facing -Z)\n            addQuad(x2,y1,z1, x1,y1,z1, x1,y2,z1, x2,y2,z1, ...faceColors.back);\n            // Top face\n            addQuad(x1,y2,z2, x2,y2,z2, x2,y2,z1, x1,y2,z1, ...faceColors.top);\n            // Bottom face\n            addQuad(x1,y1,z1, x2,y1,z1, x2,y1,z2, x1,y1,z2, ...faceColors.bottom);\n            // Right face\n            addQuad(x2,y1,z2, x2,y1,z1, x2,y2,z1, x2,y2,z2, ...faceColors.right);\n            // Left face\n            addQuad(x1,y1,z1, x1,y1,z2, x1,y2,z2, x1,y2,z1, ...faceColors.left);\n        }\n\n        function addBoxSimple(x, y, z, w, h, d, r, g, b) {\n            const light = [Math.min(r * 1.2, 1), Math.min(g * 1.2, 1), Math.min(b * 1.2, 1)];\n            const dark = [r * 0.6, g * 0.6, b * 0.6];\n            const mid = [r * 0.85, g * 0.85, b * 0.85];\n            addBox(x, y, z, w, h, d, {\n                front: [r, g, b],\n                back: dark,\n                top: light,\n                bottom: dark,\n                right: mid,\n                left: [r * 0.75, g * 0.75, b * 0.75]\n            });\n        }\n\n        // ============================================\n        // Game Object Renderers\n        // ============================================\n\n        function renderTrack(offsetZ) {\n            const segmentLength = 15;\n            const trackWidth = 10;\n            \n            for (let i = -1; i < 12; i++) {\n                const z = -i * segmentLength + (offsetZ % segmentLength);\n                const isEven = (Math.floor((offsetZ + i * segmentLength) / segmentLength)) % 2 === 0;\n                \n                // Main track\n                const trackColor = isEven ? [0.12, 0.1, 0.15] : [0.15, 0.12, 0.18];\n                addQuad(\n                    -trackWidth/2, 0, z,\n                    trackWidth/2, 0, z,\n                    trackWidth/2, 0, z - segmentLength,\n                    -trackWidth/2, 0, z - segmentLength,\n                    ...trackColor\n                );\n                \n                // Left wall\n                addBoxSimple(-trackWidth/2 - 0.5, 0, z - segmentLength/2, 1, 0.8, segmentLength, 0.2, 0.15, 0.25);\n                \n                // Right wall\n                addBoxSimple(trackWidth/2 + 0.5, 0, z - segmentLength/2, 1, 0.8, segmentLength, 0.2, 0.15, 0.25);\n                \n                // Lane dividers\n                if (isEven) {\n                    for (let lane = 0; lane < 2; lane++) {\n                        const lx = LANES[lane] + LANE_WIDTH/2;\n                        addQuad(\n                            lx - 0.08, 0.02, z,\n                            lx + 0.08, 0.02, z,\n                            lx + 0.08, 0.02, z - 5,\n                            lx - 0.08, 0.02, z - 5,\n                            0.35, 0.3, 0.45\n                        );\n                    }\n                }\n            }\n            \n            // Background buildings/windows\n            for (let side = -1; side <= 1; side += 2) {\n                for (let i = 0; i < 10; i++) {\n                    const bx = side * (14 + Math.sin(i * 1.7) * 5);\n                    const bz = -i * 18 + (offsetZ % 18);\n                    const bh = 6 + Math.sin(i * 2.3) * 4;\n                    \n                    // Building\n                    addBoxSimple(bx, 0, bz, 5, bh, 7, 0.06, 0.08, 0.12);\n                    \n                    // Window glow\n                    const windowSide = side > 0 ? bx - 2.5 : bx + 2.5;\n                    for (let wy = 1; wy < bh - 1; wy += 2) {\n                        for (let wz = -2; wz <= 2; wz += 2) {\n                            addQuad(\n                                windowSide, wy, bz + wz - 0.4,\n                                windowSide, wy, bz + wz + 0.4,\n                                windowSide, wy + 0.8, bz + wz + 0.4,\n                                windowSide, wy + 0.8, bz + wz - 0.4,\n                                0.15, 0.25, 0.35\n                            );\n                        }\n                    }\n                }\n            }\n            \n            // Ground/void below track\n            addQuad(\n                -50, -0.5, 20,\n                50, -0.5, 20,\n                50, -0.5, -200,\n                -50, -0.5, -200,\n                0.02, 0.02, 0.05\n            );\n        }\n\n        function renderPlayer(x, y, z, frame) {\n            // Chrome ball body\n            const bodyY = y + 0.7;\n            \n            // Red segment (top)\n            addBoxSimple(x, bodyY + 0.35, z, 0.9, 0.45, 0.9, 0.92, 0.26, 0.21);\n            \n            // Yellow segment (right)\n            addBoxSimple(x + 0.25, bodyY - 0.1, z, 0.45, 0.55, 0.9, 0.98, 0.74, 0.02);\n            \n            // Green segment (left)\n            addBoxSimple(x - 0.25, bodyY - 0.1, z, 0.45, 0.55, 0.9, 0.2, 0.66, 0.33);\n            \n            // Blue center (eye)\n            addBoxSimple(x, bodyY + 0.15, z + 0.4, 0.45, 0.45, 0.25, 0.26, 0.52, 0.96);\n            \n            // White eye highlight\n            addBoxSimple(x, bodyY + 0.2, z + 0.5, 0.28, 0.28, 0.1, 1, 1, 1);\n            \n            // Animated legs\n            const legSwing = Math.sin(frame * 0.5) * 0.5;\n            \n            // Left leg\n            addBoxSimple(x - 0.22, y, z + legSwing, 0.2, 0.55, 0.2, 0.15, 0.5, 0.25);\n            \n            // Right leg\n            addBoxSimple(x + 0.22, y, z - legSwing, 0.2, 0.55, 0.2, 0.15, 0.5, 0.25);\n            \n            // Arms\n            const armSwing = Math.sin(frame * 0.5) * 0.35;\n            \n            // Left arm\n            addBoxSimple(x - 0.6, bodyY + 0.05, z - armSwing, 0.18, 0.45, 0.18, 0.2, 0.66, 0.33);\n            \n            // Right arm\n            addBoxSimple(x + 0.6, bodyY + 0.05, z + armSwing, 0.18, 0.45, 0.18, 0.98, 0.74, 0.02);\n        }\n\n        function render404Block(x, y, z) {\n            // Glow base\n            addBoxSimple(x, y - 0.05, z, 2.4, 0.1, 1.8, 0.5, 0.1, 0.1);\n            \n            // Main red block\n            addBoxSimple(x, y, z, 2.2, 1.8, 1.5, 0.7, 0.12, 0.12);\n            \n            // Darker top edge\n            addBoxSimple(x, y + 1.7, z, 2.3, 0.15, 1.6, 0.5, 0.08, 0.08);\n            \n            // \"404\" text on front - simplified blocky text\n            const textZ = z + 0.76;\n            const textColor = [0.25, 0.04, 0.04];\n            \n            // First 4\n            addBoxSimple(x - 0.6, y + 1.0, textZ, 0.12, 0.6, 0.05, ...textColor);\n            addBoxSimple(x - 0.6, y + 1.1, textZ, 0.35, 0.12, 0.05, ...textColor);\n            addBoxSimple(x - 0.4, y + 0.6, textZ, 0.12, 0.7, 0.05, ...textColor);\n            \n            // 0\n            addBoxSimple(x, y + 0.9, textZ, 0.3, 0.8, 0.05, ...textColor);\n            addBoxSimple(x, y + 0.9, textZ, 0.15, 0.5, 0.06, 0.7, 0.12, 0.12); // hollow center\n            \n            // Second 4\n            addBoxSimple(x + 0.6, y + 1.0, textZ, 0.12, 0.6, 0.05, ...textColor);\n            addBoxSimple(x + 0.6, y + 1.1, textZ, 0.35, 0.12, 0.05, ...textColor);\n            addBoxSimple(x + 0.8, y + 0.6, textZ, 0.12, 0.7, 0.05, ...textColor);\n        }\n\n        function renderPopup(x, y, z) {\n            // Shadow\n            addBoxSimple(x + 0.15, y - 0.05, z - 0.15, 2.3, 0.05, 0.4, 0.02, 0.02, 0.05);\n            \n            // Window frame (blue)\n            addBoxSimple(x, y + 1.3, z, 2.4, 2.6, 0.35, 0.0, 0.45, 0.82);\n            \n            // Title bar (lighter blue)\n            addBoxSimple(x, y + 2.4, z + 0.12, 2.2, 0.35, 0.2, 0.4, 0.65, 0.95);\n            \n            // Traffic light buttons\n            addBoxSimple(x - 0.8, y + 2.42, z + 0.2, 0.15, 0.15, 0.08, 0.9, 0.3, 0.3);\n            addBoxSimple(x - 0.55, y + 2.42, z + 0.2, 0.15, 0.15, 0.08, 0.95, 0.8, 0.2);\n            addBoxSimple(x - 0.3, y + 2.42, z + 0.2, 0.15, 0.15, 0.08, 0.3, 0.85, 0.4);\n            \n            // Close button (red X)\n            addBoxSimple(x + 0.95, y + 2.42, z + 0.2, 0.2, 0.18, 0.08, 0.9, 0.2, 0.2);\n            \n            // Content area (white/gray)\n            addBoxSimple(x, y + 1.1, z + 0.12, 2.1, 1.9, 0.18, 0.92, 0.92, 0.92);\n            \n            // \"CLICK HERE\" style bars\n            addBoxSimple(x, y + 1.7, z + 0.22, 1.4, 0.2, 0.06, 0.9, 0.15, 0.15);\n            addBoxSimple(x, y + 1.35, z + 0.22, 0.9, 0.18, 0.06, 0.15, 0.8, 0.2);\n            addBoxSimple(x - 0.3, y + 1.0, z + 0.22, 0.6, 0.15, 0.06, 0.95, 0.85, 0.1);\n            addBoxSimple(x + 0.4, y + 0.65, z + 0.22, 0.7, 0.15, 0.06, 0.2, 0.5, 0.9);\n        }\n\n        function renderFolder(x, y, z) {\n            // Shadow\n            addQuad(\n                x - 1, 0.01, z + 0.6,\n                x + 1, 0.01, z + 0.6,\n                x + 1.1, 0.01, z - 0.6,\n                x - 0.9, 0.01, z - 0.6,\n                0.03, 0.03, 0.05\n            );\n            \n            // Folder body\n            addBoxSimple(x, y + 0.6, z, 2.0, 1.3, 1.1, 1.0, 0.78, 0.2);\n            \n            // Folder tab\n            addBoxSimple(x - 0.55, y + 1.55, z, 0.8, 0.35, 1.1, 1.0, 0.85, 0.35);\n            \n            // Paper sticking out\n            addBoxSimple(x + 0.25, y + 1.15, z + 0.5, 1.1, 0.7, 0.06, 0.97, 0.97, 0.93);\n            addBoxSimple(x + 0.15, y + 1.0, z + 0.45, 1.0, 0.5, 0.06, 0.93, 0.93, 0.88);\n        }\n\n        function renderCoin(x, y, z, phase) {\n            const bobY = y + Math.sin(phase) * 0.2;\n            const rotation = phase * 2.5;\n            \n            // Spinning coin effect\n            const scale = Math.abs(Math.cos(rotation));\n            const w = scale * 0.55 + 0.12;\n            \n            // Coin body\n            addBoxSimple(x, bobY, z, w, 0.55, 0.18, 1.0, 0.84, 0.0);\n            \n            // Inner ring (darker)\n            if (scale > 0.5) {\n                addBoxSimple(x, bobY, z + 0.08, w * 0.6, 0.35, 0.05, 0.85, 0.65, 0.0);\n            }\n            \n            // Sparkle effect\n            if (Math.sin(phase * 4) > 0.7) {\n                addBoxSimple(x, bobY + 0.35, z + 0.12, 0.12, 0.12, 0.05, 1, 1, 0.7);\n            }\n        }\n\n        // ============================================\n        // Game Logic\n        // ============================================\n\n        function initGame() {\n            score = 0;\n            coins = 0;\n            lives = 4;\n            gameSpeed = 0.25;\n            distance = 0;\n            \n            player.x = 0;\n            player.y = 0;\n            player.z = 0;\n            player.targetLane = 1;\n            player.velocityY = 0;\n            player.isJumping = false;\n            player.invincible = 0;\n            \n            obstacles = [];\n            collectibles = [];\n            \n            // Spawn initial obstacles\n            for (let i = 3; i < 12; i++) {\n                if (Math.random() > 0.4) {\n                    spawnObstacle(-i * 15);\n                }\n                if (Math.random() > 0.35) {\n                    spawnCoins(-i * 15);\n                }\n            }\n            \n            updateUI();\n        }\n\n        function spawnObstacle(z) {\n            const lane = Math.floor(Math.random() * 3);\n            const types = ['404', 'popup', 'folder'];\n            const type = types[Math.floor(Math.random() * types.length)];\n            \n            obstacles.push({\n                x: LANES[lane],\n                z: z,\n                type: type,\n                lane: lane\n            });\n        }\n\n        function spawnCoins(z) {\n            const lane = Math.floor(Math.random() * 3);\n            for (let i = 0; i < 3; i++) {\n                collectibles.push({\n                    x: LANES[lane],\n                    y: 1.3,\n                    z: z - i * 2.2,\n                    collected: false,\n                    phase: Math.random() * Math.PI * 2\n                });\n            }\n        }\n\n        function update(dt) {\n            if (!gameRunning) return;\n            \n            animFrame += dt * 0.08;\n            \n            // Score\n            score += Math.floor(gameSpeed * 6 * dt);\n            distance += gameSpeed * dt;\n            \n            // Speed up gradually\n            gameSpeed = Math.min(0.7, 0.25 + distance / 1200);\n            \n            // Move player to target lane smoothly\n            const targetX = LANES[player.targetLane];\n            player.x += (targetX - player.x) * 0.15 * dt;\n            \n            // Jump physics\n            if (player.isJumping) {\n                player.velocityY -= 0.028 * dt;\n                player.y += player.velocityY * dt;\n                \n                if (player.y <= 0) {\n                    player.y = 0;\n                    player.isJumping = false;\n                    player.velocityY = 0;\n                }\n            }\n            \n            // Invincibility timer\n            if (player.invincible > 0) {\n                player.invincible -= dt;\n            }\n            \n            // Move obstacles toward player\n            for (let obs of obstacles) {\n                obs.z += gameSpeed * dt;\n            }\n            \n            // Move coins\n            for (let coin of collectibles) {\n                coin.z += gameSpeed * dt;\n                coin.phase += dt * 0.12;\n            }\n            \n            // Collision with obstacles\n            for (let obs of obstacles) {\n                if (obs.z > -2.5 && obs.z < 2) {\n                    if (Math.abs(obs.x - player.x) < 1.3) {\n                        const obsHeight = obs.type === 'popup' ? 2.6 : 1.8;\n                        if (player.y < obsHeight - 0.6) {\n                            if (player.invincible <= 0) {\n                                lives--;\n                                player.invincible = 100;\n                                if (lives <= 0) {\n                                    gameOver();\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n            \n            // Coin collection\n            for (let coin of collectibles) {\n                if (coin.collected) continue;\n                if (coin.z > -1.8 && coin.z < 1.8) {\n                    if (Math.abs(coin.x - player.x) < 1.3 && Math.abs(coin.y - player.y - 1) < 1.3) {\n                        coin.collected = true;\n                        coins++;\n                        score += 100;\n                    }\n                }\n            }\n            \n            // Spawn new content\n            let minZ = 0;\n            for (let obs of obstacles) {\n                if (obs.z < minZ) minZ = obs.z;\n            }\n            if (minZ > -150) {\n                if (Math.random() > 0.35) spawnObstacle(minZ - 18 - Math.random() * 12);\n                if (Math.random() > 0.3) spawnCoins(minZ - 18 - Math.random() * 12);\n            }\n            \n            // Remove passed objects\n            obstacles = obstacles.filter(o => o.z < 25);\n            collectibles = collectibles.filter(c => c.z < 25);\n            \n            updateUI();\n        }\n\n        function gameOver() {\n            gameRunning = false;\n            document.getElementById('finalScore').textContent = `SCORE: ${score.toString().padStart(6, '0')}`;\n            document.getElementById('gameOverScreen').classList.remove('hidden');\n        }\n\n        function updateUI() {\n            document.getElementById('score').textContent = `SCORE: ${score.toString().padStart(6, '0')}`;\n            document.getElementById('coins').textContent = `COINS: ${coins}`;\n            \n            const lifeElements = document.querySelectorAll('.life');\n            lifeElements.forEach((el, i) => {\n                el.classList.toggle('lost', i >= lives);\n            });\n        }\n\n        // ============================================\n        // Render Loop\n        // ============================================\n\n        function render() {\n            positions = [];\n            colors = [];\n            \n            // Build scene geometry\n            renderTrack(distance);\n            \n            // Render obstacles\n            for (let obs of obstacles) {\n                switch (obs.type) {\n                    case '404': render404Block(obs.x, 0, obs.z); break;\n                    case 'popup': renderPopup(obs.x, 0, obs.z); break;\n                    case 'folder': renderFolder(obs.x, 0, obs.z); break;\n                }\n            }\n            \n            // Render coins\n            for (let coin of collectibles) {\n                if (!coin.collected) {\n                    renderCoin(coin.x, coin.y, coin.z, coin.phase);\n                }\n            }\n            \n            // Render player (blink when invincible)\n            if (player.invincible <= 0 || Math.floor(player.invincible / 6) % 2 === 0) {\n                renderPlayer(player.x, player.y, player.z, animFrame);\n            }\n            \n            // WebGL rendering\n            gl.viewport(0, 0, canvas.width, canvas.height);\n            gl.clearColor(0.04, 0.04, 0.1, 1);\n            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n            gl.enable(gl.DEPTH_TEST);\n            gl.enable(gl.CULL_FACE);\n            gl.cullFace(gl.BACK);\n            \n            gl.useProgram(program);\n            \n            // Camera - behind and above player, looking down the track\n            const camHeight = 6 + player.y * 0.4;\n            const proj = perspective(Math.PI / 3.2, canvas.width / canvas.height, 0.1, 250);\n            const view = lookAt(\n                [0, camHeight, 10],    // Camera position (behind player)\n                [0, 2, -20],           // Look at point (ahead on track)\n                [0, 1, 0]              // Up vector\n            );\n            \n            gl.uniformMatrix4fv(uProjection, false, proj);\n            gl.uniformMatrix4fv(uView, false, view);\n            gl.uniform3f(uFogColor, 0.04, 0.04, 0.1);\n            \n            // Upload and draw geometry\n            if (positions.length > 0) {\n                gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);\n                gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW);\n                gl.enableVertexAttribArray(aPosition);\n                gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);\n                \n                gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);\n                gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.DYNAMIC_DRAW);\n                gl.enableVertexAttribArray(aColor);\n                gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, 0, 0);\n                \n                gl.drawArrays(gl.TRIANGLES, 0, positions.length / 3);\n            }\n        }\n\n        // ============================================\n        // Game Loop\n        // ============================================\n\n        function gameLoop(time) {\n            const dt = Math.min(time - lastTime, 50) / 16.67;\n            lastTime = time;\n            \n            update(dt);\n            render();\n            \n            requestAnimationFrame(gameLoop);\n        }\n\n        // ============================================\n        // Input\n        // ============================================\n\n        document.addEventListener('keydown', (e) => {\n            if (!gameRunning) return;\n            \n            switch (e.code) {\n                case 'ArrowLeft':\n                case 'KeyA':\n                    if (player.targetLane > 0) player.targetLane--;\n                    break;\n                case 'ArrowRight':\n                case 'KeyD':\n                    if (player.targetLane < 2) player.targetLane++;\n                    break;\n                case 'Space':\n                case 'ArrowUp':\n                case 'KeyW':\n                    if (!player.isJumping) {\n                        player.isJumping = true;\n                        player.velocityY = 0.5;\n                    }\n                    break;\n            }\n            \n            e.preventDefault();\n        });\n\n        // Touch controls\n        let touchStartX = 0, touchStartY = 0;\n        \n        canvas.addEventListener('touchstart', (e) => {\n            touchStartX = e.touches[0].clientX;\n            touchStartY = e.touches[0].clientY;\n            e.preventDefault();\n        }, { passive: false });\n        \n        canvas.addEventListener('touchend', (e) => {\n            if (!gameRunning) return;\n            const dx = e.changedTouches[0].clientX - touchStartX;\n            const dy = e.changedTouches[0].clientY - touchStartY;\n            \n            if (Math.abs(dx) > Math.abs(dy)) {\n                if (dx > 40 && player.targetLane < 2) player.targetLane++;\n                else if (dx < -40 && player.targetLane > 0) player.targetLane--;\n            } else if (dy < -40 && !player.isJumping) {\n                player.isJumping = true;\n                player.velocityY = 0.5;\n            }\n            e.preventDefault();\n        }, { passive: false });\n\n        // ============================================\n        // Start/Restart\n        // ============================================\n\n        function startGame() {\n            document.getElementById('startScreen').classList.add('hidden');\n            initGame();\n            gameRunning = true;\n        }\n\n        function restartGame() {\n            document.getElementById('gameOverScreen').classList.add('hidden');\n            initGame();\n            gameRunning = true;\n        }\n\n        // Initialize and start render loop\n        initGame();\n        lastTime = performance.now();\n        requestAnimationFrame(gameLoop);\n    </script>\n\n\n</body></html>"}