Files
gov/components/VisualEffects.tsx

202 lines
5.3 KiB
TypeScript
Raw Permalink Normal View History

2026-02-04 00:04:31 +05:00
import React, { useRef, useMemo, useEffect } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
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;
}
}
}
// --- WARP EFFECT ---
const WarpParticles = () => {
const count = 2000;
const mesh = useRef<THREE.Points>(null);
// Particles setup
const particles = useMemo(() => {
const temp = [];
for (let i = 0; i < count; i++) {
const x = (Math.random() - 0.5) * 30;
const y = (Math.random() - 0.5) * 60; // Taller spread
const z = (Math.random() - 0.5) * 20;
temp.push(x, y, z);
}
return new Float32Array(temp);
}, [count]);
useFrame((state, delta) => {
if (!mesh.current) return;
const time = state.clock.getElapsedTime();
// Logic:
// 1. Initial burst (0-1s): Speed = 3.0
// 2. Slow down (1s+): Speed = 0.5 (2x slower than a standard '1.0' baseline)
let targetSpeed = 0.5; // Slow cruising speed
if (time < 1.0) {
targetSpeed = 4.0; // Fast entry
} else if (time < 1.8) {
// Smooth deceleration phase
const t = (time - 1.0) / 0.8;
targetSpeed = THREE.MathUtils.lerp(4.0, 0.5, t);
}
const positions = mesh.current.geometry.attributes.position.array as Float32Array;
const moveY = targetSpeed * delta * 10; // Scale speed to movement
for (let i = 0; i < count; i++) {
// Move particles up
positions[i * 3 + 1] += moveY;
// Seamless wrap around
if (positions[i * 3 + 1] > 30) {
positions[i * 3 + 1] -= 60; // Subtract height to wrap seamlessly
// Randomize X/Z on respawn to create new star patterns
positions[i * 3] = (Math.random() - 0.5) * 30;
positions[i * 3 + 2] = (Math.random() - 0.5) * 20;
}
}
mesh.current.geometry.attributes.position.needsUpdate = true;
// Stretch effect based on speed
// Higher speed = more vertical stretch
mesh.current.scale.y = 1 + targetSpeed * 0.5;
});
return (
<points ref={mesh}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={particles.length / 3}
array={particles}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial
size={0.08}
color="#ffffff"
transparent
opacity={0.6}
sizeAttenuation
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</points>
);
};
export const WarpOverlay = () => {
return (
<div className="absolute inset-0 z-40 pointer-events-none mix-blend-screen">
<Canvas
camera={{ position: [0, 0, 15], fov: 60 }}
gl={{ alpha: true }}
style={{ pointerEvents: 'none' }} // Explicitly disable pointer events on canvas element
>
<WarpParticles />
</Canvas>
</div>
);
};
// --- SAND EFFECT ---
const SandParticles = () => {
const count = 4000;
const mesh = useRef<THREE.Points>(null);
const particles = useMemo(() => {
const temp = [];
for (let i = 0; i < count; i++) {
const x = (Math.random() - 0.5) * 25;
const y = (Math.random() - 0.5) * 25;
const z = (Math.random() - 0.5) * 10;
temp.push(x, y, z);
}
return new Float32Array(temp);
}, [count]);
useFrame((state) => {
if (!mesh.current) return;
const positions = mesh.current.geometry.attributes.position.array as Float32Array;
const time = state.clock.getElapsedTime();
for (let i = 0; i < count; i++) {
const ix = i * 3;
const iy = i * 3 + 1;
const iz = i * 3 + 2;
// Chaotic wind movement
positions[ix] += Math.sin(time * 0.5 + positions[iy] * 0.5) * 0.02;
positions[iy] += Math.cos(time * 0.3 + positions[ix] * 0.5) * 0.01;
// Wrap around
if (Math.abs(positions[ix]) > 12) positions[ix] *= -0.9;
if (Math.abs(positions[iy]) > 12) positions[iy] *= -0.9;
}
mesh.current.geometry.attributes.position.needsUpdate = true;
mesh.current.rotation.y = time * 0.05;
});
return (
<points ref={mesh}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={particles.length / 3}
array={particles}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial
size={0.05}
color="#c4c3be"
transparent
opacity={0.8}
sizeAttenuation
depthWrite={false}
/>
</points>
);
};
export const SandOverlay = () => {
return (
<div className="absolute inset-0 z-40 pointer-events-none">
<Canvas
camera={{ position: [0, 0, 10], fov: 60 }}
gl={{ alpha: true }}
style={{ pointerEvents: 'none' }} // Explicitly disable pointer events on canvas element
>
<ambientLight intensity={1} />
<SandParticles />
</Canvas>
</div>
);
};