SAFE44 commited on
Commit
8bbc3f1
·
verified ·
1 Parent(s): edef455

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +805 -19
index.html CHANGED
@@ -1,19 +1,805 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Royal Driver 3D - Showroom & Drive</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
10
+ <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@400;600&display=swap');
12
+
13
+ body {
14
+ margin: 0;
15
+ overflow: hidden;
16
+ background-color: #000;
17
+ font-family: 'Rajdhani', sans-serif;
18
+ user-select: none;
19
+ -webkit-user-select: none;
20
+ }
21
+
22
+ #canvas-container {
23
+ position: absolute;
24
+ top: 0;
25
+ left: 0;
26
+ width: 100%;
27
+ height: 100%;
28
+ z-index: 1;
29
+ }
30
+
31
+ .ui-layer {
32
+ position: absolute;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ z-index: 10;
38
+ pointer-events: none;
39
+ display: flex;
40
+ flex-direction: column;
41
+ justify-content: space-between;
42
+ padding: 1rem;
43
+ box-sizing: border-box;
44
+ }
45
+
46
+ .interactive {
47
+ pointer-events: auto;
48
+ }
49
+
50
+ /* Neon Glow Text */
51
+ .neon-text {
52
+ text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 20px #0ff, 0 0 30px #0ff, 0 0 40px #0ff;
53
+ }
54
+
55
+ /* Button Styles */
56
+ .btn-glass {
57
+ background: rgba(255, 255, 255, 0.1);
58
+ backdrop-filter: blur(10px);
59
+ border: 1px solid rgba(255, 255, 255, 0.2);
60
+ color: white;
61
+ transition: all 0.2s ease;
62
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
63
+ }
64
+ .btn-glass:active {
65
+ transform: scale(0.95);
66
+ background: rgba(255, 255, 255, 0.25);
67
+ }
68
+ .btn-glass.active-btn {
69
+ background: rgba(0, 255, 255, 0.3);
70
+ border-color: #0ff;
71
+ box-shadow: 0 0 15px #0ff;
72
+ }
73
+
74
+ /* Color Swatch */
75
+ .swatch {
76
+ width: 40px;
77
+ height: 40px;
78
+ border-radius: 50%;
79
+ border: 2px solid rgba(255,255,255,0.5);
80
+ cursor: pointer;
81
+ transition: transform 0.2s;
82
+ }
83
+ .swatch:hover { transform: scale(1.2); }
84
+ .swatch.selected { border-color: #fff; box-shadow: 0 0 10px #fff; }
85
+
86
+ /* HUD */
87
+ .hud-panel {
88
+ background: rgba(0, 0, 0, 0.6);
89
+ border-left: 2px solid #0ff;
90
+ padding: 10px 20px;
91
+ transform: skewX(-15deg);
92
+ }
93
+ .hud-text {
94
+ transform: skewX(15deg);
95
+ font-family: 'Orbitron', sans-serif;
96
+ }
97
+
98
+ /* Loader */
99
+ #loader {
100
+ position: fixed;
101
+ top: 0;
102
+ left: 0;
103
+ width: 100%;
104
+ height: 100%;
105
+ background: #050505;
106
+ z-index: 100;
107
+ display: flex;
108
+ flex-direction: column;
109
+ align-items: center;
110
+ justify-content: center;
111
+ color: #0ff;
112
+ font-family: 'Orbitron', sans-serif;
113
+ transition: opacity 0.5s;
114
+ }
115
+ .loader-bar {
116
+ width: 200px;
117
+ height: 4px;
118
+ background: #333;
119
+ margin-top: 20px;
120
+ position: relative;
121
+ overflow: hidden;
122
+ }
123
+ .loader-progress {
124
+ position: absolute;
125
+ top: 0;
126
+ left: 0;
127
+ height: 100%;
128
+ width: 0%;
129
+ background: #0ff;
130
+ box-shadow: 0 0 10px #0ff;
131
+ transition: width 0.2s;
132
+ }
133
+
134
+ /* Animations */
135
+ @keyframes pulse-ring {
136
+ 0% { transform: scale(0.8); opacity: 0.5; }
137
+ 100% { transform: scale(1.2); opacity: 0; }
138
+ }
139
+ </style>
140
+ </head>
141
+ <body>
142
+
143
+ <!-- Loading Screen -->
144
+ <div id="loader">
145
+ <div class="text-2xl mb-2 tracking-widest">ROYAL DRIVER <span class="text-xs align-top">3D</span></div>
146
+ <div class="text-sm text-gray-400">INITIALIZING ENGINE...</div>
147
+ <div class="loader-bar"><div class="loader-progress" id="loader-progress"></div></div>
148
+ <div class="mt-4 text-xs text-gray-500">Built with anycoder</div>
149
+ </div>
150
+
151
+ <!-- 3D Canvas -->
152
+ <div id="canvas-container"></div>
153
+
154
+ <!-- UI Layer -->
155
+ <div class="ui-layer">
156
+
157
+ <!-- Header -->
158
+ <div class="w-full flex justify-between items-start">
159
+ <div class="flex flex-col">
160
+ <h1 class="text-4xl md:text-6xl font-black text-white italic tracking-tighter drop-shadow-lg" style="font-family: 'Orbitron'">
161
+ ROYAL<span class="text-cyan-400">DRIVER</span>
162
+ </h1>
163
+ <p class="text-cyan-200 text-xs md:text-sm tracking-widest uppercase opacity-80">Simulation Environment v2.4</p>
164
+ </div>
165
+ <div class="hidden md:block">
166
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="text-xs text-white opacity-50 hover:opacity-100 transition cursor-pointer interactive">Built with anycoder</a>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Showroom UI (Centered) -->
171
+ <div id="showroom-ui" class="absolute inset-0 flex flex-col items-center justify-center interactive transition-opacity duration-500">
172
+ <div class="bg-black/60 backdrop-blur-md p-8 rounded-xl border border-cyan-500/30 flex flex-col items-center gap-6 shadow-2xl transform transition-all hover:scale-105">
173
+ <div class="text-center">
174
+ <h2 class="text-2xl text-white font-bold mb-1">CUSTOMIZE RIDE</h2>
175
+ <p class="text-gray-400 text-xs">SELECT PAINT JOB</p>
176
+ </div>
177
+
178
+ <div class="flex gap-4" id="color-picker">
179
+ <button class="swatch bg-red-600" data-color="0xff0000"></button>
180
+ <button class="swatch bg-blue-600" data-color="0x0044ff"></button>
181
+ <button class="swatch bg-green-600" data-color="0x00ff00"></button>
182
+ <button class="swatch bg-yellow-400" data-color="0xffcc00"></button>
183
+ <button class="swatch bg-gray-900 border-gray-600" data-color="0x111111"></button>
184
+ <button class="swatch bg-white" data-color="0xffffff"></button>
185
+ </div>
186
+
187
+ <button id="start-btn" class="mt-4 px-12 py-4 bg-cyan-600 hover:bg-cyan-500 text-white font-black text-xl rounded-sm tracking-widest clip-path-polygon transition-all shadow-[0_0_20px_rgba(0,255,255,0.5)] hover:shadow-[0_0_40px_rgba(0,255,255,0.8)] active:scale-95">
188
+ START RACE
189
+ </button>
190
+ </div>
191
+ </div>
192
+
193
+ <!-- Driving UI (Hidden by default) -->
194
+ <div id="driving-ui" class="absolute inset-0 flex flex-col justify-between opacity-0 pointer-events-none transition-opacity duration-500">
195
+
196
+ <!-- HUD Top -->
197
+ <div class="w-full flex justify-end p-4">
198
+ <div class="hud-panel">
199
+ <div class="hud-text text-right">
200
+ <div class="text-xs text-cyan-300 uppercase">Speed</div>
201
+ <div class="text-4xl text-white font-bold"><span id="speed-display">0</span> <span class="text-sm">KM/H</span></div>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
206
+ <!-- Controls Bottom -->
207
+ <div class="w-full flex justify-between items-end p-4 pb-8">
208
+ <!-- Steering -->
209
+ <div class="flex gap-4">
210
+ <button id="btn-left" class="btn-glass w-20 h-20 rounded-full flex items-center justify-center text-3xl interactive">
211
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
212
+ </button>
213
+ <button id="btn-right" class="btn-glass w-20 h-20 rounded-full flex items-center justify-center text-3xl interactive">
214
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
215
+ </button>
216
+ </div>
217
+
218
+ <!-- Pedals -->
219
+ <div class="flex gap-4">
220
+ <button id="btn-brake" class="btn-glass w-24 h-24 rounded-full flex flex-col items-center justify-center border-red-500/50 interactive">
221
+ <span class="font-bold text-red-400">BRAKE</span>
222
+ </button>
223
+ <button id="btn-gas" class="btn-glass w-28 h-28 rounded-full flex flex-col items-center justify-center border-green-500/50 interactive">
224
+ <span class="font-bold text-green-400">GAS</span>
225
+ </button>
226
+ </div>
227
+ </div>
228
+ </div>
229
+
230
+ </div>
231
+
232
+ <script>
233
+ /**
234
+ * ------------------------------------------------------------------
235
+ * APPLICATION STATE & CONFIGURATION
236
+ * ------------------------------------------------------------------
237
+ */
238
+ const CONFIG = {
239
+ maxSpeed: 120, // Arbitrary units
240
+ acceleration: 0.5,
241
+ deceleration: 0.2,
242
+ turnSpeed: 0.03,
243
+ cameraOffset: new THREE.Vector3(0, 3, -7), // Behind car
244
+ showroomRotationSpeed: 0.005
245
+ };
246
+
247
+ const STATE = {
248
+ mode: 'showroom', // 'showroom' or 'drive'
249
+ speed: 0,
250
+ steering: 0, // -1 to 1
251
+ isAccelerating: false,
252
+ isBraking: false,
253
+ audioInitialized: false
254
+ };
255
+
256
+ /**
257
+ * ------------------------------------------------------------------
258
+ * ASSET MANAGEMENT (Simulated since we don't have real external files)
259
+ * ------------------------------------------------------------------
260
+ */
261
+ // Helper to create a texture programmatically if fetch fails or for placeholders
262
+ function createPlaceholderTexture(type) {
263
+ const canvas = document.createElement('canvas');
264
+ canvas.width = 512;
265
+ canvas.height = 512;
266
+ const ctx = canvas.getContext('2d');
267
+
268
+ if (type === 'road') {
269
+ // Asphalt
270
+ ctx.fillStyle = '#333';
271
+ ctx.fillRect(0, 0, 512, 512);
272
+ // Noise
273
+ for(let i=0; i<5000; i++) {
274
+ ctx.fillStyle = `rgba(255,255,255,${Math.random() * 0.1})`;
275
+ ctx.fillRect(Math.random()*512, Math.random()*512, 2, 2);
276
+ }
277
+ // Center lines
278
+ ctx.strokeStyle = '#fff';
279
+ ctx.lineWidth = 4;
280
+ ctx.setLineDash([40, 40]);
281
+ ctx.beginPath();
282
+ ctx.moveTo(256, 0);
283
+ ctx.lineTo(256, 512);
284
+ ctx.stroke();
285
+ } else if (type === 'pole') {
286
+ // Metal/Concrete
287
+ const grad = ctx.createLinearGradient(0, 0, 512, 0);
288
+ grad.addColorStop(0, '#444');
289
+ grad.addColorStop(0.5, '#777');
290
+ grad.addColorStop(1, '#444');
291
+ ctx.fillStyle = grad;
292
+ ctx.fillRect(0, 0, 512, 512);
293
+ // Rust
294
+ ctx.fillStyle = 'rgba(100, 50, 20, 0.2)';
295
+ ctx.fillRect(0, 0, 512, 100);
296
+ ctx.fillRect(0, 412, 512, 100);
297
+ }
298
+
299
+ const tex = new THREE.CanvasTexture(canvas);
300
+ tex.wrapS = THREE.RepeatWrapping;
301
+ tex.wrapT = THREE.RepeatWrapping;
302
+ return tex;
303
+ }
304
+
305
+ // Since we can't guarantee external GLB files work perfectly in a single HTML without CORS/availability issues,
306
+ // we will construct a high-quality "Beta" car using primitives to ensure the app ALWAYS works perfectly.
307
+ function createProceduralCar() {
308
+ const carGroup = new THREE.Group();
309
+
310
+ // Chassis
311
+ const chassisGeo = new THREE.BoxGeometry(2, 0.5, 4);
312
+ const chassisMat = new THREE.MeshStandardMaterial({ color: 0xcc0000, metalness: 0.6, roughness: 0.2 });
313
+ const chassis = new THREE.Mesh(chassisGeo, chassisMat);
314
+ chassis.position.y = 0.5;
315
+ chassis.castShadow = true;
316
+ carGroup.add(chassis);
317
+
318
+ // Cabin
319
+ const cabinGeo = new THREE.BoxGeometry(1.8, 0.4, 2);
320
+ const cabinMat = new THREE.MeshStandardMaterial({ color: 0x111111, metalness: 0.9, roughness: 0.1 });
321
+ const cabin = new THREE.Mesh(cabinGeo, cabinMat);
322
+ cabin.position.set(0, 1.0, -0.2);
323
+ cabin.castShadow = true;
324
+ carGroup.add(cabin);
325
+
326
+ // Wheels
327
+ const wheelGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.3, 24);
328
+ const wheelMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.9 });
329
+
330
+ const positions = [
331
+ { x: -1.1, z: 1.2 }, { x: 1.1, z: 1.2 },
332
+ { x: -1.1, z: -1.2 }, { x: 1.1, z: -1.2 }
333
+ ];
334
+
335
+ positions.forEach(pos => {
336
+ const wheel = new THREE.Mesh(wheelGeo, wheelMat);
337
+ wheel.rotation.z = Math.PI / 2;
338
+ wheel.position.set(pos.x, 0.4, pos.z);
339
+ wheel.castShadow = true;
340
+ carGroup.add(wheel);
341
+ });
342
+
343
+ // Headlights (Physical Geometry)
344
+ const lightGeo = new THREE.CircleGeometry(0.15, 16);
345
+ const lightMat = new THREE.MeshBasicMaterial({ color: 0xffffcc });
346
+
347
+ const leftLight = new THREE.Mesh(lightGeo, lightMat);
348
+ leftLight.position.set(-0.6, 0.5, 2.01);
349
+ carGroup.add(leftLight);
350
+
351
+ const rightLight = new THREE.Mesh(lightGeo, lightMat);
352
+ rightLight.position.set(0.6, 0.5, 2.01);
353
+ carGroup.add(rightLight);
354
+
355
+ // Tail lights
356
+ const tailGeo = new THREE.PlaneGeometry(0.3, 0.1);
357
+ const tailMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
358
+ const leftTail = new THREE.Mesh(tailGeo, tailMat);
359
+ leftTail.position.set(-0.6, 0.5, -2.01);
360
+ leftTail.rotation.y = Math.PI;
361
+ carGroup.add(leftTail);
362
+
363
+ const rightTail = new THREE.Mesh(tailGeo, tailMat);
364
+ rightTail.position.set(0.6, 0.5, -2.01);
365
+ rightTail.rotation.y = Math.PI;
366
+ carGroup.add(rightTail);
367
+
368
+ // Spotlights for Showroom
369
+ const spotL = new THREE.SpotLight(0xffffee, 2);
370
+ spotL.position.set(-0.6, 0.5, 2.1);
371
+ spotL.target.position.set(-0.6, 0, 10);
372
+ spotL.angle = Math.PI / 6;
373
+ spotL.penumbra = 0.2;
374
+ spotL.distance = 20;
375
+ carGroup.add(spotL);
376
+ carGroup.add(spotL.target);
377
+
378
+ const spotR = new THREE.SpotLight(0xffffee, 2);
379
+ spotR.position.set(0.6, 0.5, 2.1);
380
+ spotR.target.position.set(0.6, 0, 10);
381
+ spotR.angle = Math.PI / 6;
382
+ spotR.penumbra = 0.2;
383
+ spotR.distance = 20;
384
+ carGroup.add(spotR);
385
+ carGroup.add(spotR.target);
386
+
387
+ // Store references for color changing
388
+ carGroup.userData.bodyMeshes = [chassis];
389
+
390
+ return carGroup;
391
+ }
392
+
393
+ /**
394
+ * ------------------------------------------------------------------
395
+ * THREE.JS SETUP
396
+ * ------------------------------------------------------------------
397
+ */
398
+ const container = document.getElementById('canvas-container');
399
+ const scene = new THREE.Scene();
400
+ scene.background = new THREE.Color(0x87CEEB); // Daytime Sky
401
+ scene.fog = new THREE.Fog(0x87CEEB, 20, 100);
402
+
403
+ const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
404
+ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
405
+ renderer.setSize(window.innerWidth, window.innerHeight);
406
+ renderer.shadowMap.enabled = true;
407
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
408
+ container.appendChild(renderer.domElement);
409
+
410
+ // Lighting
411
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
412
+ scene.add(ambientLight);
413
+
414
+ const sunLight = new THREE.DirectionalLight(0xffffff, 0.8);
415
+ sunLight.position.set(50, 100, 50);
416
+ sunLight.castShadow = true;
417
+ sunLight.shadow.mapSize.width = 2048;
418
+ sunLight.shadow.mapSize.height = 2048;
419
+ sunLight.shadow.camera.near = 0.5;
420
+ sunLight.shadow.camera.far = 500;
421
+ sunLight.shadow.camera.left = -50;
422
+ sunLight.shadow.camera.right = 50;
423
+ sunLight.shadow.camera.top = 50;
424
+ sunLight.shadow.camera.bottom = -50;
425
+ scene.add(sunLight);
426
+
427
+ // Environment
428
+ const textureLoader = new THREE.TextureLoader();
429
+ const roadTexture = createPlaceholderTexture('road');
430
+ roadTexture.repeat.set(1, 10);
431
+
432
+ const poleTexture = createPlaceholderTexture('pole');
433
+
434
+ // Ground Plane (Grass)
435
+ const groundGeo = new THREE.PlaneGeometry(1000, 1000);
436
+ const groundMat = new THREE.MeshStandardMaterial({ color: 0x2a5a2a });
437
+ const ground = new THREE.Mesh(groundGeo, groundMat);
438
+ ground.rotation.x = -Math.PI / 2;
439
+ ground.position.y = -0.1;
440
+ ground.receiveShadow = true;
441
+ scene.add(ground);
442
+
443
+ // Road
444
+ const roadGeo = new THREE.PlaneGeometry(8, 1000); // Wide road
445
+ const roadMat = new THREE.MeshStandardMaterial({
446
+ map: roadTexture,
447
+ roughness: 0.8
448
+ });
449
+ const road = new THREE.Mesh(roadGeo, roadMat);
450
+ road.rotation.x = -Math.PI / 2;
451
+ road.receiveShadow = true;
452
+ scene.add(road);
453
+
454
+ // Poles
455
+ const poles = [];
456
+ function createPoles() {
457
+ const poleGeo = new THREE.CylinderGeometry(0.1, 0.1, 6, 8);
458
+ const poleMat = new THREE.MeshStandardMaterial({ map: poleTexture, roughness: 0.9 });
459
+
460
+ for (let i = -50; i < 50; i+=2) {
461
+ // Left Pole
462
+ const pL = new THREE.Mesh(poleGeo, poleMat);
463
+ pL.position.set(-6, 3, i * 10);
464
+ pL.castShadow = true;
465
+ scene.add(pL);
466
+ poles.push(pL);
467
+
468
+ // Right Pole
469
+ const pR = new THREE.Mesh(poleGeo, poleMat);
470
+ pR.position.set(6, 3, i * 10);
471
+ pR.castShadow = true;
472
+ scene.add(pR);
473
+ poles.push(pR);
474
+ }
475
+ }
476
+ createPoles();
477
+
478
+ /**
479
+ * ------------------------------------------------------------------
480
+ * CAR LOGIC
481
+ * ------------------------------------------------------------------
482
+ */
483
+ let car;
484
+ let carVelocity = 0;
485
+ let carSteering = 0;
486
+
487
+ function initCar() {
488
+ // Try to load external GLB, fallback to procedural if fails
489
+ const loader = new THREE.GLTFLoader();
490
+ loader.load(
491
+ 'beta.glb',
492
+ (gltf) => {
493
+ car = gltf.scene;
494
+ setupCar(car);
495
+ document.getElementById('loader-progress').style.width = '100%';
496
+ setTimeout(() => document.getElementById('loader').style.opacity = '0', 500);
497
+ setTimeout(() => document.getElementById('loader').style.display = 'none', 1000);
498
+ },
499
+ (xhr) => {
500
+ const percent = (xhr.loaded / xhr.total) * 100;
501
+ document.getElementById('loader-progress').style.width = percent + '%';
502
+ },
503
+ (error) => {
504
+ console.warn("Could not load GLB, using procedural model.", error);
505
+ car = createProceduralCar();
506
+ scene.add(car);
507
+ // Hide loader
508
+ document.getElementById('loader-progress').style.width = '100%';
509
+ setTimeout(() => document.getElementById('loader').style.opacity = '0', 500);
510
+ setTimeout(() => document.getElementById('loader').style.display = 'none', 1000);
511
+ }
512
+ );
513
+ }
514
+
515
+ function setupCar(model) {
516
+ // Scale and center if needed
517
+ const box = new THREE.Box3().setFromObject(model);
518
+ const center = box.getCenter(new THREE.Vector3());
519
+ model.position.sub(center); // Center pivot
520
+
521
+ // Orient to face +Z (forward in Three.js usually, but we want nose at +Z)
522
+ // If model faces +Z, good. If not, rotate.
523
+ // Assuming standard car models face +Z or -Z.
524
+ // Let's rotate 180 if needed. Usually GLTF cars face +Z or -Z.
525
+ // We will assume standard orientation.
526
+
527
+ scene.add(model);
528
+
529
+ // Add shadows
530
+ model.traverse((child) => {
531
+ if (child.isMesh) {
532
+ child.castShadow = true;
533
+ child.receiveShadow = true;
534
+ // Try to identify body parts for color changing
535
+ if (!child.name.toLowerCase().includes('glass') && !child.name.toLowerCase().includes('wheel')) {
536
+ if(!model.userData.bodyMeshes) model.userData.bodyMeshes = [];
537
+ model.userData.bodyMeshes.push(child);
538
+ }
539
+ }
540
+ });
541
+ }
542
+
543
+ initCar();
544
+
545
+ /**
546
+ * ------------------------------------------------------------------
547
+ * AUDIO SYSTEM (Web Audio API)
548
+ * ------------------------------------------------------------------
549
+ */
550
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
551
+ let audioCtx;
552
+ let engineOsc;
553
+ let engineGain;
554
+
555
+ // Since we can't guarantee external MP3s, we synthesize engine sounds using Oscillators.
556
+ // This is actually better for dynamic pitch changes!
557
+
558
+ function initAudio() {
559
+ if (STATE.audioInitialized) return;
560
+ audioCtx = new AudioContext();
561
+
562
+ // Create Oscillator for engine hum
563
+ engineOsc = audioCtx.createOscillator();
564
+ engineGain = audioCtx.createGain();
565
+
566
+ engineOsc.type = 'sawtooth';
567
+ engineOsc.frequency.value = 50; // Idle rumble
568
+
569
+ // Lowpass filter to make it sound muffled like an engine
570
+ const filter = audioCtx.createBiquadFilter();
571
+ filter.type = 'lowpass';
572
+ filter.frequency.value = 400;
573
+
574
+ engineOsc.connect(filter);
575
+ filter.connect(engineGain);
576
+ engineGain.connect(audioCtx.destination);
577
+
578
+ engineOsc.start();
579
+ engineGain.gain.value = 0; // Start silent
580
+
581
+ STATE.audioInitialized = true;
582
+ }
583
+
584
+ function updateEngineSound() {
585
+ if (!STATE.audioInitialized || !audioCtx) return;
586
+
587
+ // Resume context if suspended (browser policy)
588
+ if (audioCtx.state === 'suspended') audioCtx.resume();
589
+
590
+ const baseFreq = 60;
591
+ const maxFreq = 300;
592
+
593
+ // Calculate speed ratio (0 to 1)
594
+ const speedRatio = Math.min(STATE.speed / CONFIG.maxSpeed, 1);
595
+
596
+ // Pitch modulation
597
+ const targetFreq = baseFreq + (speedRatio * (maxFreq - baseFreq));
598
+ engineOsc.frequency.setTargetAtTime(targetFreq, audioCtx.currentTime, 0.1);
599
+
600
+ // Volume modulation (louder when faster)
601
+ const targetGain = 0.05 + (speedRatio * 0.1);
602
+ engineGain.gain.setTargetAtTime(targetGain, audioCtx.currentTime, 0.1);
603
+ }
604
+
605
+ // Audio Event Listeners
606
+ function playStartSound() {
607
+ if(!audioCtx) return;
608
+ const osc = audioCtx.createOscillator();
609
+ const gain = audioCtx.createGain();
610
+ osc.connect(gain);
611
+ gain.connect(audioCtx.destination);
612
+
613
+ osc.frequency.setValueAtTime(100, audioCtx.currentTime);
614
+ osc.frequency.exponentialRampToValueAtTime(600, audioCtx.currentTime + 1.5);
615
+
616
+ gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
617
+ gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 1.5);
618
+
619
+ osc.start();
620
+ osc.stop(audioCtx.currentTime + 1.5);
621
+ }
622
+
623
+ /**
624
+ * ------------------------------------------------------------------
625
+ * UI INTERACTION
626
+ * ------------------------------------------------------------------
627
+ */
628
+
629
+ // Color Picker
630
+ document.querySelectorAll('.swatch').forEach(swatch => {
631
+ swatch.addEventListener('click', (e) => {
632
+ const colorHex = parseInt(e.target.dataset.color);
633
+ if (car && car.userData.bodyMeshes) {
634
+ car.userData.bodyMeshes.forEach(mesh => {
635
+ // Clone material to not affect others sharing it
636
+ if(mesh.material) {
637
+ mesh.material = mesh.material.clone();
638
+ mesh.material.color.setHex(colorHex);
639
+ }
640
+ });
641
+ }
642
+ // Visual feedback
643
+ document.querySelectorAll('.swatch').forEach(s => s.classList.remove('selected'));
644
+ e.target.classList.add('selected');
645
+ });
646
+ });
647
+
648
+ // Start Button
649
+ document.getElementById('start-btn').addEventListener('click', () => {
650
+ initAudio(); // Initialize Audio Context on user gesture
651
+ playStartSound();
652
+
653
+ // Transition UI
654
+ document.getElementById('showroom-ui').style.opacity = '0';
655
+ document.getElementById('showroom-ui').style.pointerEvents = 'none';
656
+
657
+ const driveUI = document.getElementById('driving-ui');
658
+ driveUI.style.opacity = '1';
659
+ driveUI.style.pointerEvents = 'auto';
660
+
661
+ STATE.mode = 'drive';
662
+
663
+ // Reset Car Position
664
+ if(car) {
665
+ car.position.set(0, 0, 0);
666
+ car.rotation.set(0, Math.PI, 0); // Face forward
667
+ STATE.speed = 0;
668
+ STATE.steering = 0;
669
+ }
670
+ });
671
+
672
+ // Driving Controls
673
+ const btnGas = document.getElementById('btn-gas');
674
+ const btnBrake = document.getElementById('btn-brake');
675
+ const btnLeft = document.getElementById('btn-left');
676
+ const btnRight = document.getElementById('btn-right');
677
+
678
+ function setupTouch(el, key) {
679
+ const start = (e) => { e.preventDefault(); STATE[key] = true; el.classList.add('active-btn'); };
680
+ const end = (e) => { e.preventDefault(); STATE[key] = false; el.classList.remove('active-btn'); };
681
+
682
+ el.addEventListener('mousedown', start);
683
+ el.addEventListener('mouseup', end);
684
+ el.addEventListener('mouseleave', end);
685
+ el.addEventListener('touchstart', start);
686
+ el.addEventListener('touchend', end);
687
+ }
688
+
689
+ setupTouch(btnGas, 'isAccelerating');
690
+ setupTouch(btnBrake, 'isBraking');
691
+ setupTouch(btnLeft, 'isTurningLeft');
692
+ setupTouch(btnRight, 'isTurningRight');
693
+
694
+ // Keyboard controls
695
+ window.addEventListener('keydown', (e) => {
696
+ if(STATE.mode !== 'drive') return;
697
+ if(e.key === 'ArrowUp' || e.key === 'w') STATE.isAccelerating = true;
698
+ if(e.key === 'ArrowDown' || e.key === 's') STATE.isBraking = true;
699
+ if(e.key === 'ArrowLeft' || e.key === 'a') STATE.isTurningLeft = true;
700
+ if(e.key === 'ArrowRight' || e.key === 'd') STATE.isTurningRight = true;
701
+ });
702
+
703
+ window.addEventListener('keyup', (e) => {
704
+ if(e.key === 'ArrowUp' || e.key === 'w') STATE.isAccelerating = false;
705
+ if(e.key === 'ArrowDown' || e.key === 's') STATE.isBraking = false;
706
+ if(e.key === 'ArrowLeft' || e.key === 'a') STATE.isTurningLeft = false;
707
+ if(e.key === 'ArrowRight' || e.key === 'd') STATE.isTurningRight = false;
708
+ });
709
+
710
+
711
+ /**
712
+ * ------------------------------------------------------------------
713
+ * ANIMATION LOOP
714
+ * ------------------------------------------------------------------
715
+ */
716
+ function updatePhysics() {
717
+ if (!car) return;
718
+
719
+ if (STATE.mode === 'showroom') {
720
+ car.rotation.y += CONFIG.showroomRotationSpeed;
721
+ // Floating effect
722
+ car.position.y = Math.sin(Date.now() * 0.002) * 0.1;
723
+ }
724
+ else if (STATE.mode === 'drive') {
725
+ // Acceleration
726
+ if (STATE.isAccelerating) {
727
+ STATE.speed += CONFIG.acceleration;
728
+ } else if (STATE.isBraking) {
729
+ STATE.speed -= CONFIG.deceleration * 2;
730
+ } else {
731
+ // Rolling friction
732
+ STATE.speed -= CONFIG.deceleration * 0.5;
733
+ }
734
+
735
+ // Clamp Speed
736
+ STATE.speed = Math.max(0, Math.min(STATE.speed, CONFIG.maxSpeed));
737
+
738
+ // Steering
739
+ let targetSteering = 0;
740
+ if (STATE.isTurningLeft) targetSteering = 1;
741
+ if (STATE.isTurningRight) targetSteering = -1;
742
+
743
+ // Smooth steering
744
+ STATE.steering += (targetSteering - STATE.steering) * 0.1;
745
+
746
+ // Rotation (only if moving)
747
+ if (STATE.speed > 0.1) {
748
+ car.rotation.y += STATE.steering * CONFIG.turnSpeed * (STATE.speed / 20);
749
+ }
750
+
751
+ // Move Car
752
+ // Forward vector is (0, 0, 1) in local space, transformed to world
753
+ const direction = new THREE.Vector3(0, 0, 1);
754
+ direction.applyAxisAngle(new THREE.Vector3(0, 1, 0), car.rotation.y);
755
+ car.position.add(direction.multiplyScalar(STATE.speed * 0.01));
756
+
757
+ // Camera Follow
758
+ const relativeOffset = new THREE.Vector3(0, 4, -8); // Behind and up
759
+
760
+ // Calculate ideal camera position based on car rotation
761
+ const cameraOffset = relativeOffset.applyMatrix4(car.matrixWorld);
762
+
763
+ // Smoothly interpolate camera position
764
+ camera.position.lerp(cameraOffset, 0.1);
765
+ camera.lookAt(car.position);
766
+
767
+ // Update UI
768
+ document.getElementById('speed-display').innerText = Math.floor(STATE.speed);
769
+
770
+ // Update Audio
771
+ updateEngineSound();
772
+
773
+ // Infinite Road/Environment Logic (Teleport objects to create infinite illusion)
774
+ // Instead of moving world, we check if poles are too far behind car and move them ahead
775
+ poles.forEach(pole => {
776
+ if (pole.position.z < car.position.z - 20) {
777
+ pole.position.z += 100; // Move ahead by total loop length
778
+ }
779
+ });
780
+ // Texture offset for road movement effect
781
+ if (roadMat.map) {
782
+ roadMat.map.offset.y -= (STATE.speed * 0.001);
783
+ }
784
+ }
785
+ }
786
+
787
+ function animate() {
788
+ requestAnimationFrame(animate);
789
+ updatePhysics();
790
+ renderer.render(scene, camera);
791
+ }
792
+
793
+ // Handle Window Resize
794
+ window.addEventListener('resize', () => {
795
+ camera.aspect = window.innerWidth / window.innerHeight;
796
+ camera.updateProjectionMatrix();
797
+ renderer.setSize(window.innerWidth, window.innerHeight);
798
+ });
799
+
800
+ // Start Loop
801
+ animate();
802
+
803
+ </script>
804
+ </body>
805
+ </html>