167 lines
4.9 KiB
TypeScript
167 lines
4.9 KiB
TypeScript
|
|
|
||
|
|
import React, { useRef, useMemo, useEffect } from 'react';
|
||
|
|
import { Canvas, useFrame } from '@react-three/fiber';
|
||
|
|
import { OrbitControls, PerspectiveCamera } from '@react-three/drei';
|
||
|
|
import * as THREE from 'three';
|
||
|
|
|
||
|
|
// Add global declaration to fix TypeScript errors with React Three Fiber elements
|
||
|
|
declare module 'react' {
|
||
|
|
namespace JSX {
|
||
|
|
interface IntrinsicElements {
|
||
|
|
points: any;
|
||
|
|
bufferGeometry: any;
|
||
|
|
bufferAttribute: any;
|
||
|
|
pointsMaterial: any;
|
||
|
|
ambientLight: any;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
declare global {
|
||
|
|
namespace JSX {
|
||
|
|
interface IntrinsicElements {
|
||
|
|
points: any;
|
||
|
|
bufferGeometry: any;
|
||
|
|
bufferAttribute: any;
|
||
|
|
pointsMaterial: any;
|
||
|
|
ambientLight: any;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
interface TechVisualizerProps {
|
||
|
|
mode: 'server' | 'monitor' | 'hidden';
|
||
|
|
}
|
||
|
|
|
||
|
|
const ParticleMorph = ({ mode }: { mode: 'server' | 'monitor' | 'hidden' }) => {
|
||
|
|
const meshRef = useRef<THREE.Points>(null);
|
||
|
|
const COUNT = 3000;
|
||
|
|
|
||
|
|
// Define geometries
|
||
|
|
const data = useMemo(() => {
|
||
|
|
const pos = new Float32Array(COUNT * 3);
|
||
|
|
const col = new Float32Array(COUNT * 3);
|
||
|
|
const serverPos = new Float32Array(COUNT * 3);
|
||
|
|
const monitorPos = new Float32Array(COUNT * 3);
|
||
|
|
|
||
|
|
// SERVER SHAPE (Rectangular Tower)
|
||
|
|
for (let i = 0; i < COUNT; i++) {
|
||
|
|
const x = (Math.random() - 0.5) * 2; // Thin width
|
||
|
|
const y = (Math.random() - 0.5) * 6; // Tall
|
||
|
|
const z = (Math.random() - 0.5) * 2; // Depth
|
||
|
|
|
||
|
|
serverPos[i * 3] = x;
|
||
|
|
serverPos[i * 3 + 1] = y;
|
||
|
|
serverPos[i * 3 + 2] = z;
|
||
|
|
|
||
|
|
// Add some "layers" to make it look like a rack
|
||
|
|
if (Math.random() > 0.8) {
|
||
|
|
serverPos[i * 3] *= 1.2;
|
||
|
|
serverPos[i * 3 + 2] *= 1.2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// MONITOR SHAPE (Curved Plane)
|
||
|
|
for (let i = 0; i < COUNT; i++) {
|
||
|
|
const x = (Math.random() - 0.5) * 8; // Wide
|
||
|
|
const y = (Math.random() - 0.5) * 3.5; // Aspect ratio
|
||
|
|
// Curve the Z based on X (parabolic)
|
||
|
|
const z = Math.pow(x * 0.3, 2) - 2;
|
||
|
|
|
||
|
|
monitorPos[i * 3] = x;
|
||
|
|
monitorPos[i * 3 + 1] = y + 0.5; // Lift up slightly
|
||
|
|
monitorPos[i * 3 + 2] = z;
|
||
|
|
|
||
|
|
// Bezel/Frame particles
|
||
|
|
if (Math.random() > 0.95) {
|
||
|
|
monitorPos[i * 3 + 2] += 0.1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initial Positions (start at server)
|
||
|
|
for (let i = 0; i < COUNT * 3; i++) {
|
||
|
|
pos[i] = serverPos[i];
|
||
|
|
col[i] = 1; // Initial white/cyan mix logic in shader or simple color
|
||
|
|
}
|
||
|
|
|
||
|
|
return { positions: pos, colors: col, serverPos, monitorPos };
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// Buffer attributes
|
||
|
|
const bufferRef = useRef<THREE.BufferAttribute>(null);
|
||
|
|
|
||
|
|
useFrame((state, delta) => {
|
||
|
|
if (!meshRef.current || !bufferRef.current) return;
|
||
|
|
|
||
|
|
const target = mode === 'monitor' ? data.monitorPos : data.serverPos;
|
||
|
|
const current = bufferRef.current.array as Float32Array;
|
||
|
|
|
||
|
|
// Morph speed
|
||
|
|
const speed = 4 * delta;
|
||
|
|
|
||
|
|
// Visibility transition
|
||
|
|
const isHidden = mode === 'hidden';
|
||
|
|
|
||
|
|
for (let i = 0; i < COUNT; i++) {
|
||
|
|
const ix = i * 3;
|
||
|
|
const iy = i * 3 + 1;
|
||
|
|
const iz = i * 3 + 2;
|
||
|
|
|
||
|
|
if (isHidden) {
|
||
|
|
// Explode/Hide
|
||
|
|
current[ix] = THREE.MathUtils.lerp(current[ix], current[ix] * 1.01, speed);
|
||
|
|
current[iy] = THREE.MathUtils.lerp(current[iy], current[iy] + 10, speed);
|
||
|
|
} else {
|
||
|
|
// Standard Morph
|
||
|
|
current[ix] = THREE.MathUtils.lerp(current[ix], target[ix], speed);
|
||
|
|
current[iy] = THREE.MathUtils.lerp(current[iy], target[iy], speed);
|
||
|
|
current[iz] = THREE.MathUtils.lerp(current[iz], target[iz], speed);
|
||
|
|
|
||
|
|
// Add subtle noise/floating
|
||
|
|
current[iy] += Math.sin(state.clock.elapsedTime + current[ix]) * 0.002;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bufferRef.current.needsUpdate = true;
|
||
|
|
|
||
|
|
// Rotate entire mesh slowly
|
||
|
|
meshRef.current.rotation.y += delta * 0.1;
|
||
|
|
});
|
||
|
|
|
||
|
|
return (
|
||
|
|
<points ref={meshRef}>
|
||
|
|
<bufferGeometry>
|
||
|
|
<bufferAttribute
|
||
|
|
ref={bufferRef}
|
||
|
|
attach="attributes-position"
|
||
|
|
array={data.positions}
|
||
|
|
count={data.positions.length / 3}
|
||
|
|
itemSize={3}
|
||
|
|
/>
|
||
|
|
</bufferGeometry>
|
||
|
|
<pointsMaterial
|
||
|
|
size={0.04}
|
||
|
|
color={mode === 'monitor' ? "#ff4b4b" : "#20e3b2"} // Red for monitor (Anime.js accent), Cyan for Server
|
||
|
|
transparent
|
||
|
|
opacity={0.8}
|
||
|
|
sizeAttenuation
|
||
|
|
blending={THREE.AdditiveBlending}
|
||
|
|
/>
|
||
|
|
</points>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
const TechVisualizer: React.FC<TechVisualizerProps> = ({ mode }) => {
|
||
|
|
return (
|
||
|
|
<div className={`fixed inset-0 z-[1] transition-opacity duration-700 pointer-events-none ${mode === 'hidden' ? 'opacity-0' : 'opacity-60'}`}>
|
||
|
|
<Canvas gl={{ antialias: true, alpha: true }}>
|
||
|
|
<PerspectiveCamera makeDefault position={[0, 0, 8]} fov={50} />
|
||
|
|
<ambientLight intensity={0.5} />
|
||
|
|
<ParticleMorph mode={mode} />
|
||
|
|
</Canvas>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default TechVisualizer;
|