"use client"; import { Float, MeshDistortMaterial } from "@react-three/drei"; import { Canvas, useFrame, useThree } from "@react-three/fiber"; import { useCallback, useEffect, useMemo, useRef } from "react"; import * as THREE from "three"; type HeroSceneVariant = | "command" | "launch" | "rescue" | "signal" | "diagnostic" | "portrait"; function ParticleField({ count = 2000 }: { count?: number }) { const mesh = useRef(null); const mouseRef = useRef({ x: 0, y: 0 }); const [positions, velocities] = useMemo(() => { const pos = new Float32Array(count * 3); const vel = new Float32Array(count * 3); for (let i = 0; i < count; i++) { const i3 = i * 3; pos[i3] = (Math.random() - 0.5) * 20; pos[i3 + 1] = (Math.random() - 0.5) * 20; pos[i3 + 2] = (Math.random() - 0.5) * 15; vel[i3] = (Math.random() - 0.5) * 0.002; vel[i3 + 1] = (Math.random() - 0.5) * 0.002; vel[i3 + 2] = (Math.random() - 0.5) * 0.001; } return [pos, vel]; }, [count]); const sizes = useMemo(() => { const s = new Float32Array(count); for (let i = 0; i < count; i++) { s[i] = Math.random() * 2 + 0.5; } return s; }, [count]); const handlePointerMove = useCallback( (e: { clientX: number; clientY: number }) => { mouseRef.current.x = (e.clientX / window.innerWidth) * 2 - 1; mouseRef.current.y = -(e.clientY / window.innerHeight) * 2 + 1; }, [], ); useFrame((state) => { if (!mesh.current) return; const geo = mesh.current.geometry; const posAttr = geo.getAttribute("position") as THREE.BufferAttribute; const arr = posAttr.array as Float32Array; const time = state.clock.elapsedTime; for (let i = 0; i < count; i++) { const i3 = i * 3; arr[i3] += velocities[i3] + Math.sin(time * 0.3 + i) * 0.001; arr[i3 + 1] += velocities[i3 + 1] + Math.cos(time * 0.2 + i) * 0.001; arr[i3 + 2] += velocities[i3 + 2]; // Mouse influence const dx = mouseRef.current.x * 5 - arr[i3]; const dy = mouseRef.current.y * 5 - arr[i3 + 1]; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 3) { const force = (3 - dist) * 0.0003; arr[i3] += dx * force; arr[i3 + 1] += dy * force; } // Wrap around if (arr[i3] > 10) arr[i3] = -10; if (arr[i3] < -10) arr[i3] = 10; if (arr[i3 + 1] > 10) arr[i3 + 1] = -10; if (arr[i3 + 1] < -10) arr[i3 + 1] = 10; if (arr[i3 + 2] > 7.5) arr[i3 + 2] = -7.5; if (arr[i3 + 2] < -7.5) arr[i3 + 2] = 7.5; } posAttr.needsUpdate = true; // Slow rotation mesh.current.rotation.y = time * 0.02; mesh.current.rotation.x = Math.sin(time * 0.01) * 0.1; }); useEffect(() => { window.addEventListener("pointermove", handlePointerMove); return () => window.removeEventListener("pointermove", handlePointerMove); }, [handlePointerMove]); return ( ); } function FloatingGeometry({ variant }: { variant: HeroSceneVariant }) { const meshRef = useRef(null); const mesh2Ref = useRef(null); const mesh3Ref = useRef(null); const layout = { command: { a: [4, 1.5, -3] as [number, number, number], b: [-5, -2, -4] as [number, number, number], c: [2, -3, -5] as [number, number, number], scale: [1.2, 0.8, 1.5] as [number, number, number], }, launch: { a: [3.8, 2.1, -3] as [number, number, number], b: [0, -1.4, -4] as [number, number, number], c: [-3.8, -2.6, -5] as [number, number, number], scale: [0.9, 0.9, 1.8] as [number, number, number], }, rescue: { a: [-3.6, 1.2, -3] as [number, number, number], b: [3.6, 1.2, -4] as [number, number, number], c: [0, -2.8, -5] as [number, number, number], scale: [1, 1, 1.4] as [number, number, number], }, signal: { a: [-4.2, -1, -3] as [number, number, number], b: [0.2, 1.6, -4] as [number, number, number], c: [4.2, -0.6, -5] as [number, number, number], scale: [0.7, 0.7, 1.2] as [number, number, number], }, diagnostic: { a: [4.6, 0.6, -3] as [number, number, number], b: [-3.5, 1.8, -4] as [number, number, number], c: [-1.2, -2.8, -5] as [number, number, number], scale: [1.4, 0.7, 1.1] as [number, number, number], }, portrait: { a: [3.2, 0.4, -3] as [number, number, number], b: [4.8, 2.1, -4] as [number, number, number], c: [-3.6, -2.4, -5] as [number, number, number], scale: [1.8, 0.5, 1.1] as [number, number, number], }, }[variant]; useFrame((state) => { const t = state.clock.elapsedTime; if (meshRef.current) { meshRef.current.rotation.x = t * 0.15; meshRef.current.rotation.y = t * 0.1; meshRef.current.position.y = layout.a[1] + Math.sin(t * 0.4) * 0.3; } if (mesh2Ref.current) { mesh2Ref.current.rotation.x = t * 0.1; mesh2Ref.current.rotation.z = t * 0.12; mesh2Ref.current.position.y = layout.b[1] + Math.cos(t * 0.3) * 0.5; } if (mesh3Ref.current) { mesh3Ref.current.rotation.y = t * 0.08; mesh3Ref.current.rotation.z = t * 0.06; } }); return ( <> {variant === "diagnostic" ? ( ) : variant === "portrait" ? ( ) : ( )} {variant === "launch" ? ( ) : variant === "signal" ? ( ) : ( )} {variant === "rescue" ? ( ) : variant === "signal" ? ( ) : ( )} ); } function GridFloor() { const meshRef = useRef(null); useFrame((state) => { if (meshRef.current) { const material = meshRef.current.material as THREE.ShaderMaterial; if (material.uniforms) { material.uniforms.uTime.value = state.clock.elapsedTime; } } }); const gridShader = useMemo( () => ({ uniforms: { uTime: { value: 0 }, uColor: { value: new THREE.Color("#ccff00") }, }, vertexShader: ` varying vec2 vUv; varying vec3 vPosition; void main() { vUv = uv; vPosition = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform float uTime; uniform vec3 uColor; varying vec2 vUv; varying vec3 vPosition; void main() { vec2 grid = abs(fract(vPosition.xz * 0.5) - 0.5) / fwidth(vPosition.xz * 0.5); float line = min(grid.x, grid.y); float alpha = 1.0 - min(line, 1.0); // Distance fade float dist = length(vPosition.xz) * 0.08; alpha *= max(0.0, 1.0 - dist); alpha *= 0.12; // Pulse wave float wave = sin(length(vPosition.xz) * 0.3 - uTime * 0.8) * 0.5 + 0.5; alpha *= 0.5 + wave * 0.5; gl_FragColor = vec4(uColor, alpha); } `, }), [], ); return ( ); } function CameraRig() { const { camera } = useThree(); const mouseRef = useRef({ x: 0, y: 0 }); useFrame(() => { const targetX = mouseRef.current.x * 0.5; const targetY = mouseRef.current.y * 0.3 + 0.5; camera.position.x += (targetX - camera.position.x) * 0.02; camera.position.y += (targetY - camera.position.y) * 0.02; camera.lookAt(0, 0, 0); }); useEffect(() => { const handler = (e: MouseEvent) => { mouseRef.current.x = (e.clientX / window.innerWidth) * 2 - 1; mouseRef.current.y = -(e.clientY / window.innerHeight) * 2 + 1; }; window.addEventListener("mousemove", handler); return () => window.removeEventListener("mousemove", handler); }, []); return null; } export default function HeroScene({ variant = "command", }: { variant?: HeroSceneVariant; }) { return (
); }