first commit
This commit is contained in:
227
src/components/animations/Hero3D.tsx
Normal file
227
src/components/animations/Hero3D.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
|
||||
export default function Hero3D() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const container = containerRef.current;
|
||||
const width = container.clientWidth;
|
||||
const height = container.clientHeight;
|
||||
|
||||
// Scene setup
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
// Camera setup
|
||||
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100);
|
||||
camera.position.z = 8;
|
||||
|
||||
// Renderer setup
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setSize(width, height);
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
// Lights
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const keyLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||
keyLight.position.set(5, 5, 5);
|
||||
scene.add(keyLight);
|
||||
|
||||
const pinkLight = new THREE.PointLight(0xff2e88, 5, 15);
|
||||
pinkLight.position.set(-4, -2, 2);
|
||||
scene.add(pinkLight);
|
||||
|
||||
const goldLight = new THREE.PointLight(0xffc72c, 8, 15);
|
||||
goldLight.position.set(4, 3, 2);
|
||||
scene.add(goldLight);
|
||||
|
||||
// Creating the LYT Coin (an Octahedron + Cylinder compound shape for a stylized look)
|
||||
const coinGroup = new THREE.Group();
|
||||
|
||||
// Core Crystal (Diamond/Octahedron)
|
||||
const crystalGeom = new THREE.OctahedronGeometry(1.6, 0);
|
||||
const crystalMat = new THREE.MeshStandardMaterial({
|
||||
color: 0xffc72c,
|
||||
roughness: 0.1,
|
||||
metalness: 0.9,
|
||||
emissive: 0xffa500,
|
||||
emissiveIntensity: 0.3,
|
||||
flatShading: true,
|
||||
});
|
||||
const crystal = new THREE.Mesh(crystalGeom, crystalMat);
|
||||
coinGroup.add(crystal);
|
||||
|
||||
// Outer Ring
|
||||
const ringGeom = new THREE.TorusGeometry(2.3, 0.12, 16, 100);
|
||||
const ringMat = new THREE.MeshStandardMaterial({
|
||||
color: 0xff2e88,
|
||||
roughness: 0.2,
|
||||
metalness: 0.8,
|
||||
emissive: 0x8b3dff,
|
||||
emissiveIntensity: 0.2,
|
||||
});
|
||||
const ring = new THREE.Mesh(ringGeom, ringMat);
|
||||
ring.rotation.x = Math.PI / 2;
|
||||
coinGroup.add(ring);
|
||||
|
||||
// Floating minor energy crystals
|
||||
const smallCrystals: THREE.Mesh[] = [];
|
||||
const smallCrystalGeom = new THREE.OctahedronGeometry(0.3, 0);
|
||||
const smallCrystalMat = new THREE.MeshStandardMaterial({
|
||||
color: 0xc8ff37,
|
||||
roughness: 0.1,
|
||||
metalness: 0.8,
|
||||
flatShading: true,
|
||||
});
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const smallCrys = new THREE.Mesh(smallCrystalGeom, smallCrystalMat);
|
||||
const angle = (i / 4) * Math.PI * 2;
|
||||
smallCrys.position.set(Math.cos(angle) * 3.2, Math.sin(angle) * 3.2, 0);
|
||||
coinGroup.add(smallCrys);
|
||||
smallCrystals.push(smallCrys);
|
||||
}
|
||||
|
||||
scene.add(coinGroup);
|
||||
|
||||
// Floating particles (energy dust)
|
||||
const particleCount = 120;
|
||||
const particleGeometry = new THREE.BufferGeometry();
|
||||
const particlePositions = new Float32Array(particleCount * 3);
|
||||
|
||||
for (let i = 0; i < particleCount * 3; i += 3) {
|
||||
// Circle layout with random spreads
|
||||
const radius = 2 + Math.random() * 8;
|
||||
const theta = Math.random() * Math.PI * 2;
|
||||
const phi = Math.acos(2 * Math.random() - 1);
|
||||
|
||||
particlePositions[i] = radius * Math.sin(phi) * Math.cos(theta);
|
||||
particlePositions[i + 1] = radius * Math.sin(phi) * Math.sin(theta);
|
||||
particlePositions[i + 2] = radius * Math.cos(phi);
|
||||
}
|
||||
|
||||
particleGeometry.setAttribute(
|
||||
"position",
|
||||
new THREE.BufferAttribute(particlePositions, 3)
|
||||
);
|
||||
|
||||
// Dynamic glowing points
|
||||
const pMaterial = new THREE.PointsMaterial({
|
||||
color: 0xffffff,
|
||||
size: 0.05,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
blending: THREE.AdditiveBlending,
|
||||
});
|
||||
|
||||
const particleSystem = new THREE.Points(particleGeometry, pMaterial);
|
||||
scene.add(particleSystem);
|
||||
|
||||
// Mouse interaction states
|
||||
let mouseX = 0;
|
||||
let mouseY = 0;
|
||||
let targetX = 0;
|
||||
let targetY = 0;
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
|
||||
mouseX = (x / width) * 2 - 1;
|
||||
mouseY = -(y / height) * 2 + 1;
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
|
||||
// Animation Loop
|
||||
let animationFrameId: number;
|
||||
let clock = new THREE.Clock();
|
||||
|
||||
const animate = () => {
|
||||
const elapsedTime = clock.getElapsedTime();
|
||||
|
||||
// Slow passive rotation
|
||||
coinGroup.rotation.y = elapsedTime * 0.4;
|
||||
coinGroup.rotation.x = Math.sin(elapsedTime * 0.5) * 0.2;
|
||||
|
||||
// Outer ring counter-rotation
|
||||
ring.rotation.z = -elapsedTime * 0.6;
|
||||
|
||||
// Small crystals float around
|
||||
smallCrystals.forEach((crys, idx) => {
|
||||
const offset = idx * (Math.PI / 2);
|
||||
crys.position.y = Math.sin(elapsedTime * 1.5 + offset) * 3.4;
|
||||
crys.position.x = Math.cos(elapsedTime * 1.5 + offset) * 3.4;
|
||||
crys.rotation.x += 0.01;
|
||||
crys.rotation.y += 0.02;
|
||||
});
|
||||
|
||||
// Drifting particles
|
||||
particleSystem.rotation.y = elapsedTime * 0.05;
|
||||
particleSystem.rotation.x = Math.sin(elapsedTime * 0.02) * 0.1;
|
||||
|
||||
// Smooth mouse follow (LERP)
|
||||
targetX += (mouseX - targetX) * 0.05;
|
||||
targetY += (mouseY - targetY) * 0.05;
|
||||
|
||||
coinGroup.position.x = targetX * 1.5;
|
||||
coinGroup.position.y = targetY * 1.5;
|
||||
|
||||
// Dynamic lighting follow
|
||||
goldLight.position.x = 4 + targetX * 2;
|
||||
goldLight.position.y = 3 + targetY * 2;
|
||||
|
||||
renderer.render(scene, camera);
|
||||
animationFrameId = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
// Resize Handler
|
||||
const handleResize = () => {
|
||||
if (!container) return;
|
||||
const newWidth = container.clientWidth;
|
||||
const newHeight = container.clientHeight;
|
||||
|
||||
camera.aspect = newWidth / newHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
renderer.setSize(newWidth, newHeight);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
container.removeChild(renderer.domElement);
|
||||
|
||||
// Dispose materials/geometries to prevent memory leaks
|
||||
crystalGeom.dispose();
|
||||
crystalMat.dispose();
|
||||
ringGeom.dispose();
|
||||
ringMat.dispose();
|
||||
smallCrystalGeom.dispose();
|
||||
smallCrystalMat.dispose();
|
||||
particleGeometry.dispose();
|
||||
pMaterial.dispose();
|
||||
renderer.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="w-full h-full relative">
|
||||
<div className="absolute inset-0 bg-radial-[circle_at_center,_transparent_60%,_var(--color-dark)_100%] pointer-events-none" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user