ParthSadaria commited on
Commit
6b4f469
·
verified ·
1 Parent(s): 0b3619c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1025 -232
index.html CHANGED
@@ -13,9 +13,6 @@
13
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet">
15
 
16
- <!-- Three.js for 3D elements -->
17
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
18
-
19
  <style>
20
  :root {
21
  --bg-color: #050505;
@@ -87,24 +84,16 @@
87
  width: 90%;
88
  animation: slideInUp 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.5s forwards;
89
  opacity: 0;
90
- transform: translateY(30px) rotateX(5deg);
91
- transform-style: preserve-3d;
92
  }
93
 
94
  @keyframes slideInUp {
95
  to {
96
  opacity: 1;
97
- transform: translateY(0) rotateX(0);
98
  }
99
  }
100
 
101
- .title-container {
102
- position: relative;
103
- display: inline-block;
104
- transform-style: preserve-3d;
105
- margin-bottom: 1.5rem;
106
- }
107
-
108
  h1 {
109
  font-size: clamp(3rem, 10vw, 6rem);
110
  font-weight: 700;
@@ -113,8 +102,6 @@
113
  color: var(--text-color);
114
  text-shadow: 0 0 15px rgba(255, 255, 255, 0.1);
115
  transition: color var(--transition-speed) ease, text-shadow var(--transition-speed) ease;
116
- transform-style: preserve-3d;
117
- transform: translateZ(0);
118
  }
119
 
120
  .subtitle {
@@ -124,16 +111,12 @@
124
  margin-bottom: 2.5rem;
125
  letter-spacing: 0.05em;
126
  transition: color var(--transition-speed) ease;
127
- transform-style: preserve-3d;
128
- transform: translateZ(5px);
129
  }
130
 
131
  .models-section {
132
  margin-bottom: 3rem;
133
  opacity: 0;
134
  animation: fadeIn 1s ease 1s forwards;
135
- transform-style: preserve-3d;
136
- transform: translateZ(10px);
137
  }
138
 
139
  .models-section h2 {
@@ -151,7 +134,6 @@
151
  flex-wrap: wrap;
152
  justify-content: center;
153
  gap: 0.8rem;
154
- perspective: 1000px;
155
  }
156
 
157
  .model-badge {
@@ -164,17 +146,15 @@
164
  border-radius: 15px;
165
  border: 1px solid var(--border-color);
166
  transition: all var(--transition-speed) cubic-bezier(0.25, 0.46, 0.45, 0.94);
167
- transform: scale(1) translateZ(0);
168
- transform-style: preserve-3d;
169
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
170
  }
171
 
172
  .model-badge:hover {
173
  background-color: var(--hover-bg);
174
  color: var(--accent-color);
175
  border-color: var(--accent-color);
176
- transform: scale(1.05) translateY(-2px) translateZ(10px) rotateX(5deg);
177
- box-shadow: 0 10px 25px rgba(0, 255, 255, 0.2), 0 2px 5px rgba(0, 0, 0, 0.2);
178
  }
179
 
180
  .playgrounds-section {
@@ -184,8 +164,6 @@
184
  flex-direction: column; /* Stack links vertically on mobile */
185
  align-items: center;
186
  gap: 1rem;
187
- transform-style: preserve-3d;
188
- transform: translateZ(15px);
189
  }
190
 
191
  @media (min-width: 600px) {
@@ -209,8 +187,6 @@
209
  overflow: hidden;
210
  transition: all var(--transition-speed) ease;
211
  z-index: 1;
212
- transform-style: preserve-3d;
213
- transform: translateZ(0);
214
  }
215
 
216
  .playground-link::before {
@@ -228,8 +204,8 @@
228
  .playground-link:hover {
229
  color: var(--bg-color);
230
  border-color: var(--accent-color);
231
- transform: translateY(-3px) translateZ(20px) rotateX(10deg);
232
- box-shadow: 0 15px 30px rgba(0, 255, 255, 0.3), 0 5px 10px rgba(0, 0, 0, 0.2);
233
  }
234
 
235
  .playground-link:hover::before {
@@ -309,20 +285,89 @@
309
  to { opacity: 1; }
310
  }
311
 
312
- /* 3D Glass Orb */
313
- #orb-container {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  position: absolute;
315
- top: -80px;
316
- left: 50%;
317
- transform: translateX(-50%);
318
- width: 120px;
319
- height: 120px;
 
 
 
 
 
 
 
 
 
 
320
  z-index: 3;
321
- pointer-events: none;
322
  }
323
 
324
- /* Loading Animation Container */
325
- #loading-container {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  position: fixed;
327
  top: 0;
328
  left: 0;
@@ -332,108 +377,152 @@
332
  display: flex;
333
  justify-content: center;
334
  align-items: center;
335
- z-index: 9999;
336
- transition: opacity 0.8s ease;
 
337
  }
338
 
339
- .loading-spinner {
340
  width: 100px;
341
  height: 100px;
342
  position: relative;
343
- perspective: 1000px;
 
344
  }
345
 
346
- .spinner-cube {
347
- width: 50px;
348
- height: 50px;
349
- position: absolute;
350
- top: 25px;
351
- left: 25px;
352
- transform-style: preserve-3d;
353
- animation: spinner-rotate 3s infinite linear;
354
  }
355
 
356
- .spinner-face {
357
  position: absolute;
358
- width: 100%;
359
- height: 100%;
360
- background-color: rgba(0, 255, 255, 0.1);
361
- border: 1px solid var(--accent-color);
362
  display: flex;
363
  justify-content: center;
364
  align-items: center;
365
- box-shadow: 0 0 10px var(--glow-color);
 
 
 
 
366
  }
367
 
368
- .spinner-face:nth-child(1) { transform: translateZ(25px); }
369
- .spinner-face:nth-child(2) { transform: rotateY(90deg) translateZ(25px); }
370
- .spinner-face:nth-child(3) { transform: rotateY(180deg) translateZ(25px); }
371
- .spinner-face:nth-child(4) { transform: rotateY(270deg) translateZ(25px); }
372
- .spinner-face:nth-child(5) { transform: rotateX(90deg) translateZ(25px); }
373
- .spinner-face:nth-child(6) { transform: rotateX(-90deg) translateZ(25px); }
374
 
375
- @keyframes spinner-rotate {
376
- 0% { transform: rotateX(0deg) rotateY(0deg); }
377
- 100% { transform: rotateX(360deg) rotateY(360deg); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  }
379
 
380
- /* Responsive adjustments */
381
- @media (max-width: 768px) {
382
- h1 {
383
- letter-spacing: -0.03em;
384
- }
385
- .container {
386
- padding: 1.5rem;
387
- }
388
- .models-grid {
389
- gap: 0.6rem;
390
- }
391
- .playgrounds-section {
392
- gap: 0.8rem;
393
- }
394
- .playground-link {
395
- padding: 0.7em 1.5em;
396
- }
397
- /* Reduce glow size on smaller screens */
398
- .glow {
399
- width: 500px;
400
- height: 500px;
401
- }
402
- #orb-container {
403
- top: -60px;
404
- width: 90px;
405
- height: 90px;
406
- }
407
  }
408
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  </style>
410
  </head>
411
  <body>
412
  <!-- Loading Animation -->
413
- <div id="loading-container">
414
- <div class="loading-spinner">
415
- <div class="spinner-cube">
416
- <div class="spinner-face"></div>
417
- <div class="spinner-face"></div>
418
- <div class="spinner-face"></div>
419
- <div class="spinner-face"></div>
420
- <div class="spinner-face"></div>
421
- <div class="spinner-face"></div>
422
- </div>
423
  </div>
424
  </div>
425
 
 
 
 
426
  <!-- Mouse Effect Elements -->
427
  <div class="cursor"></div>
428
  <div class="glow"></div>
429
 
430
  <!-- Main Content -->
431
  <div class="container">
432
- <div class="title-container">
433
- <!-- 3D Glass Orb Container -->
434
- <div id="orb-container"></div>
435
- <h1>Loki.AI</h1>
436
- </div>
437
  <p class="subtitle">By Parth Sadaria</p>
438
 
439
  <div class="models-section">
@@ -462,13 +551,25 @@
462
  </div>
463
  </div>
464
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  <script>
 
466
  const cursor = document.querySelector('.cursor');
467
  const glow = document.querySelector('.glow');
468
- const hoverables = document.querySelectorAll('a, .model-badge');
469
- const textElements = document.querySelectorAll('h1, p, h2, .model-badge'); // Include badges for text hover
470
- const loadingContainer = document.getElementById('loading-container');
471
- const orbContainer = document.getElementById('orb-container');
472
 
473
  let mouseX = 0;
474
  let mouseY = 0;
@@ -488,11 +589,8 @@
488
  const hoverTextCursorScale = 0.7;
489
  const clickCursorScale = 0.6;
490
 
491
- // Parallax effect variables
492
- let windowWidth = window.innerWidth;
493
- let windowHeight = window.innerHeight;
494
-
495
  // Pre-calculate half-widths/heights ONCE (assuming they don't change)
 
496
  let cursorHalfWidth = cursor.offsetWidth / 2;
497
  let cursorHalfHeight = cursor.offsetHeight / 2;
498
  let glowHalfWidth = glow.offsetWidth / 2;
@@ -504,74 +602,8 @@
504
  cursorHalfHeight = cursor.offsetHeight / 2;
505
  glowHalfWidth = glow.offsetWidth / 2;
506
  glowHalfHeight = glow.offsetHeight / 2;
507
- windowWidth = window.innerWidth;
508
- windowHeight = window.innerHeight;
509
  });
510
 
511
- // Initialize Three.js for the glass orb
512
- let orbScene, orbCamera, orbRenderer, orbSphere, orbLight;
513
-
514
- function initOrb() {
515
- orbScene = new THREE.Scene();
516
-
517
- // Perspective camera
518
- orbCamera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
519
- orbCamera.position.z = 2;
520
-
521
- // Renderer with transparency
522
- orbRenderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
523
- orbRenderer.setSize(orbContainer.offsetWidth, orbContainer.offsetHeight);
524
- orbRenderer.setPixelRatio(window.devicePixelRatio);
525
- orbContainer.appendChild(orbRenderer.domElement);
526
-
527
- // Create a glass sphere
528
- const sphereGeometry = new THREE.SphereGeometry(0.8, 64, 64);
529
- const sphereMaterial = new THREE.MeshPhysicalMaterial({
530
- color: 0x00ffff,
531
- transparent: true,
532
- opacity: 0.2,
533
- metalness: 0.2,
534
- roughness: 0.05,
535
- transmission: 0.98,
536
- clearcoat: 1.0,
537
- clearcoatRoughness: 0.1,
538
- side: THREE.DoubleSide
539
- });
540
-
541
- orbSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
542
- orbScene.add(orbSphere);
543
-
544
- // Add ambient light
545
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
546
- orbScene.add(ambientLight);
547
-
548
- // Add point light
549
- orbLight = new THREE.PointLight(0x00ffff, 2, 10);
550
- orbLight.position.set(2, 2, 2);
551
- orbScene.add(orbLight);
552
-
553
- // Add directional light
554
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
555
- directionalLight.position.set(-1, 1, 1);
556
- orbScene.add(directionalLight);
557
- }
558
-
559
- function animateOrb() {
560
- requestAnimationFrame(animateOrb);
561
-
562
- // Rotate the sphere
563
- orbSphere.rotation.x += 0.005;
564
- orbSphere.rotation.y += 0.01;
565
-
566
- // Make light move in a circle
567
- const time = Date.now() * 0.001;
568
- orbLight.position.x = Math.sin(time) * 2;
569
- orbLight.position.z = Math.cos(time) * 2;
570
-
571
- // Render the scene
572
- orbRenderer.render(orbScene, orbCamera);
573
- }
574
-
575
  function animate() {
576
  // Lerp for smooth following
577
  cursorX += (mouseX - cursorX) * cursorSpeed;
@@ -587,27 +619,7 @@
587
  const glowTranslateY = glowY - glowHalfHeight;
588
 
589
  cursor.style.transform = `translate(${cursorTranslateX}px, ${cursorTranslateY}px) scale(${currentCursorScale})`;
590
- glow.style.transform = `translate(${glowTranslateX}px, ${glowTranslateY}px)`; // Glow doesn't scale dynamically here
591
-
592
- // 3D parallax effect for elements based on mouse position
593
- const containerElements = document.querySelectorAll('.container > div, .title-container > h1');
594
- containerElements.forEach((element, index) => {
595
- const depth = 15 + (index * 3); // Different depths for different elements
596
- const moveX = (mouseX - windowWidth / 2) / windowWidth * depth;
597
- const moveY = (mouseY - windowHeight / 2) / windowHeight * depth;
598
- element.style.transform = `translate3d(${moveX}px, ${moveY}px, ${index * 5}px)`;
599
- });
600
-
601
- // Badge parallax effect
602
- const badges = document.querySelectorAll('.model-badge');
603
- badges.forEach((badge, index) => {
604
- const badgeDepth = 5;
605
- const offsetX = (mouseX - windowWidth / 2) / windowWidth * badgeDepth;
606
- const offsetY = (mouseY - windowHeight / 2) / windowHeight * badgeDepth;
607
- const rotateX = (mouseY - windowHeight / 2) / windowHeight * 5;
608
- const rotateY = -(mouseX - windowWidth / 2) / windowWidth * 5;
609
- badge.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
610
- });
611
 
612
  requestAnimationFrame(animate);
613
  }
@@ -625,7 +637,7 @@
625
  let isHoveringLink = false;
626
  let isHoveringText = false;
627
 
628
- // Check hover states directly (more robust than relying on mouseenter/leave timing with lerp)
629
  hoverables.forEach(el => {
630
  const rect = el.getBoundingClientRect();
631
  if (mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom) {
@@ -634,7 +646,6 @@
634
  });
635
 
636
  textElements.forEach(el => {
637
- // Avoid text hover if already hovering a link defined in hoverables
638
  if (!isHoveringLink) {
639
  const rect = el.getBoundingClientRect();
640
  if (mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom) {
@@ -643,9 +654,8 @@
643
  }
644
  });
645
 
646
- // Apply styles and target scale based on state
647
  cursor.classList.toggle('hover-link', isHoveringLink);
648
- cursor.classList.toggle('hover-text', isHoveringText && !isHoveringLink);
649
 
650
  if (isHoveringLink) {
651
  targetScale = hoverLinkCursorScale;
@@ -653,53 +663,836 @@
653
  targetScale = hoverTextCursorScale;
654
  }
655
 
656
- // Handle clicking state separately
657
  if (cursor.classList.contains('clicking')) {
658
- targetScale = clickCursorScale;
659
  }
660
 
661
- // Set the target scale
662
  currentCursorScale = targetScale;
663
  }
664
 
665
- // Update state on mouse move for accuracy
666
  document.addEventListener('mousemove', updateCursorState);
667
 
668
- // Add/Remove clicking class
669
  document.addEventListener('mousedown', () => {
670
  cursor.classList.add('clicking');
671
- updateCursorState();
672
  });
673
 
674
  document.addEventListener('mouseup', () => {
675
  cursor.classList.remove('clicking');
676
- updateCursorState();
677
  });
678
 
679
- // Initial state check
680
  updateCursorState();
681
 
682
- // Initialize the 3D orb
683
- initOrb();
684
- animateOrb();
685
-
686
- // Hide loading screen after everything is loaded
687
  window.addEventListener('load', () => {
 
688
  setTimeout(() => {
689
- loadingContainer.style.opacity = '0';
690
  setTimeout(() => {
691
- loadingContainer.style.display = 'none';
692
- }, 800);
693
- }, 1500); // Show loading for at least 1.5 seconds
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
694
  });
695
 
696
- // If loading takes too long, hide it anyway
697
- setTimeout(() => {
698
- loadingContainer.style.opacity = '0';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699
  setTimeout(() => {
700
- loadingContainer.style.display = 'none';
701
- }, 800);
702
- }, 3000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  </script>
704
  </body>
705
  </html>
 
13
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet">
15
 
 
 
 
16
  <style>
17
  :root {
18
  --bg-color: #050505;
 
84
  width: 90%;
85
  animation: slideInUp 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.5s forwards;
86
  opacity: 0;
87
+ transform: translateY(30px);
 
88
  }
89
 
90
  @keyframes slideInUp {
91
  to {
92
  opacity: 1;
93
+ transform: translateY(0);
94
  }
95
  }
96
 
 
 
 
 
 
 
 
97
  h1 {
98
  font-size: clamp(3rem, 10vw, 6rem);
99
  font-weight: 700;
 
102
  color: var(--text-color);
103
  text-shadow: 0 0 15px rgba(255, 255, 255, 0.1);
104
  transition: color var(--transition-speed) ease, text-shadow var(--transition-speed) ease;
 
 
105
  }
106
 
107
  .subtitle {
 
111
  margin-bottom: 2.5rem;
112
  letter-spacing: 0.05em;
113
  transition: color var(--transition-speed) ease;
 
 
114
  }
115
 
116
  .models-section {
117
  margin-bottom: 3rem;
118
  opacity: 0;
119
  animation: fadeIn 1s ease 1s forwards;
 
 
120
  }
121
 
122
  .models-section h2 {
 
134
  flex-wrap: wrap;
135
  justify-content: center;
136
  gap: 0.8rem;
 
137
  }
138
 
139
  .model-badge {
 
146
  border-radius: 15px;
147
  border: 1px solid var(--border-color);
148
  transition: all var(--transition-speed) cubic-bezier(0.25, 0.46, 0.45, 0.94);
149
+ transform: scale(1);
 
 
150
  }
151
 
152
  .model-badge:hover {
153
  background-color: var(--hover-bg);
154
  color: var(--accent-color);
155
  border-color: var(--accent-color);
156
+ transform: scale(1.05) translateY(-2px);
157
+ box-shadow: 0 4px 15px rgba(0, 255, 255, 0.2);
158
  }
159
 
160
  .playgrounds-section {
 
164
  flex-direction: column; /* Stack links vertically on mobile */
165
  align-items: center;
166
  gap: 1rem;
 
 
167
  }
168
 
169
  @media (min-width: 600px) {
 
187
  overflow: hidden;
188
  transition: all var(--transition-speed) ease;
189
  z-index: 1;
 
 
190
  }
191
 
192
  .playground-link::before {
 
204
  .playground-link:hover {
205
  color: var(--bg-color);
206
  border-color: var(--accent-color);
207
+ transform: translateY(-3px);
208
+ box-shadow: 0 5px 20px rgba(0, 255, 255, 0.3);
209
  }
210
 
211
  .playground-link:hover::before {
 
285
  to { opacity: 1; }
286
  }
287
 
288
+ /* Responsive adjustments */
289
+ @media (max-width: 768px) {
290
+ h1 {
291
+ letter-spacing: -0.03em;
292
+ }
293
+ .container {
294
+ padding: 1.5rem;
295
+ }
296
+ .models-grid {
297
+ gap: 0.6rem;
298
+ }
299
+ .playgrounds-section {
300
+ gap: 0.8rem;
301
+ }
302
+ .playground-link {
303
+ padding: 0.7em 1.5em;
304
+ }
305
+ /* Reduce glow size on smaller screens */
306
+ .glow {
307
+ width: 500px;
308
+ height: 500px;
309
+ }
310
+ }
311
+
312
+ /* 3D Glass Ball Style */
313
+ .glass-ball {
314
  position: absolute;
315
+ top: 40px;
316
+ right: 40px;
317
+ width: 150px;
318
+ height: 150px;
319
+ border-radius: 50%;
320
+ background: radial-gradient(circle at 30% 30%,
321
+ rgba(255, 255, 255, 0.2) 0%,
322
+ rgba(0, 255, 255, 0.1) 40%,
323
+ rgba(0, 0, 0, 0.1) 100%);
324
+ box-shadow:
325
+ inset 0 0 20px rgba(0, 255, 255, 0.3),
326
+ 0 0 30px rgba(0, 255, 255, 0.2);
327
+ animation: float 6s infinite ease-in-out;
328
+ transform-style: preserve-3d;
329
+ perspective: 1000px;
330
  z-index: 3;
 
331
  }
332
 
333
+ .glass-ball::before {
334
+ content: '';
335
+ position: absolute;
336
+ top: 15%;
337
+ left: 15%;
338
+ width: 20%;
339
+ height: 20%;
340
+ border-radius: 50%;
341
+ background: rgba(255, 255, 255, 0.6);
342
+ filter: blur(2px);
343
+ }
344
+
345
+ .glass-ball::after {
346
+ content: '';
347
+ position: absolute;
348
+ top: 45%;
349
+ left: 45%;
350
+ width: 70%;
351
+ height: 70%;
352
+ border-radius: 50%;
353
+ background: radial-gradient(circle at center,
354
+ rgba(0, 255, 255, 0.05) 0%,
355
+ rgba(0, 0, 0, 0) 70%);
356
+ animation: pulse 4s infinite alternate;
357
+ }
358
+
359
+ @keyframes float {
360
+ 0%, 100% { transform: translateY(0) rotate(0deg); }
361
+ 50% { transform: translateY(-20px) rotate(10deg); }
362
+ }
363
+
364
+ @keyframes pulse {
365
+ 0% { opacity: 0.2; transform: scale(0.8); }
366
+ 100% { opacity: 0.4; transform: scale(1.1); }
367
+ }
368
+
369
+ /* Loading Animation */
370
+ .loading-container {
371
  position: fixed;
372
  top: 0;
373
  left: 0;
 
377
  display: flex;
378
  justify-content: center;
379
  align-items: center;
380
+ z-index: 10000;
381
+ perspective: 1000px;
382
+ transition: opacity 0.5s ease-out;
383
  }
384
 
385
+ .loading-cube {
386
  width: 100px;
387
  height: 100px;
388
  position: relative;
389
+ transform-style: preserve-3d;
390
+ animation: spin 4s infinite linear;
391
  }
392
 
393
+ @keyframes spin {
394
+ 0% { transform: rotateX(0deg) rotateY(0deg); }
395
+ 100% { transform: rotateX(360deg) rotateY(360deg); }
 
 
 
 
 
396
  }
397
 
398
+ .cube-face {
399
  position: absolute;
400
+ width: 100px;
401
+ height: 100px;
402
+ background: rgba(0, 255, 255, 0.1);
403
+ border: 2px solid var(--accent-color);
404
  display: flex;
405
  justify-content: center;
406
  align-items: center;
407
+ font-weight: bold;
408
+ color: var(--accent-color);
409
+ text-shadow: 0 0 10px var(--accent-color);
410
+ box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
411
+ backface-visibility: visible;
412
  }
413
 
414
+ .front { transform: translateZ(50px); }
415
+ .back { transform: translateZ(-50px) rotateY(180deg); }
416
+ .right { transform: translateX(50px) rotateY(90deg); }
417
+ .left { transform: translateX(-50px) rotateY(-90deg); }
418
+ .top { transform: translateY(-50px) rotateX(90deg); }
419
+ .bottom { transform: translateY(50px) rotateX(-90deg); }
420
 
421
+ /* Game Related Styles */
422
+ .game-toggle {
423
+ position: fixed;
424
+ bottom: 20px;
425
+ right: 20px;
426
+ background-color: var(--accent-color);
427
+ color: var(--bg-color);
428
+ border: none;
429
+ border-radius: 50%;
430
+ width: 60px;
431
+ height: 60px;
432
+ font-size: 24px;
433
+ display: flex;
434
+ justify-content: center;
435
+ align-items: center;
436
+ cursor: pointer;
437
+ z-index: 1000;
438
+ box-shadow: 0 0 15px rgba(0, 255, 255, 0.5);
439
+ transition: all 0.3s ease;
440
  }
441
 
442
+ .game-toggle:hover {
443
+ transform: scale(1.1);
444
+ box-shadow: 0 0 25px rgba(0, 255, 255, 0.7);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  }
446
 
447
+ #game-container {
448
+ position: fixed;
449
+ top: 0;
450
+ left: 0;
451
+ width: 100%;
452
+ height: 100%;
453
+ background-color: rgba(0, 0, 0, 0.9);
454
+ z-index: 5000;
455
+ display: none;
456
+ overflow: hidden;
457
+ }
458
+
459
+ #game-canvas {
460
+ width: 100%;
461
+ height: 100%;
462
+ display: block;
463
+ }
464
+
465
+ .controls-info {
466
+ position: absolute;
467
+ bottom: 20px;
468
+ left: 20px;
469
+ color: var(--text-color);
470
+ background-color: rgba(0, 0, 0, 0.7);
471
+ padding: 10px 15px;
472
+ border-radius: 5px;
473
+ font-size: 14px;
474
+ z-index: 5001;
475
+ }
476
+
477
+ .score-display {
478
+ position: absolute;
479
+ top: 20px;
480
+ left: 20px;
481
+ color: var(--accent-color);
482
+ font-size: 24px;
483
+ font-weight: bold;
484
+ z-index: 5001;
485
+ text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
486
+ }
487
+
488
+ .game-message {
489
+ position: absolute;
490
+ top: 50%;
491
+ left: 50%;
492
+ transform: translate(-50%, -50%);
493
+ color: var(--accent-color);
494
+ font-size: 40px;
495
+ font-weight: bold;
496
+ text-align: center;
497
+ z-index: 5002;
498
+ text-shadow: 0 0 20px rgba(0, 255, 255, 0.7);
499
+ display: none;
500
+ }
501
  </style>
502
  </head>
503
  <body>
504
  <!-- Loading Animation -->
505
+ <div class="loading-container" id="loading-screen">
506
+ <div class="loading-cube">
507
+ <div class="cube-face front">LOKI.AI</div>
508
+ <div class="cube-face back">LOADING</div>
509
+ <div class="cube-face right">AI</div>
510
+ <div class="cube-face left">MODELS</div>
511
+ <div class="cube-face top">PREMIUM</div>
512
+ <div class="cube-face bottom">FREE</div>
 
 
513
  </div>
514
  </div>
515
 
516
+ <!-- 3D Glass Ball -->
517
+ <div class="glass-ball"></div>
518
+
519
  <!-- Mouse Effect Elements -->
520
  <div class="cursor"></div>
521
  <div class="glow"></div>
522
 
523
  <!-- Main Content -->
524
  <div class="container">
525
+ <h1>Loki.AI</h1>
 
 
 
 
526
  <p class="subtitle">By Parth Sadaria</p>
527
 
528
  <div class="models-section">
 
551
  </div>
552
  </div>
553
 
554
+ <!-- Car Game Toggle Button -->
555
+ <button class="game-toggle" id="game-toggle">🏎️</button>
556
+
557
+ <!-- Game Container -->
558
+ <div id="game-container">
559
+ <canvas id="game-canvas"></canvas>
560
+ <div class="controls-info">
561
+ WASD or Arrow Keys to drive | SPACE for handbrake drift | ESC to exit
562
+ </div>
563
+ <div class="score-display" id="score-display">Score: 0</div>
564
+ <div class="game-message" id="game-message"></div>
565
+ </div>
566
+
567
  <script>
568
+ // Mouse cursor effects
569
  const cursor = document.querySelector('.cursor');
570
  const glow = document.querySelector('.glow');
571
+ const hoverables = document.querySelectorAll('a, .model-badge, .game-toggle');
572
+ const textElements = document.querySelectorAll('h1, p, h2, .model-badge');
 
 
573
 
574
  let mouseX = 0;
575
  let mouseY = 0;
 
589
  const hoverTextCursorScale = 0.7;
590
  const clickCursorScale = 0.6;
591
 
 
 
 
 
592
  // Pre-calculate half-widths/heights ONCE (assuming they don't change)
593
+ // Note: If elements resize dynamically, might need recalculation
594
  let cursorHalfWidth = cursor.offsetWidth / 2;
595
  let cursorHalfHeight = cursor.offsetHeight / 2;
596
  let glowHalfWidth = glow.offsetWidth / 2;
 
602
  cursorHalfHeight = cursor.offsetHeight / 2;
603
  glowHalfWidth = glow.offsetWidth / 2;
604
  glowHalfHeight = glow.offsetHeight / 2;
 
 
605
  });
606
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  function animate() {
608
  // Lerp for smooth following
609
  cursorX += (mouseX - cursorX) * cursorSpeed;
 
619
  const glowTranslateY = glowY - glowHalfHeight;
620
 
621
  cursor.style.transform = `translate(${cursorTranslateX}px, ${cursorTranslateY}px) scale(${currentCursorScale})`;
622
+ glow.style.transform = `translate(${glowTranslateX}px, ${glowTranslateY}px)`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
 
624
  requestAnimationFrame(animate);
625
  }
 
637
  let isHoveringLink = false;
638
  let isHoveringText = false;
639
 
640
+ // Check hover states directly
641
  hoverables.forEach(el => {
642
  const rect = el.getBoundingClientRect();
643
  if (mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom) {
 
646
  });
647
 
648
  textElements.forEach(el => {
 
649
  if (!isHoveringLink) {
650
  const rect = el.getBoundingClientRect();
651
  if (mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom) {
 
654
  }
655
  });
656
 
 
657
  cursor.classList.toggle('hover-link', isHoveringLink);
658
+ cursor.classList.toggle('hover-text', isHoveringText && !isHoveringLink);
659
 
660
  if (isHoveringLink) {
661
  targetScale = hoverLinkCursorScale;
 
663
  targetScale = hoverTextCursorScale;
664
  }
665
 
 
666
  if (cursor.classList.contains('clicking')) {
667
+ targetScale = clickCursorScale;
668
  }
669
 
 
670
  currentCursorScale = targetScale;
671
  }
672
 
 
673
  document.addEventListener('mousemove', updateCursorState);
674
 
 
675
  document.addEventListener('mousedown', () => {
676
  cursor.classList.add('clicking');
677
+ updateCursorState();
678
  });
679
 
680
  document.addEventListener('mouseup', () => {
681
  cursor.classList.remove('clicking');
682
+ updateCursorState();
683
  });
684
 
 
685
  updateCursorState();
686
 
687
+ // Loading screen animation
 
 
 
 
688
  window.addEventListener('load', () => {
689
+ const loadingScreen = document.getElementById('loading-screen');
690
  setTimeout(() => {
691
+ loadingScreen.style.opacity = '0';
692
  setTimeout(() => {
693
+ loadingScreen.style.display = 'none';
694
+ }, 500);
695
+ }, 2000);
696
+ });
697
+
698
+ // Game logic
699
+ const gameToggle = document.getElementById('game-toggle');
700
+ const gameContainer = document.getElementById('game-container');
701
+ const canvas = document.getElementById('game-canvas');
702
+ const ctx = canvas.getContext('2d');
703
+ const scoreDisplay = document.getElementById('score-display');
704
+ const gameMessage = document.getElementById('game-message');
705
+
706
+ // Game state
707
+ let gameActive = false;
708
+ let score = 0;
709
+ let obstaclesHit = 0;
710
+ let lastTimestamp = 0;
711
+ let deltaTime = 0;
712
+
713
+ // Canvas sizing
714
+ function resizeCanvas() {
715
+ canvas.width = window.innerWidth;
716
+ canvas.height = window.innerHeight;
717
+ }
718
+
719
+ window.addEventListener('resize', resizeCanvas);
720
+ resizeCanvas();
721
+
722
+ // Toggle game display
723
+ gameToggle.addEventListener('click', () => {
724
+ if (gameContainer.style.display === 'block') {
725
+ gameContainer.style.display = 'none';
726
+ gameActive = false;
727
+ } else {
728
+ gameContainer.style.display = 'block';
729
+ gameActive = true;
730
+ resetGame();
731
+ gameLoop(performance.now());
732
+ }
733
  });
734
 
735
+ // Key handling for the game
736
+ const keys = {
737
+ up: false,
738
+ down: false,
739
+ left: false,
740
+ right: false,
741
+ handbrake: false
742
+ };
743
+
744
+ window.addEventListener('keydown', (e) => {
745
+ if (!gameActive) return;
746
+
747
+ switch(e.key) {
748
+ case 'ArrowUp':
749
+ case 'w':
750
+ case 'W':
751
+ keys.up = true;
752
+ break;
753
+ case 'ArrowDown':
754
+ case 's':
755
+ case 'S':
756
+ keys.down = true;
757
+ break;
758
+ case 'ArrowLeft':
759
+ case 'a':
760
+ case 'A':
761
+ keys.left = true;
762
+ break;
763
+ case 'ArrowRight':
764
+ case 'd':
765
+ case 'D':
766
+ keys.right = true;
767
+ break;
768
+ case ' ':
769
+ keys.handbrake = true;
770
+ break;
771
+ case 'Escape':
772
+ gameContainer.style.display = 'none';
773
+ gameActive = false;
774
+ break;
775
+ }
776
+ });
777
+
778
+ window.addEventListener('keyup', (e) => {
779
+ switch(e.key) {
780
+ case 'ArrowUp':
781
+ case 'w':
782
+ case 'W':
783
+ keys.up = false;
784
+ break;
785
+ case 'ArrowDown':
786
+ case 's':
787
+ case 'S':
788
+ keys.down = false;
789
+ break;
790
+ case 'ArrowLeft':
791
+ case 'a':
792
+ case 'A':
793
+ keys.left = false;
794
+ break;
795
+ case 'ArrowRight':
796
+ case 'd':
797
+ case 'D':
798
+ keys.right = false;
799
+ break;
800
+ case ' ':
801
+ keys.handbrake = false;
802
+ break;
803
+ }
804
+ });
805
+
806
+ // Car properties
807
+ const car = {
808
+ x: 0,
809
+ y: 0,
810
+ width: 40,
811
+ height: 70,
812
+ angle: 0,
813
+ speed: 0,
814
+ acceleration: 200,
815
+ deceleration: 120,
816
+ maxSpeed: 500,
817
+ reverseMaxSpeed: -200,
818
+ turnSpeed: 2.5,
819
+ driftFactor: 0.95,
820
+ friction: 0.97,
821
+ brakeStrength: 0.85,
822
+ drifting: false,
823
+ tireMarks: [],
824
+ color: '#00ffff',
825
+ shadowColor: 'rgba(0, 255, 255, 0.5)',
826
+ turboMode: false,
827
+ turboTimer: 0,
828
+ turboMaxTime: 3,
829
+ turboCooldown: 0,
830
+ turboCooldownMax: 5
831
+ };
832
+
833
+ // Obstacles based on website elements
834
+ const obstacleTypes = [
835
+ { text: "GPT-4o", width: 100, height: 40, color: "#ff5a5a" },
836
+ { text: "Claude 3.7", width: 120, height: 40, color: "#5a92ff" },
837
+ { text: "Gemini", width: 90, height: 40, color: "#5aff7f" },
838
+ { text: "Loki.AI", width: 120, height: 50, color: "#ffaa5a" },
839
+ { text: "AI", width: 60, height: 40, color: "#aa5aff" },
840
+ { text: "Premium", width: 110, height: 40, color: "#ff5aaa" }
841
+ ];
842
+
843
+ const obstacles = [];
844
+ const maxObstacles = 30;
845
+
846
+ const powerUps = [];
847
+ const maxPowerUps = 5;
848
+
849
+ function createObstacles() {
850
+ const margin = 100; // Keep obstacles away from edges
851
+
852
+ while (obstacles.length < maxObstacles) {
853
+ const type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];
854
+ // Continuing from where the code left off in the createObstacles function:
855
+
856
+ const obstacle = {
857
+ x: Math.random() * (canvas.width - type.width - 2 * margin) + margin,
858
+ y: Math.random() * (canvas.height - type.height - 2 * margin) + margin,
859
+ width: type.width,
860
+ height: type.height,
861
+ text: type.text,
862
+ color: type.color,
863
+ vx: 0,
864
+ vy: 0,
865
+ angle: Math.random() * Math.PI * 2,
866
+ angularVelocity: 0,
867
+ mass: type.width * type.height / 1000
868
+ };
869
+
870
+ // Ensure obstacles don't overlap
871
+ let overlaps = false;
872
+ for (const existing of obstacles) {
873
+ const dx = obstacle.x - existing.x;
874
+ const dy = obstacle.y - existing.y;
875
+ const distance = Math.sqrt(dx * dx + dy * dy);
876
+ if (distance < obstacle.width + existing.width) {
877
+ overlaps = true;
878
+ break;
879
+ }
880
+ }
881
+
882
+ if (!overlaps) {
883
+ obstacles.push(obstacle);
884
+ }
885
+ }
886
+ }
887
+
888
+ function createPowerUps() {
889
+ while (powerUps.length < maxPowerUps) {
890
+ const powerUp = {
891
+ x: Math.random() * (canvas.width - 30),
892
+ y: Math.random() * (canvas.height - 30),
893
+ radius: 15,
894
+ type: Math.random() < 0.3 ? 'turbo' : 'score',
895
+ color: Math.random() < 0.3 ? '#ff00ff' : '#ffff00',
896
+ collectEffect: Math.random() < 0.3 ? activateTurbo : addScore,
897
+ active: true
898
+ };
899
+
900
+ // Ensure power-ups don't overlap with obstacles
901
+ let overlaps = false;
902
+ for (const obstacle of obstacles) {
903
+ const dx = powerUp.x - obstacle.x;
904
+ const dy = powerUp.y - obstacle.y;
905
+ const distance = Math.sqrt(dx * dx + dy * dy);
906
+ if (distance < powerUp.radius + Math.max(obstacle.width, obstacle.height) / 2) {
907
+ overlaps = true;
908
+ break;
909
+ }
910
+ }
911
+
912
+ if (!overlaps) {
913
+ powerUps.push(powerUp);
914
+ }
915
+ }
916
+ }
917
+
918
+ function activateTurbo() {
919
+ if (car.turboCooldown <= 0) {
920
+ car.turboMode = true;
921
+ car.turboTimer = car.turboMaxTime;
922
+ car.maxSpeed = 800;
923
+ car.acceleration = 400;
924
+
925
+ // Show turbo message
926
+ gameMessage.textContent = "TURBO ACTIVATED!";
927
+ gameMessage.style.display = "block";
928
+ gameMessage.style.color = "#ff00ff";
929
+
930
+ setTimeout(() => {
931
+ gameMessage.style.display = "none";
932
+ }, 1500);
933
+ }
934
+ }
935
+
936
+ function addScore() {
937
+ score += 100;
938
+ updateScore();
939
+
940
+ // Show score message
941
+ gameMessage.textContent = "+100 POINTS!";
942
+ gameMessage.style.display = "block";
943
+ gameMessage.style.color = "#ffff00";
944
+
945
  setTimeout(() => {
946
+ gameMessage.style.display = "none";
947
+ }, 1000);
948
+ }
949
+
950
+ function resetGame() {
951
+ car.x = canvas.width / 2;
952
+ car.y = canvas.height / 2;
953
+ car.angle = 0;
954
+ car.speed = 0;
955
+ car.tireMarks = [];
956
+ car.turboMode = false;
957
+ car.turboTimer = 0;
958
+ car.turboCooldown = 0;
959
+ car.maxSpeed = 500;
960
+ car.acceleration = 200;
961
+
962
+ score = 0;
963
+ obstaclesHit = 0;
964
+ updateScore();
965
+
966
+ obstacles.length = 0;
967
+ powerUps.length = 0;
968
+
969
+ createObstacles();
970
+ createPowerUps();
971
+ }
972
+
973
+ function updateScore() {
974
+ scoreDisplay.textContent = `Score: ${score} | Hits: ${obstaclesHit}`;
975
+ }
976
+
977
+ function drawCar(ctx) {
978
+ ctx.save();
979
+ ctx.translate(car.x, car.y);
980
+ ctx.rotate(car.angle);
981
+
982
+ // Car shadow
983
+ ctx.fillStyle = car.shadowColor;
984
+ ctx.fillRect(-car.width/2 - 2, -car.height/2 - 2, car.width + 4, car.height + 4);
985
+
986
+ // Car body
987
+ ctx.fillStyle = car.turboMode ? '#ff00ff' : car.color;
988
+ ctx.fillRect(-car.width/2, -car.height/2, car.width, car.height);
989
+
990
+ // Windows
991
+ ctx.fillStyle = "#333";
992
+ ctx.fillRect(-car.width/2 + 5, -car.height/2 + 10, car.width - 10, car.height/3);
993
+ ctx.fillRect(-car.width/2 + 5, car.height/2 - 20, car.width - 10, 10);
994
+
995
+ // Wheels
996
+ ctx.fillStyle = "#222";
997
+ ctx.fillRect(-car.width/2 - 3, -car.height/2 + 10, 5, 15);
998
+ ctx.fillRect(car.width/2 - 2, -car.height/2 + 10, 5, 15);
999
+ ctx.fillRect(-car.width/2 - 3, car.height/2 - 25, 5, 15);
1000
+ ctx.fillRect(car.width/2 - 2, car.height/2 - 25, 5, 15);
1001
+
1002
+ // Turbo effect
1003
+ if (car.turboMode) {
1004
+ ctx.beginPath();
1005
+ ctx.moveTo(-car.width/2, car.height/2);
1006
+ ctx.lineTo(-car.width/2 - 10, car.height/2 + 20);
1007
+ ctx.lineTo(car.width/2, car.height/2);
1008
+ ctx.fillStyle = "#ff00ff";
1009
+ ctx.fill();
1010
+
1011
+ ctx.beginPath();
1012
+ ctx.moveTo(-car.width/2, car.height/2);
1013
+ ctx.lineTo(-car.width/2 - 5, car.height/2 + 30);
1014
+ ctx.lineTo(car.width/2, car.height/2);
1015
+ ctx.fillStyle = "#ffff00";
1016
+ ctx.fill();
1017
+ }
1018
+
1019
+ ctx.restore();
1020
+ }
1021
+
1022
+ function drawTireMarks(ctx) {
1023
+ ctx.lineWidth = 2;
1024
+
1025
+ for (let i = 0; i < car.tireMarks.length; i++) {
1026
+ const mark = car.tireMarks[i];
1027
+
1028
+ // Fade out tire marks over time
1029
+ mark.life -= deltaTime;
1030
+ if (mark.life <= 0) {
1031
+ car.tireMarks.splice(i, 1);
1032
+ i--;
1033
+ continue;
1034
+ }
1035
+
1036
+ const alpha = mark.life / mark.maxLife;
1037
+ ctx.strokeStyle = `rgba(0, 0, 0, ${alpha * 0.6})`;
1038
+
1039
+ ctx.beginPath();
1040
+ ctx.moveTo(mark.x1, mark.y1);
1041
+ ctx.lineTo(mark.x2, mark.y2);
1042
+ ctx.stroke();
1043
+ }
1044
+ }
1045
+
1046
+ function addTireMark() {
1047
+ // Calculate wheel positions
1048
+ const wheelDistance = car.width / 2;
1049
+ const backAxleOffset = car.height / 3;
1050
+
1051
+ // Left rear wheel
1052
+ const leftX = car.x - Math.cos(car.angle) * wheelDistance + Math.sin(car.angle) * backAxleOffset;
1053
+ const leftY = car.y - Math.sin(car.angle) * wheelDistance - Math.cos(car.angle) * backAxleOffset;
1054
+
1055
+ // Right rear wheel
1056
+ const rightX = car.x + Math.cos(car.angle) * wheelDistance + Math.sin(car.angle) * backAxleOffset;
1057
+ const rightY = car.y + Math.sin(car.angle) * wheelDistance - Math.cos(car.angle) * backAxleOffset;
1058
+
1059
+ // Previous positions
1060
+ const prevMark = car.tireMarks[car.tireMarks.length - 1];
1061
+
1062
+ if (prevMark && car.drifting) {
1063
+ car.tireMarks.push({
1064
+ x1: prevMark.x2,
1065
+ y1: prevMark.y2,
1066
+ x2: leftX,
1067
+ y2: leftY,
1068
+ life: 2,
1069
+ maxLife: 2
1070
+ });
1071
+
1072
+ car.tireMarks.push({
1073
+ x1: prevMark.x2b,
1074
+ y1: prevMark.y2b,
1075
+ x2: rightX,
1076
+ y2: rightY,
1077
+ life: 2,
1078
+ maxLife: 2
1079
+ });
1080
+ } else {
1081
+ car.tireMarks.push({
1082
+ x1: leftX,
1083
+ y1: leftY,
1084
+ x2: leftX,
1085
+ y2: leftY,
1086
+ x2b: rightX,
1087
+ y2b: rightY,
1088
+ life: 2,
1089
+ maxLife: 2
1090
+ });
1091
+ }
1092
+
1093
+ // Limit the number of tire marks
1094
+ if (car.tireMarks.length > 100) {
1095
+ car.tireMarks.shift();
1096
+ }
1097
+ }
1098
+
1099
+ function drawObstacles(ctx) {
1100
+ for (const obstacle of obstacles) {
1101
+ ctx.save();
1102
+ ctx.translate(obstacle.x, obstacle.y);
1103
+ ctx.rotate(obstacle.angle);
1104
+
1105
+ // Draw obstacle body
1106
+ ctx.fillStyle = obstacle.color;
1107
+ ctx.fillRect(-obstacle.width/2, -obstacle.height/2, obstacle.width, obstacle.height);
1108
+
1109
+ // Draw text
1110
+ ctx.fillStyle = '#fff';
1111
+ ctx.font = '16px var(--font-main), sans-serif';
1112
+ ctx.textAlign = 'center';
1113
+ ctx.textBaseline = 'middle';
1114
+ ctx.fillText(obstacle.text, 0, 0);
1115
+
1116
+ ctx.restore();
1117
+ }
1118
+ }
1119
+
1120
+ function drawPowerUps(ctx) {
1121
+ for (const powerUp of powerUps) {
1122
+ if (!powerUp.active) continue;
1123
+
1124
+ ctx.beginPath();
1125
+ ctx.arc(powerUp.x, powerUp.y, powerUp.radius, 0, Math.PI * 2);
1126
+ ctx.fillStyle = powerUp.color;
1127
+ ctx.fill();
1128
+
1129
+ ctx.fillStyle = '#000';
1130
+ ctx.font = '12px var(--font-main), sans-serif';
1131
+ ctx.textAlign = 'center';
1132
+ ctx.textBaseline = 'middle';
1133
+ ctx.fillText(powerUp.type === 'turbo' ? 'TURBO' : 'SCORE', powerUp.x, powerUp.y);
1134
+
1135
+ // Pulsing effect
1136
+ powerUp.radius = 15 + Math.sin(Date.now() / 200) * 2;
1137
+ }
1138
+ }
1139
+
1140
+ function updateCarPosition(deltaTime) {
1141
+ // Handle input
1142
+ if (keys.up) {
1143
+ car.speed += car.acceleration * deltaTime;
1144
+ } else if (keys.down) {
1145
+ car.speed -= car.deceleration * deltaTime;
1146
+ } else {
1147
+ // Apply friction
1148
+ car.speed *= car.friction;
1149
+ }
1150
+
1151
+ // Limit speed
1152
+ car.speed = Math.max(car.reverseMaxSpeed, Math.min(car.maxSpeed, car.speed));
1153
+
1154
+ // Apply braking
1155
+ if (keys.handbrake) {
1156
+ car.speed *= car.brakeStrength;
1157
+ car.drifting = Math.abs(car.speed) > 100;
1158
+
1159
+ if (car.drifting) {
1160
+ addTireMark();
1161
+ }
1162
+ } else {
1163
+ car.drifting = false;
1164
+ }
1165
+
1166
+ // Steering
1167
+ let turnFactor = (car.speed / car.maxSpeed) * car.turnSpeed;
1168
+
1169
+ // Reduce turning at very low speeds
1170
+ if (Math.abs(car.speed) < 50) {
1171
+ turnFactor *= Math.abs(car.speed) / 50;
1172
+ }
1173
+
1174
+ // Apply drift effect
1175
+ if (car.drifting) {
1176
+ turnFactor *= 1.5; // More responsive turning while drifting
1177
+ }
1178
+
1179
+ if (keys.left) {
1180
+ car.angle -= turnFactor * deltaTime;
1181
+ }
1182
+ if (keys.right) {
1183
+ car.angle += turnFactor * deltaTime;
1184
+ }
1185
+
1186
+ // Update position based on velocity
1187
+ const moveAngle = car.drifting ? car.angle - (car.speed > 0 ? 0.2 : -0.2) : car.angle;
1188
+ car.x += Math.sin(moveAngle) * car.speed * deltaTime;
1189
+ car.y -= Math.cos(moveAngle) * car.speed * deltaTime;
1190
+
1191
+ // Handle turbo mode
1192
+ if (car.turboMode) {
1193
+ car.turboTimer -= deltaTime;
1194
+
1195
+ if (car.turboTimer <= 0) {
1196
+ car.turboMode = false;
1197
+ car.maxSpeed = 500;
1198
+ car.acceleration = 200;
1199
+ car.turboCooldown = car.turboCooldownMax;
1200
+ }
1201
+ }
1202
+
1203
+ // Update turbo cooldown
1204
+ if (car.turboCooldown > 0) {
1205
+ car.turboCooldown -= deltaTime;
1206
+ }
1207
+
1208
+ // Screen boundaries with bounce
1209
+ if (car.x < 0) {
1210
+ car.x = 0;
1211
+ car.speed *= -0.5;
1212
+ }
1213
+ if (car.x > canvas.width) {
1214
+ car.x = canvas.width;
1215
+ car.speed *= -0.5;
1216
+ }
1217
+ if (car.y < 0) {
1218
+ car.y = 0;
1219
+ car.speed *= -0.5;
1220
+ }
1221
+ if (car.y > canvas.height) {
1222
+ car.y = canvas.height;
1223
+ car.speed *= -0.5;
1224
+ }
1225
+ }
1226
+
1227
+ function checkCollisions() {
1228
+ // Check collisions with obstacles
1229
+ for (const obstacle of obstacles) {
1230
+ // Calculate distance between car and obstacle centers
1231
+ const dx = car.x - obstacle.x;
1232
+ const dy = car.y - obstacle.y;
1233
+ const distance = Math.sqrt(dx * dx + dy * dy);
1234
+
1235
+ // Simple collision detection based on distance
1236
+ const minDistance = Math.max(car.width, car.height) / 2 + Math.max(obstacle.width, obstacle.height) / 2;
1237
+
1238
+ if (distance < minDistance * 0.8) {
1239
+ // Calculate collision normal
1240
+ const nx = dx / distance;
1241
+ const ny = dy / distance;
1242
+
1243
+ // Calculate relative velocity
1244
+ const speed = Math.abs(car.speed);
1245
+ const impactSpeed = speed * 0.8;
1246
+
1247
+ // Apply impulse to obstacle
1248
+ obstacle.vx += nx * impactSpeed * 0.1;
1249
+ obstacle.vy += ny * impactSpeed * 0.1;
1250
+ obstacle.angularVelocity += (Math.random() - 0.5) * impactSpeed * 0.01;
1251
+
1252
+ // Apply reaction to car
1253
+ car.speed *= -0.3;
1254
+
1255
+ // Add score and update counter
1256
+ score += Math.floor(speed / 10);
1257
+ obstaclesHit++;
1258
+ updateScore();
1259
+
1260
+ // Screen shake effect for strong impacts
1261
+ if (speed > 200) {
1262
+ const shakeIntensity = speed / 100;
1263
+ document.getElementById('game-container').style.transform = `translate(${(Math.random() - 0.5) * shakeIntensity}px, ${(Math.random() - 0.5) * shakeIntensity}px)`;
1264
+ setTimeout(() => {
1265
+ document.getElementById('game-container').style.transform = 'translate(0, 0)';
1266
+ }, 100);
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ // Check collisions with power-ups
1272
+ for (let i = 0; i < powerUps.length; i++) {
1273
+ const powerUp = powerUps[i];
1274
+ if (!powerUp.active) continue;
1275
+
1276
+ // Calculate distance
1277
+ const dx = car.x - powerUp.x;
1278
+ const dy = car.y - powerUp.y;
1279
+ const distance = Math.sqrt(dx * dx + dy * dy);
1280
+
1281
+ if (distance < car.width / 2 + powerUp.radius) {
1282
+ // Collect power-up
1283
+ powerUp.active = false;
1284
+ powerUp.collectEffect();
1285
+
1286
+ // Create a new power-up
1287
+ setTimeout(() => {
1288
+ powerUps.splice(i, 1);
1289
+ createPowerUps();
1290
+ }, 1000);
1291
+ }
1292
+ }
1293
+ }
1294
+
1295
+ function updateObstacles(deltaTime) {
1296
+ for (const obstacle of obstacles) {
1297
+ // Apply physics
1298
+ obstacle.x += obstacle.vx * deltaTime;
1299
+ obstacle.y += obstacle.vy * deltaTime;
1300
+ obstacle.angle += obstacle.angularVelocity * deltaTime;
1301
+
1302
+ // Apply friction
1303
+ obstacle.vx *= 0.98;
1304
+ obstacle.vy *= 0.98;
1305
+ obstacle.angularVelocity *= 0.95;
1306
+
1307
+ // Keep on screen with bounce
1308
+ if (obstacle.x < 0) {
1309
+ obstacle.x = 0;
1310
+ obstacle.vx *= -0.7;
1311
+ }
1312
+ if (obstacle.x > canvas.width) {
1313
+ obstacle.x = canvas.width;
1314
+ obstacle.vx *= -0.7;
1315
+ }
1316
+ if (obstacle.y < 0) {
1317
+ obstacle.y = 0;
1318
+ obstacle.vy *= -0.7;
1319
+ }
1320
+ if (obstacle.y > canvas.height) {
1321
+ obstacle.y = canvas.height;
1322
+ obstacle.vy *= -0.7;
1323
+ }
1324
+
1325
+ // Obstacle-obstacle collisions
1326
+ for (const other of obstacles) {
1327
+ if (obstacle === other) continue;
1328
+
1329
+ const dx = obstacle.x - other.x;
1330
+ const dy = obstacle.y - other.y;
1331
+ const distance = Math.sqrt(dx * dx + dy * dy);
1332
+ const minDistance = (obstacle.width + other.width) / 2;
1333
+
1334
+ if (distance < minDistance) {
1335
+ // Calculate collision response
1336
+ const nx = dx / distance;
1337
+ const ny = dy / distance;
1338
+ const overlap = minDistance - distance;
1339
+
1340
+ // Separate objects
1341
+ obstacle.x += nx * overlap * 0.5;
1342
+ obstacle.y += ny * overlap * 0.5;
1343
+ other.x -= nx * overlap * 0.5;
1344
+ other.y -= ny * overlap * 0.5;
1345
+
1346
+ // Exchange momentum (simplified)
1347
+ const p = 2 * (obstacle.vx * nx + obstacle.vy * ny - other.vx * nx - other.vy * ny)
1348
+ / (obstacle.mass + other.mass);
1349
+
1350
+ obstacle.vx -= p * other.mass * nx;
1351
+ obstacle.vy -= p * other.mass * ny;
1352
+ other.vx += p * obstacle.mass * nx;
1353
+ other.vy += p * obstacle.mass * ny;
1354
+
1355
+ // Add some random rotation
1356
+ obstacle.angularVelocity += (Math.random() - 0.5) * 2;
1357
+ other.angularVelocity += (Math.random() - 0.5) * 2;
1358
+ }
1359
+ }
1360
+ }
1361
+ }
1362
+
1363
+ function drawBackground(ctx) {
1364
+ // Grid pattern
1365
+ ctx.fillStyle = '#050505';
1366
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
1367
+
1368
+ ctx.strokeStyle = 'rgba(0, 255, 255, 0.1)';
1369
+ ctx.lineWidth = 1;
1370
+
1371
+ const gridSize = 50;
1372
+ const offsetX = car.x % gridSize;
1373
+ const offsetY = car.y % gridSize;
1374
+
1375
+ for (let x = -offsetX; x < canvas.width; x += gridSize) {
1376
+ ctx.beginPath();
1377
+ ctx.moveTo(x, 0);
1378
+ ctx.lineTo(x, canvas.height);
1379
+ ctx.stroke();
1380
+ }
1381
+
1382
+ for (let y = -offsetY; y < canvas.height; y += gridSize) {
1383
+ ctx.beginPath();
1384
+ ctx.moveTo(0, y);
1385
+ ctx.lineTo(canvas.width, y);
1386
+ ctx.stroke();
1387
+ }
1388
+
1389
+ // Track border
1390
+ ctx.strokeStyle = 'rgba(0, 255, 255, 0.3)';
1391
+ ctx.lineWidth = 10;
1392
+ ctx.strokeRect(50, 50, canvas.width - 100, canvas.height - 100);
1393
+ }
1394
+
1395
+ function drawGameInfo(ctx) {
1396
+ // Speed gauge
1397
+ const speedPercent = Math.abs(car.speed) / car.maxSpeed;
1398
+ ctx.fillStyle = car.turboMode ? '#ff00ff' : '#00ffff';
1399
+ ctx.fillRect(canvas.width - 120, canvas.height - 30, 100 * speedPercent, 15);
1400
+
1401
+ ctx.strokeStyle = '#ffffff';
1402
+ ctx.strokeRect(canvas.width - 120, canvas.height - 30, 100, 15);
1403
+
1404
+ ctx.fillStyle = '#ffffff';
1405
+ ctx.font = '12px var(--font-main), sans-serif';
1406
+ ctx.textAlign = 'right';
1407
+ ctx.fillText(`${Math.abs(Math.floor(car.speed))} km/h`, canvas.width - 125, canvas.height - 20);
1408
+
1409
+ // Turbo cooldown indicator
1410
+ if (car.turboCooldown > 0) {
1411
+ ctx.fillStyle = 'rgba(255, 0, 255, 0.3)';
1412
+ ctx.fillRect(canvas.width - 120, canvas.height - 50, 100 * (1 - car.turboCooldown / car.turboCooldownMax), 15);
1413
+ ctx.strokeStyle = '#ff00ff';
1414
+ ctx.strokeRect(canvas.width - 120, canvas.height - 50, 100, 15);
1415
+ ctx.fillStyle = '#ff00ff';
1416
+ ctx.textAlign = 'right';
1417
+ ctx.fillText('TURBO RECHARGING', canvas.width - 125, canvas.height - 40);
1418
+ } else if (car.turboMode) {
1419
+ ctx.fillStyle = 'rgba(255, 0, 255, 0.8)';
1420
+ ctx.fillRect(canvas.width - 120, canvas.height - 50, 100 * (car.turboTimer / car.turboMaxTime), 15);
1421
+ ctx.strokeStyle = '#ff00ff';
1422
+ ctx.strokeRect(canvas.width - 120, canvas.height - 50, 100, 15);
1423
+ ctx.fillStyle = '#ff00ff';
1424
+ ctx.textAlign = 'right';
1425
+ ctx.fillText('TURBO ACTIVE', canvas.width - 125, canvas.height - 40);
1426
+ } else {
1427
+ ctx.fillStyle = '#ff00ff';
1428
+ ctx.textAlign = 'right';
1429
+ ctx.fillText('TURBO READY', canvas.width - 125, canvas.height - 40);
1430
+ }
1431
+ }
1432
+
1433
+ function gameLoop(timestamp) {
1434
+ if (!gameActive) return;
1435
+
1436
+ // Calculate delta time for smooth animation
1437
+ if (!lastTimestamp) lastTimestamp = timestamp;
1438
+ deltaTime = (timestamp - lastTimestamp) / 1000; // Convert to seconds
1439
+ deltaTime = Math.min(deltaTime, 0.1); // Cap delta time to prevent large jumps
1440
+ lastTimestamp = timestamp;
1441
+
1442
+ // Clear canvas
1443
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1444
+
1445
+ // Draw background
1446
+ drawBackground(ctx);
1447
+
1448
+ // Update car position
1449
+ updateCarPosition(deltaTime);
1450
+
1451
+ // Update obstacles
1452
+ updateObstacles(deltaTime);
1453
+
1454
+ // Check collisions
1455
+ checkCollisions();
1456
+
1457
+ // Draw tire marks
1458
+ drawTireMarks(ctx);
1459
+
1460
+ // Draw obstacles
1461
+ drawObstacles(ctx);
1462
+
1463
+ // Draw power-ups
1464
+ drawPowerUps(ctx);
1465
+
1466
+ // Draw car
1467
+ drawCar(ctx);
1468
+
1469
+ // Draw game information
1470
+ drawGameInfo(ctx);
1471
+
1472
+ requestAnimationFrame(gameLoop);
1473
+ }
1474
+
1475
+ // Initialize game when toggled
1476
+ gameToggle.addEventListener('click', () => {
1477
+ if (!gameActive) {
1478
+ gameActive = true;
1479
+ gameContainer.style.display = 'block';
1480
+ resetGame();
1481
+ lastTimestamp = 0;
1482
+ requestAnimationFrame(gameLoop);
1483
+ } else {
1484
+ gameActive = false;
1485
+ gameContainer.style.display = 'none';
1486
+ }
1487
+ });
1488
+
1489
+ // Also close the game when pressing ESC
1490
+ window.addEventListener('keydown', (e) => {
1491
+ if (e.key === 'Escape' && gameActive) {
1492
+ gameActive = false;
1493
+ gameContainer.style.display = 'none';
1494
+ }
1495
+ });
1496
  </script>
1497
  </body>
1498
  </html>