Spaces:
Running
Running
Update index.html
Browse files- 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;
|
53 |
}
|
54 |
|
55 |
/* Subtle Animated Background Gradient */
|
@@ -82,57 +85,36 @@
|
|
82 |
padding: 2rem;
|
83 |
max-width: 900px;
|
84 |
width: 90%;
|
85 |
-
|
86 |
-
animation: slideInUp 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.5s forwards;
|
87 |
opacity: 0;
|
88 |
-
transform
|
89 |
-
|
90 |
}
|
91 |
|
92 |
-
/* Enhanced Entry Animation */
|
93 |
@keyframes slideInUp {
|
94 |
-
|
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 |
-
|
102 |
-
transform: translateY(0) rotateX(0deg) translateZ(0);
|
103 |
}
|
104 |
}
|
105 |
|
106 |
.title-container {
|
107 |
-
position: relative;
|
108 |
-
display: inline-block;
|
109 |
-
|
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 |
-
|
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 |
-
|
135 |
-
|
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 |
-
|
146 |
-
|
147 |
}
|
148 |
|
149 |
.models-section {
|
150 |
margin-bottom: 3rem;
|
151 |
opacity: 0;
|
152 |
animation: fadeIn 1s ease 1s forwards;
|
153 |
-
|
154 |
-
|
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);
|
185 |
-
|
186 |
-
|
187 |
}
|
188 |
|
189 |
.model-badge:hover {
|
190 |
background-color: var(--hover-bg);
|
191 |
color: var(--accent-color);
|
192 |
border-color: var(--accent-color);
|
193 |
-
|
194 |
-
|
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 |
-
|
206 |
-
|
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:
|
|
|
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 |
-
|
249 |
-
|
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 |
-
|
352 |
-
|
353 |
-
|
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 |
-
|
375 |
-
|
376 |
-
|
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 |
-
|
433 |
-
let
|
434 |
-
let
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
|
|
|
|
|
|
476 |
|
477 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
492 |
-
if (
|
493 |
-
|
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 |
-
|
|
|
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 |
-
//
|
510 |
-
|
511 |
-
|
512 |
-
cursor.classList.toggle('hover-text', isHoveringText && !isHoveringLink);
|
513 |
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
|
|
|
525 |
currentCursorScale = targetScale;
|
526 |
}
|
527 |
|
|
|
528 |
document.addEventListener('mousemove', updateCursorState);
|
529 |
|
|
|
530 |
document.addEventListener('mousedown', () => {
|
531 |
-
|
532 |
-
updateCursorState();
|
533 |
});
|
534 |
|
535 |
document.addEventListener('mouseup', () => {
|
536 |
-
|
537 |
-
updateCursorState();
|
538 |
});
|
539 |
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
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>
|