domingo, 8 de noviembre de 2026

Un simple juego de coches en html por IA;

https://sites.google.com/view/coche-3d-minimalista/full-page-embed 



<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Coche 3D Minimalista - Controles WASD</title>
    <!-- Cargamos Three.js desde CDN -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <style>
        /* Estilos básicos para que el canvas ocupe toda la pantalla sin barras de desplazamiento */
        body {
            margin: 0;
            overflow: hidden;
            background-color: #1a1a1a;
            font-family: sans-serif;
        }
        /* Cartel de instrucciones */
        #instrucciones {
            position: absolute;
            top: 20px;
            left: 20px;
            color: white;
            background: rgba(0, 0, 0, 0.6);
            padding: 12px 20px;
            border-radius: 8px;
            pointer-events: none;
        }
    </style>
</head>
<body>

    <!-- Cuadro informativo simple -->
    <div id="instrucciones">
        <h3 style="margin: 0 0 5px 0;">Controles:</h3>
        <p style="margin: 0;"><b>W / S:</b> Avanzar / Retroceder</p>
        <p style="margin: 0;"><b>A / D:</b> Girar Izquierda / Derecha</p>
    </div>

    <script>
        // ==========================================
        // 1. CONFIGURACIÓN INICIAL (Escena, Cámara, Renderizador)
        // ==========================================
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x87ceeb); // Fondo color cielo

        // Cámara de perspectiva
        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
       
        // Renderizador WebGL
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        // ==========================================
        // 2. ILUMINACIÓN
        // ==========================================
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
        scene.add(ambientLight);

        const sunLight = new THREE.DirectionalLight(0xffffff, 0.8);
        sunLight.position.set(20, 40, 20);
        scene.add(sunLight);

        // ==========================================
        // 3. SUELO
        // ==========================================
        const planeGeo = new THREE.PlaneGeometry(100, 100);
        const planeMat = new THREE.MeshLambertMaterial({ color: 0x3e8e41 }); // Verde hierba
        const ground = new THREE.Mesh(planeGeo, planeMat);
        ground.rotation.x = -Math.PI / 2; // Acostar el plano horizontalmente
        scene.add(ground);

        // Grid (Rejilla visual para dar sensación de movimiento)
        const grid = new THREE.GridHelper(100, 40, 0x000000, 0x555555);
        grid.position.y = 0.01;
        scene.add(grid);

        // ==========================================
        // 4. CREACIÓN DEL COCHE (Grupo de Objetos)
        // ==========================================
        const car = new THREE.Group();

        // Carrocería (Caja azul)
        const bodyGeo = new THREE.BoxGeometry(2, 0.8, 4);
        const bodyMat = new THREE.MeshLambertMaterial({ color: 0x1e90ff });
        const carBody = new THREE.Mesh(bodyGeo, bodyMat);
        carBody.position.y = 0.6; // Elevar ligeramente del suelo
        car.add(carBody);

        // Techo/Cabina (Caja más pequeña sobre la carrocería)
        const cabinGeo = new THREE.BoxGeometry(1.6, 0.6, 2);
        const cabinMat = new THREE.MeshLambertMaterial({ color: 0x333333 });
        const cabin = new THREE.Mesh(cabinGeo, cabinMat);
        cabin.position.set(0, 1.3, -0.3);
        car.add(cabin);

        // Función auxiliar para crear ruedas simples
        function createWheel(x, z) {
            const wheelGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.3, 16);
            const wheelMat = new THREE.MeshLambertMaterial({ color: 0x111111 });
            const wheel = new THREE.Mesh(wheelGeo, wheelMat);
            wheel.rotation.z = Math.PI / 2; // Orientar rueda hacia los lados
            wheel.position.set(x, 0.4, z);
            return wheel;
        }

        // Añadir 4 ruedas al coche
        car.add(createWheel(-1.1, 1.2));  // Delantera Izquierda
        car.add(createWheel(1.1, 1.2));   // Delantera Derecha
        car.add(createWheel(-1.1, -1.2)); // Trasera Izquierda
        car.add(createWheel(1.1, -1.2));  // Trasera Derecha

        // Agregar el coche a la escena
        scene.add(car);

        // ==========================================
        // 5. CONTROL DE TECLADO (WASD)
        // ==========================================
        const keys = { w: false, a: false, s: false, d: false };

        window.addEventListener('keydown', (e) => {
            const key = e.key.toLowerCase();
            if (keys.hasOwnProperty(key)) keys[key] = true;
        });

        window.addEventListener('keyup', (e) => {
            const key = e.key.toLowerCase();
            if (keys.hasOwnProperty(key)) keys[key] = false;
        });

        // ==========================================
        // 6. LÓGICA DE MOVIMIENTO Y ANIMACIÓN
        // ==========================================
        let speed = 0;
        const maxSpeed = 0.25;
        const acceleration = 0.01;
        const friction = 0.96; // Resistencia para frenar progresivamente
        const turnSpeed = 0.04;

        function animate() {
            requestAnimationFrame(animate);

            // Acelerar / Retroceder
            if (keys.w) speed += acceleration;
            if (keys.s) speed -= acceleration;

            // Limitar velocidad máxima
            speed = Math.max(-maxSpeed / 2, Math.min(maxSpeed, speed));

            // Aplicar fricción cuando no se presiona ni W ni S
            if (!keys.w && !keys.s) {
                speed *= friction;
            }

            // Girar solo si el coche está en movimiento
            if (Math.abs(speed) > 0.005) {
                const direction = speed > 0 ? 1 : -1; // Invertir giro al ir marcha atrás
                if (keys.a) car.rotation.y += turnSpeed * direction;
                if (keys.d) car.rotation.y -= turnSpeed * direction;
            }

            // Mover el coche según su orientación actual
            car.translateZ(speed);

            // Hacer que la cámara siga al coche suavemente desde atrás
            const relativeCameraOffset = new THREE.Vector3(0, 5, -10);
            const cameraOffset = relativeCameraOffset.applyMatrix4(car.matrixWorld);
            camera.position.x = cameraOffset.x;
            camera.position.y = cameraOffset.y;
            camera.position.z = cameraOffset.z;
            camera.lookAt(car.position);

            // Renderizar la escena
            renderer.render(scene, camera);
        }

        // Redimensionar la pantalla dinámicamente
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        // Iniciar bucle de animación
        window.onload = function() {
            animate();
        };
    </script>
</body>
</html>


Simulador de coche; IA en html;

https://sites.google.com/view/jeep-3d-off-road-simulator/full-page-embed 


<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Jeep 3D Off-Road Simulator</title>
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- FontAwesome icons -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <!-- Google Fonts -->
    <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <!-- Three.js & OrbitControls -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>

    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            font-family: 'Inter', sans-serif;
            user-select: none;
            -webkit-user-select: none;
            background-color: #0f172a;
        }
        .font-orbitron {
            font-family: 'Orbitron', sans-serif;
        }
        /* Custom glassmorphism styling */
        .glass-panel {
            background: rgba(15, 23, 42, 0.75);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            border: 1px solid rgba(255, 255, 255, 0.15);
        }
        /* Custom slider styles */
        input[type=range] {
            -webkit-appearance: none;
            background: transparent;
        }
        input[type=range]:focus {
            outline: none;
        }
        input[type=range]::-webkit-slider-runnable-track {
            width: 100%;
            height: 10px;
            cursor: pointer;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 5px;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }
        input[type=range]::-webkit-slider-thumb {
            height: 22px;
            width: 22px;
            border-radius: 50%;
            background: #f59e0b;
            cursor: pointer;
            -webkit-appearance: none;
            margin-top: -6px;
            box-shadow: 0 0 10px rgba(245, 158, 11, 0.8);
            transition: transform 0.1s ease;
        }
        input[type=range]::-webkit-slider-thumb:hover {
            transform: scale(1.15);
        }
        /* Controls key visualization */
        .key-btn {
            background: rgba(255, 255, 255, 0.1);
            border: 1px solid rgba(255, 255, 255, 0.2);
            box-shadow: inset 0 -2px 0 rgba(0,0,0,0.5);
            transition: all 0.1s ease;
        }
        .key-btn.active {
            background: #f59e0b;
            color: #000;
            transform: translateY(2px);
            box-shadow: none;
        }
    </style>
</head>
<body class="w-screen h-screen overflow-hidden text-white">

    <!-- Canvas Container -->
    <div id="canvas-container" class="w-full h-full absolute inset-0 z-0"></div>

    <!-- UI Overlay HUD -->
    <div id="hud-layer" class="relative z-10 w-full h-full pointer-events-none flex flex-col justify-between p-4 sm:p-6">
       
        <!-- Top Bar Header & Controls info -->
        <div class="flex justify-between items-start w-full">
            <!-- Brand / Title -->
            <div class="glass-panel p-4 rounded-2xl pointer-events-auto shadow-2xl flex items-center gap-3">
                <div class="w-10 h-10 rounded-xl bg-amber-500 text-slate-950 flex items-center justify-center font-extrabold text-xl shadow-lg">
                    <i class="fa-solid fa-truck-monster"></i>
                </div>
                <div>
                    <h1 class="font-orbitron font-extrabold text-base sm:text-lg tracking-wider text-amber-400">JEEP OFF-ROAD 3D</h1>
                    <p class="text-xs text-slate-300">Simulador de terreno con baches</p>
                </div>
            </div>

            <!-- Action buttons (Respawn & Camera Mode) -->
            <div class="flex gap-3 pointer-events-auto">
                <button id="btn-respawn" title="Reiniciar Posición (R)" class="glass-panel hover:bg-amber-500/20 active:scale-95 text-amber-400 hover:text-amber-300 font-semibold px-4 py-3 rounded-xl flex items-center gap-2 transition-all shadow-lg border border-amber-500/30">
                    <i class="fa-solid fa-rotate-left text-lg"></i>
                    <span class="hidden sm:inline text-xs font-orbitron uppercase tracking-wider">Reiniciar (R)</span>
                </button>
                <button id="btn-camera" title="Cambiar vista de cámara (C)" class="glass-panel hover:bg-slate-700/50 active:scale-95 text-slate-200 font-semibold p-3 sm:px-4 sm:py-3 rounded-xl flex items-center gap-2 transition-all shadow-lg">
                    <i class="fa-solid fa-video text-lg"></i>
                    <span class="hidden sm:inline text-xs font-orbitron uppercase tracking-wider">Cámara (C)</span>
                </button>
            </div>
        </div>

        <!-- Center Notification Toast / Flip Warning -->
        <div id="warning-toast" class="self-center glass-panel border-amber-500/50 bg-amber-500/10 text-amber-300 px-6 py-3 rounded-full flex items-center gap-3 opacity-0 transition-opacity duration-300 pointer-events-none">
            <i class="fa-solid fa-triangle-exclamation text-xl animate-bounce text-amber-400"></i>
            <span class="font-orbitron text-xs sm:text-sm tracking-wide">¡Jeep Inclinado/Volcado! Presiona R para colocarlo derecho</span>
        </div>

        <!-- Bottom Controls & Speedometer Section -->
        <div class="flex flex-col sm:flex-row justify-between items-end gap-4 w-full">
           
            <!-- Calibration Panel (Slider Potencia/Velocidad) -->
            <div class="glass-panel p-4 rounded-2xl pointer-events-auto w-full sm:w-80 shadow-2xl space-y-3">
                <div class="flex justify-between items-center border-b border-white/10 pb-2">
                    <span class="text-xs font-orbitron text-amber-400 font-bold uppercase tracking-wider flex items-center gap-2">
                        <i class="fa-solid fa-gauge-high"></i> Calibrar Motor
                    </span>
                    <span id="speed-limit-val" class="font-orbitron text-sm font-extrabold text-white">80 km/h</span>
                </div>
               
                <div>
                    <label class="text-[11px] text-slate-300 flex justify-between mb-1">
                        <span>Potencia Máxima (V-Max)</span>
                        <span id="power-percentage" class="text-amber-400 font-semibold">100%</span>
                    </label>
                    <input type="range" id="slider-speed" min="30" max="150" value="80" class="w-full">
                </div>

                <div class="grid grid-cols-2 gap-2 pt-1 text-[10px] text-slate-400">
                    <div class="flex items-center gap-1.5 bg-slate-800/50 p-1.5 rounded-lg border border-white/5">
                        <i class="fa-solid fa-mountain text-amber-400"></i> Terreno: <span class="text-white font-medium">Baches 3D</span>
                    </div>
                    <div class="flex items-center gap-1.5 bg-slate-800/50 p-1.5 rounded-lg border border-white/5">
                        <i class="fa-solid fa-car-side text-amber-400"></i> Tracción: <span class="text-white font-medium">4x4 OffRoad</span>
                    </div>
                </div>
            </div>

            <!-- Digital Speedometer HUD -->
            <div class="glass-panel p-5 rounded-3xl pointer-events-auto flex items-center gap-5 shadow-2xl self-center sm:self-end">
                <div class="relative flex items-center justify-center">
                    <!-- Circular speed meter SVG -->
                    <svg class="w-24 h-24 transform -rotate-90">
                        <circle cx="48" cy="48" r="40" stroke="rgba(255,255,255,0.1)" stroke-width="8" fill="transparent"/>
                        <circle id="speed-circle" cx="48" cy="48" r="40" stroke="#f59e0b" stroke-width="8" fill="transparent"
                                stroke-dasharray="251.2" stroke-dashoffset="251.2" stroke-linecap="round" class="transition-all duration-100"/>
                    </svg>
                    <div class="absolute flex flex-col items-center justify-center text-center">
                        <span id="speed-display" class="font-orbitron font-black text-2xl sm:text-3xl text-white tracking-tighter">0</span>
                        <span class="text-[9px] font-orbitron text-amber-400 font-bold uppercase tracking-widest">KM/H</span>
                    </div>
                </div>

                <div class="flex flex-col gap-2 border-l border-white/10 pl-4">
                    <div class="flex items-center gap-2">
                        <span class="text-[10px] text-slate-400 font-orbitron">MARCHA</span>
                        <span id="gear-display" class="font-orbitron text-sm font-bold bg-amber-500 text-slate-950 px-2 py-0.5 rounded">D</span>
                    </div>
                    <div class="flex items-center gap-2">
                        <span class="text-[10px] text-slate-400 font-orbitron">SUSPENSIÓN</span>
                        <span id="suspension-status" class="text-xs font-semibold text-emerald-400 flex items-center gap-1">
                            <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span> ACTIVA
                        </span>
                    </div>
                </div>
            </div>

            <!-- Controls Keyboard Legend / Touch Controls -->
            <div class="glass-panel p-4 rounded-2xl pointer-events-auto hidden md:flex flex-col items-center gap-2 shadow-2xl">
                <span class="text-[10px] font-orbitron text-slate-400 uppercase tracking-wider font-bold mb-1">Controles Teclado</span>
                <div class="flex gap-1.5">
                    <div id="key-w" class="key-btn w-9 h-9 rounded-lg flex items-center justify-center font-orbitron font-bold text-sm">W</div>
                </div>
                <div class="flex gap-1.5">
                    <div id="key-a" class="key-btn w-9 h-9 rounded-lg flex items-center justify-center font-orbitron font-bold text-sm">A</div>
                    <div id="key-s" class="key-btn w-9 h-9 rounded-lg flex items-center justify-center font-orbitron font-bold text-sm">S</div>
                    <div id="key-d" class="key-btn w-9 h-9 rounded-lg flex items-center justify-center font-orbitron font-bold text-sm">D</div>
                </div>
            </div>

        </div>

    </div>

    <!-- Mobile Touch Controls Overlay (visible standard touch devices) -->
    <div class="md:hidden fixed bottom-4 right-4 z-20 pointer-events-auto flex flex-col gap-2">
        <div class="flex justify-center">
            <button id="touch-up" class="w-14 h-14 bg-amber-500/80 active:bg-amber-400 text-slate-950 rounded-2xl font-bold text-xl flex items-center justify-center shadow-lg active:scale-95 touch-none">
                <i class="fa-solid fa-chevron-up"></i>
            </button>
        </div>
        <div class="flex gap-2">
            <button id="touch-left" class="w-14 h-14 bg-slate-800/80 active:bg-slate-700 text-white rounded-2xl font-bold text-xl flex items-center justify-center shadow-lg active:scale-95 touch-none">
                <i class="fa-solid fa-chevron-left"></i>
            </button>
            <button id="touch-down" class="w-14 h-14 bg-slate-800/80 active:bg-slate-700 text-white rounded-2xl font-bold text-xl flex items-center justify-center shadow-lg active:scale-95 touch-none">
                <i class="fa-solid fa-chevron-down"></i>
            </button>
            <button id="touch-right" class="w-14 h-14 bg-slate-800/80 active:bg-slate-700 text-white rounded-2xl font-bold text-xl flex items-center justify-center shadow-lg active:scale-95 touch-none">
                <i class="fa-solid fa-chevron-right"></i>
            </button>
        </div>
    </div>

    <!-- MAIN GAME LOGIC SCRIPT -->
    <script>
        // --- PERLIN NOISE IMPLEMENTATION FOR TERRAIN HEIGHT MAP ---
        const SimplexNoise = function() {
            var F3 = 1.0/3.0, G3 = 1.0/6.0;
            var p = new Uint8Array(256);
            for (var i=0; i<256; i++) p[i] = Math.floor(Math.random()*256);
            var perm = new Uint8Array(512), permMod12 = new Uint8Array(512);
            for (var i=0; i<512; i++) {
                perm[i] = p[i & 255];
                permMod12[i] = (perm[i] % 12);
            }
            return {
                noise2D: function(xin, yin) {
                    var n0, n1, n2;
                    var s = (xin+yin)*0.5*(Math.sqrt(3.0)-1.0);
                    var i = Math.floor(xin+s), j = Math.floor(yin+s);
                    var t = (i+j)*(3.0-Math.sqrt(3.0))/6.0;
                    var X0 = i-t, Y0 = j-t;
                    var x0 = xin-X0, y0 = yin-Y0;
                    var i1, j1;
                    if(x0>y0) {i1=1; j1=0;} else {i1=0; j1=1;}
                    var x1 = x0 - i1 + (3.0-Math.sqrt(3.0))/6.0, y1 = y0 - j1 + (3.0-Math.sqrt(3.0))/6.0;
                    var x2 = x0 - 1.0 + 2.0*(3.0-Math.sqrt(3.0))/6.0, y2 = y0 - 1.0 + 2.0*(3.0-Math.sqrt(3.0))/6.0;
                    var ii = i & 255, jj = j & 255;
                    var gi0 = permMod12[ii+perm[jj]], gi1 = permMod12[ii+i1+perm[jj+j1]], gi2 = permMod12[ii+1+perm[jj+1]];
                    var t0 = 0.5 - x0*x0-y0*y0; if(t0<0) n0 = 0.0; else { t0 *= t0; n0 = t0 * t0 * (gi0%2 === 0 ? 1 : -1)*(x0 + y0); }
                    var t1 = 0.5 - x1*x1-y1*y1; if(t1<0) n1 = 0.0; else { t1 *= t1; n1 = t1 * t1 * (gi1%2 === 0 ? 1 : -1)*(x1 + y1); }
                    var t2 = 0.5 - x2*x2-y2*y2; if(t2<0) n2 = 0.0; else { t2 *= t2; n2 = t2 * t2 * (gi2%2 === 0 ? 1 : -1)*(x2 + y2); }
                    return 70.0 * (n0 + n1 + n2);
                }
            };
        };
        const noise = SimplexNoise();

        // Dynamic Heightmap function for bumps and hills
        function getTerrainHeight(x, z) {
            // Multi-octave bumps & hills
            const detailBumps = noise.noise2D(x * 0.08, z * 0.08) * 0.7; // Fast small bumps
            const mediumBumps = noise.noise2D(x * 0.03, z * 0.03) * 2.5; // Rough uneven ground
            const hills = noise.noise2D(x * 0.008, z * 0.008) * 12.0;    // Mountain rolling hills
           
            // Safe flat spawn area around origin (0,0)
            const distFromOrigin = Math.sqrt(x*x + z*z);
            const flatFactor = Math.min(1.0, Math.max(0.0, (distFromOrigin - 12) / 30));

            return (detailBumps + mediumBumps + hills) * flatFactor;
        }

        // --- GLOBAL SCENE SETUP ---
        let scene, camera, renderer, orbitControls;
        let jeepGroup, wheels = [], headlights = [];
        let terrainMesh, terrainGeometry;
        const TERRAIN_SIZE = 500;
        const TERRAIN_SEGMENTS = 250;

        // Vehicle Physics & Dynamics State
        const vehicle = {
            x: 0,
            z: 0,
            y: 0,
            speed: 0,
            maxSpeed: 80, // Calibrated by slider (converted to physics scale)
            acceleration: 35.0,
            friction: 0.965,
            reverseSpeed: -25,
            steeringAngle: 0,
            maxSteer: 0.5,
            steerSpeed: 3.5,
            heading: 0, // Yaw orientation in radians
            pitch: 0,   // Front/back tilt based on terrain slope
            roll: 0,    // Left/right tilt based on terrain slope
            wheelRotation: 0,
            isFlipped: false
        };

        // Inputs State
        const keys = { w: false, a: false, s: false, d: false };

        // Camera mode: 0 = Chase 3rd Person, 1 = Orbit Free Camera
        let cameraMode = 0;

        // --- INIT FUNCTION ---
        function init() {
            const container = document.getElementById('canvas-container');

            // 1. Scene setup
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x87ceeb); // Sky blue
            scene.fog = new THREE.FogExp2(0x87ceeb, 0.007);

            // 2. Camera setup
            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 8, -14);

            // 3. Renderer setup
            renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 1.1;
            container.appendChild(renderer.domElement);

            // 4. Controls (Orbit mode toggleable)
            orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
            orbitControls.enableDamping = true;
            orbitControls.dampingFactor = 0.05;
            orbitControls.enabled = false; // Off by default for 3rd person chase camera

            // 5. Lighting
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.45);
            scene.add(ambientLight);

            const sunLight = new THREE.DirectionalLight(0xfffaed, 1.2);
            sunLight.position.set(100, 150, 80);
            sunLight.castShadow = true;
            sunLight.shadow.mapSize.width = 2048;
            sunLight.shadow.mapSize.height = 2048;
            sunLight.shadow.camera.near = 0.5;
            sunLight.shadow.camera.far = 400;
            const d = 100;
            sunLight.shadow.camera.left = -d;
            sunLight.shadow.camera.right = d;
            sunLight.shadow.camera.top = d;
            sunLight.shadow.camera.bottom = -d;
            scene.add(sunLight);

            // Hemisphere light for natural sky ground shading
            const hemiLight = new THREE.HemisphereLight(0x87ceeb, 0x3d5a2b, 0.4);
            scene.add(hemiLight);

            // 6. Build Environment
            createTerrain();
            createTreesAndRocks();

            // 7. Build Jeep Vehicle Model
            createJeep();

            // 8. Event Listeners & UI hooks
            setupEventListeners();

            // 9. Start Render Loop
            window.addEventListener('resize', onWindowResize);
            animate();
        }

        // --- CREATING TERRAIN WITH BUMPS ---
        function createTerrain() {
            terrainGeometry = new THREE.PlaneGeometry(TERRAIN_SIZE, TERRAIN_SIZE, TERRAIN_SEGMENTS, TERRAIN_SEGMENTS);
            terrainGeometry.rotateX(-Math.PI / 2); // Make horizontal

            const posAttribute = terrainGeometry.attributes.position;
            const colors = [];

            const colorSand = new THREE.Color(0xd2b48c);
            const colorGrass = new THREE.Color(0x4a7c59);
            const colorRock = new THREE.Color(0x6b7280);
            const colorHighGrass = new THREE.Color(0x2d5a27);

            for (let i = 0; i < posAttribute.count; i++) {
                const x = posAttribute.getX(i);
                const z = posAttribute.getZ(i);
                const h = getTerrainHeight(x, z);
               
                posAttribute.setY(i, h);

                // Vertex coloring based on height and slope
                const mixColor = new THREE.Color();
                if (h < 0.5) {
                    mixColor.copy(colorSand);
                } else if (h < 5.0) {
                    mixColor.copy(colorGrass).lerp(colorHighGrass, (h - 0.5) / 4.5);
                } else {
                    mixColor.copy(colorHighGrass).lerp(colorRock, Math.min(1.0, (h - 5.0) / 7.0));
                }
               
                // Add tiny color noise variations
                mixColor.r += (Math.random() - 0.5) * 0.03;
                mixColor.g += (Math.random() - 0.5) * 0.03;
                colors.push(mixColor.r, mixColor.g, mixColor.b);
            }

            terrainGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
            terrainGeometry.computeVertexNormals();

            const terrainMaterial = new THREE.MeshStandardMaterial({
                vertexColors: true,
                roughness: 0.9,
                metalness: 0.1,
                flatShading: true
            });

            terrainMesh = new THREE.Mesh(terrainGeometry, terrainMaterial);
            terrainMesh.receiveShadow = true;
            scene.add(terrainMesh);
        }

        // --- DECORATIONS: TREES & ROCKS ---
        function createTreesAndRocks() {
            const treeGeo = new THREE.ConeGeometry(1.6, 5, 5);
            const trunkGeo = new THREE.CylinderGeometry(0.3, 0.4, 1.5, 5);
            const treeMat = new THREE.MeshStandardMaterial({ color: 0x1e4620, roughness: 0.8 });
            const trunkMat = new THREE.MeshStandardMaterial({ color: 0x4a2e18, roughness: 0.9 });
            const rockMat = new THREE.MeshStandardMaterial({ color: 0x78716c, roughness: 0.9, flatShading: true });

            const treeInstanced = new THREE.Group();

            for (let i = 0; i < 200; i++) {
                const rx = (Math.random() - 0.5) * (TERRAIN_SIZE - 40);
                const rz = (Math.random() - 0.5) * (TERRAIN_SIZE - 40);

                // Keep clear from spawn center
                if (Math.abs(rx) < 15 && Math.abs(rz) < 15) continue;

                const ry = getTerrainHeight(rx, rz);

                if (Math.random() > 0.35) {
                    // Create Tree
                    const tree = new THREE.Group();
                    const trunk = new THREE.Mesh(trunkGeo, trunkMat);
                    trunk.position.y = 0.75;
                    trunk.castShadow = true;

                    const leaves = new THREE.Mesh(treeGeo, treeMat);
                    leaves.position.y = 3.25;
                    leaves.castShadow = true;

                    tree.add(trunk, leaves);
                    tree.position.set(rx, ry, rz);
                    const scale = 0.7 + Math.random() * 0.7;
                    tree.scale.set(scale, scale, scale);
                    treeInstanced.add(tree);
                } else {
                    // Create Rock/Boulder bump obstacle
                    const rockGeo = new THREE.DodecahedronGeometry(0.8 + Math.random() * 0.8, 1);
                    const rock = new THREE.Mesh(rockGeo, rockMat);
                    rock.position.set(rx, ry + 0.3, rz);
                    rock.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0);
                    rock.castShadow = true;
                    rock.receiveShadow = true;
                    treeInstanced.add(rock);
                }
            }
            scene.add(treeInstanced);
        }

        // --- DETAILED 3D JEEP VEHICLE GENERATOR ---
        function createJeep() {
            jeepGroup = new THREE.Group();

            // Materials
            const bodyMat = new THREE.MeshStandardMaterial({ color: 0xd97706, roughness: 0.3, metalness: 0.4 }); // Amber / Orange Paint
            const darkMetalMat = new THREE.MeshStandardMaterial({ color: 0x1f2937, roughness: 0.6, metalness: 0.8 }); // Bumpers, Rollbars
            const glassMat = new THREE.MeshPhysicalMaterial({ color: 0x111827, transparent: true, opacity: 0.7, roughness: 0.1, transmission: 0.6 });
            const wheelMat = new THREE.MeshStandardMaterial({ color: 0x111827, roughness: 0.9 });
            const rimMat = new THREE.MeshStandardMaterial({ color: 0x9ca3af, metalness: 0.9, roughness: 0.2 });
            const lightFrontMat = new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xfffbeb, emissiveIntensity: 0.8 });
            const lightBackMat = new THREE.MeshStandardMaterial({ color: 0xef4444, emissive: 0xd97706, emissiveIntensity: 0.6 });

            // 1. Chassis Base Body
            const bodyMesh = new THREE.Mesh(new THREE.BoxGeometry(2.0, 0.8, 3.8), bodyMat);
            bodyMesh.position.y = 0.8;
            bodyMesh.castShadow = true;
            jeepGroup.add(bodyMesh);

            // Engine Hood Front
            const hoodMesh = new THREE.Mesh(new THREE.BoxGeometry(1.9, 0.5, 1.4), bodyMat);
            hoodMesh.position.set(0, 1.15, 1.1);
            hoodMesh.castShadow = true;
            jeepGroup.add(hoodMesh);

            // Front Off-Road Grille
            const grille = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.5, 0.1), darkMetalMat);
            grille.position.set(0, 1.1, 1.81);
            jeepGroup.add(grille);

            // Headlights
            const headLightGeo = new THREE.CylinderGeometry(0.2, 0.2, 0.1, 16);
            headLightGeo.rotateX(Math.PI / 2);
           
            const headL = new THREE.Mesh(headLightGeo, lightFrontMat);
            headL.position.set(-0.65, 1.15, 1.82);
            const headR = new THREE.Mesh(headLightGeo, lightFrontMat);
            headR.position.set(0.65, 1.15, 1.82);
            jeepGroup.add(headL, headR);

            // Spotlights (Front beams)
            const spotL = new THREE.SpotLight(0xfff5ea, 2, 40, Math.PI / 6, 0.4);
            spotL.position.set(-0.65, 1.15, 1.85);
            spotL.target.position.set(-0.65, 0.5, 10);
            jeepGroup.add(spotL, spotL.target);
           
            const spotR = new THREE.SpotLight(0xfff5ea, 2, 40, Math.PI / 6, 0.4);
            spotR.position.set(0.65, 1.15, 1.85);
            spotR.target.position.set(0.65, 0.5, 10);
            jeepGroup.add(spotR, spotR.target);

            // Tail Lights
            const tailLightGeo = new THREE.BoxGeometry(0.3, 0.2, 0.05);
            const tailL = new THREE.Mesh(tailLightGeo, lightBackMat);
            tailL.position.set(-0.75, 1.1, -1.91);
            const tailR = new THREE.Mesh(tailLightGeo, lightBackMat);
            tailR.position.set(0.75, 1.1, -1.91);
            jeepGroup.add(tailL, tailR);

            // Windshield Frame & Glass
            const frameMat = new THREE.MeshStandardMaterial({ color: 0x1f2937 });
            const windshieldFrame = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.8, 0.1), frameMat);
            windshieldFrame.position.set(0, 1.6, 0.35);
            windshieldFrame.rotation.x = -0.25;
            jeepGroup.add(windshieldFrame);

            const glass = new THREE.Mesh(new THREE.PlaneGeometry(1.6, 0.65), glassMat);
            glass.position.set(0, 1.6, 0.41);
            glass.rotation.x = -0.25;
            jeepGroup.add(glass);

            // Roll Cage / Roof Bars
            const barGeo = new THREE.CylinderGeometry(0.06, 0.06, 1.4);
            const bar1 = new THREE.Mesh(barGeo, darkMetalMat);
            bar1.position.set(-0.85, 1.7, -0.4);
            const bar2 = new THREE.Mesh(barGeo, darkMetalMat);
            bar2.position.set(0.85, 1.7, -0.4);
            const bar3 = new THREE.Mesh(barGeo, darkMetalMat);
            bar3.position.set(-0.85, 1.7, -1.5);
            const bar4 = new THREE.Mesh(barGeo, darkMetalMat);
            bar4.position.set(0.85, 1.7, -1.5);
           
            const topBarGeo = new THREE.CylinderGeometry(0.06, 0.06, 1.2);
            topBarGeo.rotateZ(Math.PI / 2);
            const barTopF = new THREE.Mesh(topBarGeo, darkMetalMat);
            barTopF.position.set(0, 2.3, -0.4);
            const barTopB = new THREE.Mesh(topBarGeo, darkMetalMat);
            barTopB.position.set(0, 2.3, -1.5);

            jeepGroup.add(bar1, bar2, bar3, bar4, barTopF, barTopB);

            // Front Bumpers & Tow Hooks
            const bumper = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.35, 0.4), darkMetalMat);
            bumper.position.set(0, 0.6, 2.0);
            jeepGroup.add(bumper);

            // Spare Tire on Back
            const spareTire = createSingleWheelMesh(0.5, 0.4, wheelMat, rimMat);
            spareTire.rotation.z = Math.PI / 2;
            spareTire.position.set(0, 1.2, -2.15);
            jeepGroup.add(spareTire);

            // 2. Wheels Assembly (4 Wheels)
            const wheelPositions = [
                { x: -1.1, y: 0.5, z: 1.1, name: 'FL' },  // Front Left
                { x: 1.1, y: 0.5, z: 1.1, name: 'FR' },   // Front Right
                { x: -1.1, y: 0.5, z: -1.1, name: 'RL' }, // Rear Left
                { x: 1.1, y: 0.5, z: -1.1, name: 'RR' }   // Rear Right
            ];

            wheels = [];
            wheelPositions.forEach(pos => {
                const wheelPivot = new THREE.Group();
                wheelPivot.position.set(pos.x, pos.y, pos.z);

                const wheelMesh = createSingleWheelMesh(0.55, 0.45, wheelMat, rimMat);
                wheelPivot.add(wheelMesh);
               
                // Shock Absorber / Spring visual
                const springGeo = new THREE.CylinderGeometry(0.08, 0.08, 0.6, 8);
                const springMat = new THREE.MeshStandardMaterial({ color: 0xef4444, metalness: 0.8 });
                const spring = new THREE.Mesh(springGeo, springMat);
                spring.position.set(pos.x > 0 ? -0.2 : 0.2, 0.3, 0);
                wheelPivot.add(spring);

                jeepGroup.add(wheelPivot);
                wheels.push({ group: wheelPivot, mesh: wheelMesh, config: pos });
            });

            scene.add(jeepGroup);
        }

        // Helper for creating detailed off-road wheels
        function createSingleWheelMesh(radius, width, tireMat, rimMat) {
            const wheelGroup = new THREE.Group();
           
            // Tire rubber
            const tireGeo = new THREE.CylinderGeometry(radius, radius, width, 24);
            tireGeo.rotateZ(Math.PI / 2);
            const tire = new THREE.Mesh(tireGeo, tireMat);
            tire.castShadow = true;
            wheelGroup.add(tire);

            // Metallic Rim
            const rimGeo = new THREE.CylinderGeometry(radius * 0.55, radius * 0.55, width + 0.02, 16);
            rimGeo.rotateZ(Math.PI / 2);
            const rim = new THREE.Mesh(rimGeo, rimMat);
            wheelGroup.add(rim);

            // Rim Spokes / Off-road bolts
            const spokeGeo = new THREE.BoxGeometry(width + 0.04, radius * 0.9, 0.1);
            for(let a=0; a<3; a++) {
                const spoke = new THREE.Mesh(spokeGeo, rimMat);
                spoke.rotation.x = (a * Math.PI) / 3;
                wheelGroup.add(spoke);
            }

            return wheelGroup;
        }

        // --- VEHICLE PHYSICS & TERRAIN SUSPENSION ADAPTATION ---
        function updateVehiclePhysics(delta) {
            // 1. Inputs handling
            const throttle = keys.w ? 1 : (keys.s ? -0.6 : 0);
           
            // Steering Angle
            let targetSteer = 0;
            if (keys.a) targetSteer += vehicle.maxSteer;
            if (keys.d) targetSteer -= vehicle.maxSteer;
           
            // Smooth Steering transition
            vehicle.steeringAngle += (targetSteer - vehicle.steeringAngle) * vehicle.steerSpeed * delta;

            // 2. Speed / Acceleration Dynamics
            if (throttle !== 0) {
                // Scaled max speed from UI Slider
                const effectiveMaxSpeed = throttle > 0 ? (vehicle.maxSpeed * 0.35) : (vehicle.reverseSpeed * 0.35);
                vehicle.speed += throttle * vehicle.acceleration * delta;
               
                // Clamp speed
                if (throttle > 0 && vehicle.speed > effectiveMaxSpeed) vehicle.speed = effectiveMaxSpeed;
                if (throttle < 0 && vehicle.speed < effectiveMaxSpeed) vehicle.speed = effectiveMaxSpeed;
            } else {
                // Natural Friction / Engine Braking
                vehicle.speed *= Math.pow(vehicle.friction, delta * 60);
                if (Math.abs(vehicle.speed) < 0.01) vehicle.speed = 0;
            }

            // 3. Turn Heading Yaw angle
            if (Math.abs(vehicle.speed) > 0.05) {
                const directionFactor = vehicle.speed > 0 ? 1 : -1;
                vehicle.heading += vehicle.steeringAngle * directionFactor * (vehicle.speed / 15.0) * delta;
            }

            // 4. Update Horizontal Position (X, Z)
            vehicle.x += Math.sin(vehicle.heading) * vehicle.speed * delta;
            vehicle.z += Math.cos(vehicle.heading) * vehicle.speed * delta;

            // 5. Calculate Terrain Heights under 4 Wheels for Pitch/Roll Physics
            const wheelBase = 2.2; // Distance front-to-back
            const trackWidth = 2.2; // Distance left-to-right

            // Compute world positions for 4 corners of the jeep
            const sinH = Math.sin(vehicle.heading);
            const cosH = Math.cos(vehicle.heading);

            // Front & Back offsets
            const frontX = vehicle.x + sinH * (wheelBase * 0.5);
            const frontZ = vehicle.z + cosH * (wheelBase * 0.5);
            const rearX  = vehicle.x - sinH * (wheelBase * 0.5);
            const rearZ  = vehicle.z - cosH * (wheelBase * 0.5);

            // Left & Right corner heights
            const hFL = getTerrainHeight(frontX - cosH * (trackWidth * 0.5), frontZ + sinH * (trackWidth * 0.5));
            const hFR = getTerrainHeight(frontX + cosH * (trackWidth * 0.5), frontZ - sinH * (trackWidth * 0.5));
            const hRL = getTerrainHeight(rearX - cosH * (trackWidth * 0.5), rearZ + sinH * (trackWidth * 0.5));
            const hRR = getTerrainHeight(rearX + cosH * (trackWidth * 0.5), rearZ - sinH * (trackWidth * 0.5));

            // Average Height under center of vehicle
            const targetY = (hFL + hFR + hRL + hRR) / 4.0;
            // Smooth vertical suspension dampening
            vehicle.y += (targetY - vehicle.y) * 12.0 * delta;

            // Compute Terrain Slopes (Pitch & Roll)
            const frontAvgHeight = (hFL + hFR) / 2.0;
            const rearAvgHeight  = (hRL + hRR) / 2.0;
            const leftAvgHeight  = (hFL + hRL) / 2.0;
            const rightAvgHeight = (hFR + hRR) / 2.0;

            const targetPitch = Math.atan2(frontAvgHeight - rearAvgHeight, wheelBase);
            const targetRoll  = Math.atan2(leftAvgHeight - rightAvgHeight, trackWidth);

            // Dampened tilt response
            vehicle.pitch += (targetPitch - vehicle.pitch) * 10.0 * delta;
            vehicle.roll  += (targetRoll - vehicle.roll)  * 10.0 * delta;

            // 6. Apply transformations to Jeep 3D Group
            jeepGroup.position.set(vehicle.x, vehicle.y + 0.1, vehicle.z);
           
            // Quaternion rotation order: Heading (Yaw) -> Pitch -> Roll
            const euler = new THREE.Euler(vehicle.pitch, vehicle.heading, vehicle.roll, 'YXZ');
            jeepGroup.quaternion.setFromEuler(euler);

            // 7. Animate Front Wheel Steering & All Wheel Rotations
            const wheelRotationDelta = (vehicle.speed / 0.55) * delta;
            vehicle.wheelRotation += wheelRotationDelta;

            wheels.forEach(w => {
                // Spin wheels forward/backward
                w.mesh.rotation.x = vehicle.wheelRotation;

                // Turn front wheels left/right
                if (w.config.name === 'FL' || w.config.name === 'FR') {
                    w.group.rotation.y = vehicle.steeringAngle;
                }
            });

            // 8. Check Flip / Warning Condition
            vehicle.isFlipped = Math.abs(vehicle.roll) > 1.1 || Math.abs(vehicle.pitch) > 1.2;
            const warningToast = document.getElementById('warning-toast');
            if (vehicle.isFlipped) {
                warningToast.classList.remove('opacity-0');
            } else {
                warningToast.classList.add('opacity-0');
            }

            // 9. Update UI HUD (Speedometer & Dash)
            updateHUD();
        }

        // --- CAMERA CONTROLLER ---
        function updateCamera() {
            if (cameraMode === 0) {
                // 3rd Person Smooth Follow Camera
                const cameraDistance = 11.0;
                const cameraHeight = 4.2;

                // Position behind jeep based on current vehicle heading
                const targetCamX = vehicle.x - Math.sin(vehicle.heading) * cameraDistance;
                const targetCamZ = vehicle.z - Math.cos(vehicle.heading) * cameraDistance;
                const targetCamY = vehicle.y + cameraHeight;

                // Lerp smooth movement
                camera.position.x += (targetCamX - camera.position.x) * 0.1;
                camera.position.y += (targetCamY - camera.position.y) * 0.1;
                camera.position.z += (targetCamZ - camera.position.z) * 0.1;

                // Look at point slightly above vehicle center
                const lookAtTarget = new THREE.Vector3(vehicle.x, vehicle.y + 1.2, vehicle.z);
                camera.lookAt(lookAtTarget);
            } else {
                // Orbit mode controls update target
                orbitControls.target.set(vehicle.x, vehicle.y + 1.0, vehicle.z);
                orbitControls.update();
            }
        }

        // --- HUD / UI UPDATER ---
        function updateHUD() {
            // Speed in km/h scale conversion
            const speedKmh = Math.round(Math.abs(vehicle.speed) * 3.6 * 0.85);
           
            document.getElementById('speed-display').innerText = speedKmh;

            // Circular Speed Meter SVG Offset calculation
            const maxDisplaySpeed = vehicle.maxSpeed;
            const progress = Math.min(1.0, speedKmh / maxDisplaySpeed);
            const dashoffset = 251.2 * (1 - progress);
            document.getElementById('speed-circle').style.strokeDashoffset = dashoffset;

            // Gear indicator
            const gearDisplay = document.getElementById('gear-display');
            if (keys.s && vehicle.speed < 0) {
                gearDisplay.innerText = 'R';
                gearDisplay.className = "font-orbitron text-sm font-bold bg-rose-500 text-white px-2 py-0.5 rounded";
            } else if (vehicle.speed === 0) {
                gearDisplay.innerText = 'N';
                gearDisplay.className = "font-orbitron text-sm font-bold bg-slate-600 text-white px-2 py-0.5 rounded";
            } else {
                gearDisplay.innerText = 'D';
                gearDisplay.className = "font-orbitron text-sm font-bold bg-amber-500 text-slate-950 px-2 py-0.5 rounded";
            }

            // Key Buttons Highlight Feedback in HUD
            toggleKeyHighlight('key-w', keys.w);
            toggleKeyHighlight('key-a', keys.a);
            toggleKeyHighlight('key-s', keys.s);
            toggleKeyHighlight('key-d', keys.d);
        }

        function toggleKeyHighlight(elementId, isActive) {
            const el = document.getElementById(elementId);
            if (!el) return;
            if (isActive) el.classList.add('active');
            else el.classList.remove('active');
        }

        // --- RESPAWN VEHICLE ---
        function respawnVehicle() {
            vehicle.x = 0;
            vehicle.z = 0;
            vehicle.speed = 0;
            vehicle.heading = 0;
            vehicle.pitch = 0;
            vehicle.roll = 0;
            vehicle.steeringAngle = 0;
            vehicle.y = getTerrainHeight(0, 0);

            // Reset camera back smoothly
            camera.position.set(0, vehicle.y + 5, -12);
        }

        // --- EVENT LISTENERS & UI HOOKS ---
        function setupEventListeners() {
            // Keyboard controls
            window.addEventListener('keydown', (e) => {
                const k = e.key.toLowerCase();
                if (k === 'w' || e.key === 'ArrowUp') keys.w = true;
                if (k === 's' || e.key === 'ArrowDown') keys.s = true;
                if (k === 'a' || e.key === 'ArrowLeft') keys.a = true;
                if (k === 'd' || e.key === 'ArrowRight') keys.d = true;
                if (k === 'r') respawnVehicle();
                if (k === 'c') toggleCameraMode();
            });

            window.addEventListener('keyup', (e) => {
                const k = e.key.toLowerCase();
                if (k === 'w' || e.key === 'ArrowUp') keys.w = false;
                if (k === 's' || e.key === 'ArrowDown') keys.s = false;
                if (k === 'a' || e.key === 'ArrowLeft') keys.a = false;
                if (k === 'd' || e.key === 'ArrowRight') keys.d = false;
            });

            // Speed Limit Slider Event
            const slider = document.getElementById('slider-speed');
            const speedLimitValStr = document.getElementById('speed-limit-val');
            const powerPercentage = document.getElementById('power-percentage');

            slider.addEventListener('input', (e) => {
                const val = parseInt(e.target.value);
                vehicle.maxSpeed = val;
                speedLimitValStr.innerText = val + ' km/h';

                // Percentage relative to slider max (150)
                const pct = Math.round((val / 150) * 100);
                powerPercentage.innerText = pct + '%';
            });

            // UI Button Hooks
            document.getElementById('btn-respawn').addEventListener('click', respawnVehicle);
            document.getElementById('btn-camera').addEventListener('click', toggleCameraMode);

            // Mobile Touch Controls Binding
            bindTouchButton('touch-up', 'w');
            bindTouchButton('touch-down', 's');
            bindTouchButton('touch-left', 'a');
            bindTouchButton('touch-right', 'd');
        }

        function bindTouchButton(elementId, keyProp) {
            const btn = document.getElementById(elementId);
            if (!btn) return;
           
            const startPress = (e) => {
                e.preventDefault();
                keys[keyProp] = true;
            };
            const endPress = (e) => {
                e.preventDefault();
                keys[keyProp] = false;
            };

            btn.addEventListener('touchstart', startPress);
            btn.addEventListener('touchend', endPress);
            btn.addEventListener('mousedown', startPress);
            btn.addEventListener('mouseup', endPress);
            btn.addEventListener('mouseleave', endPress);
        }

        function toggleCameraMode() {
            cameraMode = cameraMode === 0 ? 1 : 0;
            orbitControls.enabled = (cameraMode === 1);
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        // --- MAIN ANIMATION LOOP ---
        let lastTime = performance.now();
        function animate() {
            requestAnimationFrame(animate);

            const now = performance.now();
            const delta = Math.min((now - lastTime) / 1000, 0.1); // Cap delta to prevent huge jumps
            lastTime = now;

            // Physics Update
            updateVehiclePhysics(delta);

            // Camera Update
            updateCamera();

            // Render
            renderer.render(scene, camera);
        }

        // Start initialization on window load
        window.onload = init;
    </script>
</body>
</html>