ParthSadaria commited on
Commit
0b3619c
·
verified ·
1 Parent(s): 63dc499

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +301 -259
index.html CHANGED
@@ -13,6 +13,9 @@
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;
@@ -49,7 +52,7 @@
49
  position: relative;
50
  overflow: hidden;
51
  cursor: none; /* Hide default cursor */
52
- perspective: 1000px; /* Enable 3D transforms for children */
53
  }
54
 
55
  /* Subtle Animated Background Gradient */
@@ -82,57 +85,36 @@
82
  padding: 2rem;
83
  max-width: 900px;
84
  width: 90%;
85
- /* Updated animation with 3D rotation */
86
- animation: slideInUp 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.5s forwards;
87
  opacity: 0;
88
- transform-style: preserve-3d; /* Ensure children respect 3D space */
89
- /* Initial transform moved to keyframe */
90
  }
91
 
92
- /* Enhanced Entry Animation */
93
  @keyframes slideInUp {
94
- 0% {
95
- opacity: 0;
96
- /* Start further down, slightly rotated back */
97
- transform: translateY(60px) rotateX(-15deg) translateZ(-50px);
98
- }
99
- 100% {
100
  opacity: 1;
101
- /* End in the final position with no rotation */
102
- transform: translateY(0) rotateX(0deg) translateZ(0);
103
  }
104
  }
105
 
106
  .title-container {
107
- position: relative; /* Needed for absolute positioning of canvas */
108
- display: inline-block; /* Keep it tight around the h1 */
109
- margin-bottom: 0.2rem;
110
- }
111
-
112
- /* Canvas for the 3D Glass Ball */
113
- #glass-ball-canvas {
114
- position: absolute;
115
- top: 50%; /* Center vertically */
116
- left: 50%; /* Center horizontally */
117
- width: 180px; /* Adjust size as needed */
118
- height: 180px; /* Adjust size as needed */
119
- transform: translate(-50%, -65%) translateZ(20px); /* Center and bring slightly forward, adjust Y offset */
120
- z-index: 1; /* Place it behind the text (adjust if needed) */
121
- pointer-events: none; /* Allow clicks to pass through */
122
- opacity: 0; /* Start hidden */
123
- animation: fadeIn 2s ease 1.5s forwards; /* Fade in after main animation */
124
  }
125
 
126
  h1 {
127
  font-size: clamp(3rem, 10vw, 6rem);
128
  font-weight: 700;
129
  letter-spacing: -0.05em;
130
- /* margin-bottom: 0.2rem; Removed, handled by title-container */
131
  color: var(--text-color);
132
  text-shadow: 0 0 15px rgba(255, 255, 255, 0.1);
133
  transition: color var(--transition-speed) ease, text-shadow var(--transition-speed) ease;
134
- position: relative; /* Needed for z-index stacking */
135
- z-index: 2; /* Ensure text is above the canvas */
136
  }
137
 
138
  .subtitle {
@@ -142,16 +124,16 @@
142
  margin-bottom: 2.5rem;
143
  letter-spacing: 0.05em;
144
  transition: color var(--transition-speed) ease;
145
- position: relative; /* Stacking context */
146
- z-index: 2;
147
  }
148
 
149
  .models-section {
150
  margin-bottom: 3rem;
151
  opacity: 0;
152
  animation: fadeIn 1s ease 1s forwards;
153
- position: relative; /* Stacking context */
154
- z-index: 2;
155
  }
156
 
157
  .models-section h2 {
@@ -169,6 +151,7 @@
169
  flex-wrap: wrap;
170
  justify-content: center;
171
  gap: 0.8rem;
 
172
  }
173
 
174
  .model-badge {
@@ -181,18 +164,17 @@
181
  border-radius: 15px;
182
  border: 1px solid var(--border-color);
183
  transition: all var(--transition-speed) cubic-bezier(0.25, 0.46, 0.45, 0.94);
184
- transform: scale(1) translateZ(0); /* Prepare for 3D hover */
185
- position: relative; /* Stacking context */
186
- z-index: 2;
187
  }
188
 
189
  .model-badge:hover {
190
  background-color: var(--hover-bg);
191
  color: var(--accent-color);
192
  border-color: var(--accent-color);
193
- /* Subtle 3D lift effect */
194
- transform: scale(1.05) translateY(-2px) translateZ(10px);
195
- box-shadow: 0 6px 20px rgba(0, 255, 255, 0.25);
196
  }
197
 
198
  .playgrounds-section {
@@ -202,8 +184,8 @@
202
  flex-direction: column; /* Stack links vertically on mobile */
203
  align-items: center;
204
  gap: 1rem;
205
- position: relative; /* Stacking context */
206
- z-index: 2;
207
  }
208
 
209
  @media (min-width: 600px) {
@@ -227,7 +209,8 @@
227
  overflow: hidden;
228
  transition: all var(--transition-speed) ease;
229
  z-index: 1;
230
- transform: translateZ(0); /* Prepare for 3D hover */
 
231
  }
232
 
233
  .playground-link::before {
@@ -245,9 +228,8 @@
245
  .playground-link:hover {
246
  color: var(--bg-color);
247
  border-color: var(--accent-color);
248
- /* Subtle 3D lift */
249
- transform: translateY(-3px) translateZ(15px);
250
- box-shadow: 0 8px 25px rgba(0, 255, 255, 0.35);
251
  }
252
 
253
  .playground-link:hover::before {
@@ -267,18 +249,20 @@
267
 
268
  .glow {
269
  position: fixed;
 
270
  left: 0;
271
  top: 0;
272
  width: 800px;
273
  height: 800px;
274
  background: radial-gradient(circle at center, var(--glow-color) 0%, rgba(0, 0, 0, 0) 60%);
275
- border-radius: 50%;
276
  pointer-events: none;
277
  opacity: 0;
278
- transition: opacity 0.4s ease;
 
279
  z-index: 1;
280
  filter: blur(10px);
281
- will-change: transform, opacity;
282
  }
283
 
284
  body:hover .glow {
@@ -287,6 +271,7 @@
287
 
288
  .cursor {
289
  position: fixed;
 
290
  left: 0;
291
  top: 0;
292
  width: 25px;
@@ -294,24 +279,29 @@
294
  border: 2px solid var(--accent-color);
295
  border-radius: 50%;
296
  pointer-events: none;
297
- transition: background-color 0.2s ease, border-color 0.2s ease, border-width 0.2s ease, width 0.2s ease, height 0.2s ease;
 
298
  z-index: 9999;
299
  mix-blend-mode: difference;
300
  background-color: transparent;
301
- will-change: transform;
302
  }
303
 
 
304
  .cursor.hover-link {
 
305
  background-color: var(--accent-color);
306
  border-color: transparent;
307
  }
308
 
309
  .cursor.hover-text {
 
310
  border-width: 4px;
311
  border-color: rgba(255,255,255, 0.5);
312
  }
313
 
314
  .cursor.clicking {
 
315
  background-color: rgba(255,255,255,0.3);
316
  }
317
 
@@ -319,15 +309,76 @@
319
  to { opacity: 1; }
320
  }
321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  /* Responsive adjustments */
323
  @media (max-width: 768px) {
324
- /* Adjust canvas size/position on smaller screens if needed */
325
- #glass-ball-canvas {
326
- width: 120px;
327
- height: 120px;
328
- transform: translate(-50%, -60%) translateZ(15px); /* Adjust Y offset and Z */
329
- }
330
-
331
  h1 {
332
  letter-spacing: -0.03em;
333
  }
@@ -343,37 +394,45 @@
343
  .playground-link {
344
  padding: 0.7em 1.5em;
345
  }
 
346
  .glow {
347
  width: 500px;
348
  height: 500px;
349
  }
350
- }
351
- @media (max-width: 480px) {
352
- #glass-ball-canvas {
353
- width: 100px;
354
- height: 100px;
355
- transform: translate(-50%, -55%) translateZ(10px); /* Further adjust */
356
- }
357
- h1 {
358
- font-size: clamp(2.5rem, 10vw, 4rem); /* Slightly smaller H1 on very small screens */
359
  }
360
- }
361
 
362
  </style>
363
  </head>
364
  <body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  <!-- Mouse Effect Elements -->
366
  <div class="cursor"></div>
367
  <div class="glow"></div>
368
 
369
  <!-- Main Content -->
370
  <div class="container">
371
-
372
- <!-- Title container for positioning canvas -->
373
  <div class="title-container">
374
- <!-- Canvas for 3D Glass Ball -->
375
- <canvas id="glass-ball-canvas"></canvas>
376
- <h1>Loki.AI</h1>
377
  </div>
378
  <p class="subtitle">By Parth Sadaria</p>
379
 
@@ -403,15 +462,13 @@
403
  </div>
404
  </div>
405
 
406
- <!-- Include Three.js Library -->
407
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
408
-
409
  <script>
410
- // --- Existing Cursor Logic ---
411
  const cursor = document.querySelector('.cursor');
412
  const glow = document.querySelector('.glow');
413
  const hoverables = document.querySelectorAll('a, .model-badge');
414
  const textElements = document.querySelectorAll('h1, p, h2, .model-badge'); // Include badges for text hover
 
 
415
 
416
  let mouseX = 0;
417
  let mouseY = 0;
@@ -420,61 +477,139 @@
420
  let glowX = 0;
421
  let glowY = 0;
422
 
 
423
  const cursorSpeed = 0.15;
424
  const glowSpeed = 0.1;
425
 
 
426
  let currentCursorScale = 1;
427
  const baseCursorScale = 1;
428
  const hoverLinkCursorScale = 1.5;
429
  const hoverTextCursorScale = 0.7;
430
  const clickCursorScale = 0.6;
431
 
432
- let cursorHalfWidth = 12.5; // Initial guess, will be updated
433
- let cursorHalfHeight = 12.5;
434
- let glowHalfWidth = 400; // Initial guess
435
- let glowHalfHeight = 400;
436
-
437
- function updateElementSizes() {
438
- cursorHalfWidth = cursor.offsetWidth / 2;
439
- cursorHalfHeight = cursor.offsetHeight / 2;
440
- // Only update glow size if the element exists and is rendered
441
- if (glow && glow.offsetWidth > 0) {
442
- glowHalfWidth = glow.offsetWidth / 2;
443
- glowHalfHeight = glow.offsetHeight / 2;
444
- } else {
445
- // Fallback or default if not rendered yet (might happen on first frame)
446
- glowHalfWidth = window.innerWidth > 768 ? 400 : 250;
447
- glowHalfHeight = window.innerWidth > 768 ? 400 : 250;
448
- }
449
- }
450
-
451
- window.addEventListener('resize', updateElementSizes);
452
- // Initial size calculation after elements are potentially rendered
453
- window.addEventListener('load', updateElementSizes);
454
- updateElementSizes(); // Call once early
455
-
456
 
457
- function animateCursor() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  cursorX += (mouseX - cursorX) * cursorSpeed;
459
  cursorY += (mouseY - cursorY) * cursorSpeed;
460
  glowX += (mouseX - glowX) * glowSpeed;
461
  glowY += (mouseY - glowY) * glowSpeed;
462
 
 
 
463
  const cursorTranslateX = cursorX - cursorHalfWidth;
464
  const cursorTranslateY = cursorY - cursorHalfHeight;
465
  const glowTranslateX = glowX - glowHalfWidth;
466
  const glowTranslateY = glowY - glowHalfHeight;
467
 
468
- // Use requestAnimationFrame for cursor/glow as well for consistency
469
- // Check if elements exist before trying to style them
470
- if (cursor) {
471
- cursor.style.transform = `translate(${cursorTranslateX}px, ${cursorTranslateY}px) scale(${currentCursorScale})`;
472
- }
473
- if (glow) {
474
- glow.style.transform = `translate(${glowTranslateX}px, ${glowTranslateY}px)`;
475
- }
 
 
 
476
 
477
- // requestAnimationFrame(animateCursor); // This will be called by the main loop
 
 
 
 
 
 
 
 
 
 
 
478
  }
479
 
480
  document.addEventListener('mousemove', (e) => {
@@ -482,182 +617,89 @@
482
  mouseY = e.clientY;
483
  });
484
 
 
 
 
485
  function updateCursorState() {
486
  let targetScale = baseCursorScale;
487
  let isHoveringLink = false;
488
  let isHoveringText = false;
489
 
 
490
  hoverables.forEach(el => {
491
- // Ensure element is visible before getting rect
492
- if (el.offsetParent !== null) {
493
- const rect = el.getBoundingClientRect();
494
- if (mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom) {
495
- isHoveringLink = true;
496
- }
497
  }
498
  });
499
 
500
  textElements.forEach(el => {
501
- if (!isHoveringLink && el.offsetParent !== null) {
 
502
  const rect = el.getBoundingClientRect();
503
  if (mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom) {
504
  isHoveringText = true;
505
  }
506
- }
507
  });
508
 
509
- // Check if cursor exists before toggling classes
510
- if (cursor) {
511
- cursor.classList.toggle('hover-link', isHoveringLink);
512
- cursor.classList.toggle('hover-text', isHoveringText && !isHoveringLink);
513
 
514
- if (isHoveringLink) {
515
- targetScale = hoverLinkCursorScale;
516
- } else if (isHoveringText) {
517
- targetScale = hoverTextCursorScale;
518
- }
519
 
520
- if (cursor.classList.contains('clicking')) {
521
- targetScale = clickCursorScale;
522
- }
523
- }
524
 
 
525
  currentCursorScale = targetScale;
526
  }
527
 
 
528
  document.addEventListener('mousemove', updateCursorState);
529
 
 
530
  document.addEventListener('mousedown', () => {
531
- if(cursor) cursor.classList.add('clicking');
532
- updateCursorState();
533
  });
534
 
535
  document.addEventListener('mouseup', () => {
536
- if(cursor) cursor.classList.remove('clicking');
537
- updateCursorState();
538
  });
539
 
540
- updateCursorState(); // Initial check
541
-
542
- // --- New Three.js Logic for Glass Ball ---
543
- let scene, camera, renderer, sphere, pointLight;
544
-
545
- function initThreeJS() {
546
- const canvas = document.getElementById('glass-ball-canvas');
547
- if (!canvas || !THREE) return; // Ensure canvas and THREE exist
548
-
549
- // Scene
550
- scene = new THREE.Scene();
551
-
552
- // Camera
553
- const canvasRect = canvas.getBoundingClientRect();
554
- // Adjust FOV based on desired sphere appearance size within canvas
555
- camera = new THREE.PerspectiveCamera(50, canvasRect.width / canvasRect.height, 0.1, 100);
556
- // Position camera to view the sphere centered at (0,0,0)
557
- camera.position.z = 3;
558
-
559
- // Renderer
560
- renderer = new THREE.WebGLRenderer({
561
- canvas: canvas,
562
- alpha: true, // Transparent background
563
- antialias: true
564
- });
565
- renderer.setSize(canvasRect.width, canvasRect.height);
566
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // Optimize for high DPI displays
567
-
568
- // Geometry
569
- const geometry = new THREE.SphereGeometry(1, 32, 32); // Radius 1, detail 32x32
570
-
571
- // Material - Physical material for glass effect
572
- const material = new THREE.MeshPhysicalMaterial({
573
- color: 0xffffff, // Base color (can be white for clear glass)
574
- metalness: 0.1, // Low metalness for non-metals
575
- roughness: 0.05, // Very low roughness for smooth glass
576
- transmission: 0.95, // High transmission (opacity/translucency)
577
- transparent: true,
578
- opacity: 0.9, // Overall opacity (can be slightly less than 1)
579
- ior: 1.52, // Index of Refraction for glass (~1.52)
580
- thickness: 0.5, // Simulates thickness for refraction effects
581
- // clearcoat: 1.0, // Optional: add a clear coat layer
582
- // clearcoatRoughness: 0.1, // Roughness of the clear coat
583
- envMapIntensity: 1 // How much environment affects reflection (needs env map for best results, but works ok without)
584
- });
585
-
586
- // Mesh
587
- sphere = new THREE.Mesh(geometry, material);
588
- scene.add(sphere);
589
-
590
- // Lighting
591
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // Soft white light
592
- scene.add(ambientLight);
593
-
594
- pointLight = new THREE.PointLight(var(--accent-color), 2, 10); // Cyan light, intensity 2, distance 10
595
- pointLight.position.set(1.5, 2, 2); // Position the light
596
- scene.add(pointLight);
597
-
598
- // Optional: Add another subtle light for more dimension
599
- const pointLight2 = new THREE.PointLight(0xffffff, 0.7, 10);
600
- pointLight2.position.set(-2, -1, 1.5);
601
- scene.add(pointLight2);
602
-
603
- // Start animation loop (combined with cursor loop)
604
- animateCombined();
605
-
606
- // Handle Resize
607
- window.addEventListener('resize', onWindowResize, false);
608
- }
609
-
610
- function onWindowResize() {
611
- const canvas = document.getElementById('glass-ball-canvas');
612
- if (!canvas || !camera || !renderer) return;
613
-
614
- const canvasRect = canvas.getBoundingClientRect(); // Use current rect
615
- const newWidth = canvasRect.width;
616
- const newHeight = canvasRect.height;
617
-
618
- // Update camera aspect ratio
619
- camera.aspect = newWidth / newHeight;
620
- camera.updateProjectionMatrix();
621
-
622
- // Update renderer size
623
- renderer.setSize(newWidth, newHeight);
624
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
625
-
626
- // Update cursor/glow element sizes too
627
- updateElementSizes();
628
- }
629
-
630
- // Combined Animation Loop
631
- function animateCombined() {
632
- requestAnimationFrame(animateCombined);
633
-
634
- // Update Cursor & Glow position/state
635
- animateCursor(); // Updates cursor/glow positions via lerp
636
- updateCursorState(); // Updates cursor style/scale based on hover
637
-
638
- // Update Three.js Scene (if initialized)
639
- if (sphere && scene && camera && renderer) {
640
- // Subtle rotation
641
- sphere.rotation.y += 0.003;
642
- sphere.rotation.x += 0.001;
643
-
644
- // Maybe subtle light movement or reaction to mouse (more complex)
645
- // Example: light follows mouse slightly on x-axis
646
- // pointLight.position.x = 1.5 + (mouseX / window.innerWidth - 0.5) * 2;
647
-
648
- // Render the 3D scene
649
- renderer.render(scene, camera);
650
- }
651
- }
652
-
653
- // Initialize Three.js after the DOM is ready
654
- // Use setTimeout to ensure layout is somewhat stable, especially for canvas size
655
- // Alternatively, use window.onload if dependencies are complex
656
- document.addEventListener('DOMContentLoaded', () => {
657
- // Small delay to help ensure canvas has dimensions
658
- setTimeout(initThreeJS, 100);
659
  });
660
 
 
 
 
 
 
 
 
661
  </script>
662
  </body>
663
  </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
+ <!-- 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;
 
52
  position: relative;
53
  overflow: hidden;
54
  cursor: none; /* Hide default cursor */
55
+ perspective: 1000px;
56
  }
57
 
58
  /* Subtle Animated Background Gradient */
 
85
  padding: 2rem;
86
  max-width: 900px;
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;
111
  letter-spacing: -0.05em;
112
+ margin-bottom: 0.2rem;
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
  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
  flex-wrap: wrap;
152
  justify-content: center;
153
  gap: 0.8rem;
154
+ perspective: 1000px;
155
  }
156
 
157
  .model-badge {
 
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
  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
  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
  .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 {
 
249
 
250
  .glow {
251
  position: fixed;
252
+ /* Position set by JS */
253
  left: 0;
254
  top: 0;
255
  width: 800px;
256
  height: 800px;
257
  background: radial-gradient(circle at center, var(--glow-color) 0%, rgba(0, 0, 0, 0) 60%);
258
+ border-radius: 50%; /* Ensure it's circular for centering */
259
  pointer-events: none;
260
  opacity: 0;
261
+ transition: opacity 0.4s ease; /* Only transition opacity */
262
+ /* transform: translate(-50%, -50%); NO - JS handles transform */
263
  z-index: 1;
264
  filter: blur(10px);
265
+ will-change: transform, opacity; /* Optimize animation */
266
  }
267
 
268
  body:hover .glow {
 
271
 
272
  .cursor {
273
  position: fixed;
274
+ /* Position set by JS */
275
  left: 0;
276
  top: 0;
277
  width: 25px;
 
279
  border: 2px solid var(--accent-color);
280
  border-radius: 50%;
281
  pointer-events: none;
282
+ transition: background-color 0.2s ease, border-color 0.2s ease, border-width 0.2s ease, width 0.2s ease, height 0.2s ease; /* Smooth style changes, transform handled by JS loop */
283
+ /* transform: translate(-50%, -50%); NO - JS handles transform */
284
  z-index: 9999;
285
  mix-blend-mode: difference;
286
  background-color: transparent;
287
+ will-change: transform; /* Optimize animation */
288
  }
289
 
290
+ /* Cursor interaction states (applied via JS) */
291
  .cursor.hover-link {
292
+ /* Scale handled by JS */
293
  background-color: var(--accent-color);
294
  border-color: transparent;
295
  }
296
 
297
  .cursor.hover-text {
298
+ /* Scale handled by JS */
299
  border-width: 4px;
300
  border-color: rgba(255,255,255, 0.5);
301
  }
302
 
303
  .cursor.clicking {
304
+ /* Scale handled by JS */
305
  background-color: rgba(255,255,255,0.3);
306
  }
307
 
 
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;
329
+ width: 100%;
330
+ height: 100%;
331
+ background-color: var(--bg-color);
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
  }
 
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
 
 
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;
 
477
  let glowX = 0;
478
  let glowY = 0;
479
 
480
+ // Adjust speed for desired smoothness (lower = smoother, more 'lag')
481
  const cursorSpeed = 0.15;
482
  const glowSpeed = 0.1;
483
 
484
+ // Store current scale factor for smooth transitions
485
  let currentCursorScale = 1;
486
  const baseCursorScale = 1;
487
  const hoverLinkCursorScale = 1.5;
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;
499
+ let glowHalfHeight = glow.offsetHeight / 2;
500
+
501
+ // Recalculate on resize just in case
502
+ window.addEventListener('resize', () => {
503
+ cursorHalfWidth = cursor.offsetWidth / 2;
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;
578
  cursorY += (mouseY - cursorY) * cursorSpeed;
579
  glowX += (mouseX - glowX) * glowSpeed;
580
  glowY += (mouseY - glowY) * glowSpeed;
581
 
582
+ // Apply transform for positioning and scaling
583
+ // Calculate the top-left position needed to center the element
584
  const cursorTranslateX = cursorX - cursorHalfWidth;
585
  const cursorTranslateY = cursorY - cursorHalfHeight;
586
  const glowTranslateX = glowX - glowHalfWidth;
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
  }
614
 
615
  document.addEventListener('mousemove', (e) => {
 
617
  mouseY = e.clientY;
618
  });
619
 
620
+ // Start the animation loop
621
+ animate();
622
+
623
  function updateCursorState() {
624
  let targetScale = baseCursorScale;
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) {
632
+ isHoveringLink = true;
 
 
 
633
  }
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) {
641
  isHoveringText = true;
642
  }
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;
652
+ } else if (isHoveringText) {
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>