Spaces:
Sleeping
Sleeping
| import { useState, useRef, useCallback } from 'react'; | |
| /** | |
| * Hook for managing particle effects | |
| * @param {React.RefObject} containerRef - Reference to container element | |
| * @param {boolean} isMobile - Whether the device is mobile | |
| * @returns {Object} Particle effects state and functions | |
| */ | |
| export const useParticleEffects = (containerRef, isMobile) => { | |
| const [particles, setParticles] = useState([]); | |
| const [popParticles, setPopParticles] = useState([]); | |
| const particleId = useRef(0); | |
| const isComponentMounted = useRef(true); | |
| // Handler for sparkle particles | |
| const handleCreateSparkleParticles = useCallback((x, y) => { | |
| const newParticles = createSparkleParticles(x, y, containerRef, isMobile, particleId); | |
| setParticles(prev => [...prev, ...newParticles]); | |
| setTimeout(() => { | |
| if (isComponentMounted.current) { | |
| setParticles(prev => prev.filter(p => !newParticles.find(np => np.id === p.id))); | |
| } | |
| }, 1000); | |
| }, [isMobile, containerRef]); | |
| // Handler for pop particles | |
| const handleCreatePopParticles = useCallback((x, y) => { | |
| const newParticles = createPopParticles(x, y, containerRef, isMobile, particleId); | |
| setPopParticles(prev => [...prev, ...newParticles]); | |
| setTimeout(() => { | |
| if (isComponentMounted.current) { | |
| setPopParticles(prev => prev.filter(p => !newParticles.find(np => np.id === p.id))); | |
| } | |
| }, 600); | |
| }, [isMobile, containerRef]); | |
| return { | |
| particles, | |
| popParticles, | |
| createSparkleParticles: handleCreateSparkleParticles, | |
| createPopParticles: handleCreatePopParticles, | |
| isComponentMounted | |
| }; | |
| }; | |
| /** | |
| * Particle effects component | |
| */ | |
| const ParticleEffects = ({ particles, popParticles }) => { | |
| return ( | |
| <div className="absolute inset-0 pointer-events-none overflow-hidden"> | |
| {/* Render sparkle particles */} | |
| {particles.map(particle => ( | |
| <div | |
| key={particle.id} | |
| className="particle" | |
| style={particle.style} | |
| /> | |
| ))} | |
| {/* Render pop particles */} | |
| {popParticles.map(particle => ( | |
| <div | |
| key={particle.id} | |
| className="pop-particle" | |
| style={particle.style} | |
| /> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| /** | |
| * Create sparkle particles | |
| * @param {number} x - X position | |
| * @param {number} y - Y position | |
| * @param {React.RefObject} containerRef - Reference to container element | |
| * @param {boolean} isMobile - Whether the device is mobile | |
| * @param {React.MutableRefObject} particleId - Reference to particle ID counter | |
| * @returns {Array} - Array of particle objects | |
| */ | |
| export const createSparkleParticles = (x, y, containerRef, isMobile, particleId) => { | |
| const newParticles = []; | |
| const numParticles = isMobile ? 8 : 12; | |
| // Get container position for relative positioning | |
| const containerRect = containerRef.current.getBoundingClientRect(); | |
| const relativeX = x - containerRect.left; | |
| const relativeY = y - containerRect.top; | |
| for (let i = 0; i < numParticles; i++) { | |
| const angle = (Math.PI * 2 * i) / numParticles; | |
| const velocity = 2 + Math.random() * 2; | |
| const size = isMobile ? 3 + Math.random() * 2 : 4 + Math.random() * 3; | |
| const tx = Math.cos(angle) * (30 + Math.random() * 20); | |
| const ty = Math.sin(angle) * (30 + Math.random() * 20); | |
| const rotation = Math.random() * 360; | |
| newParticles.push({ | |
| id: particleId.current++, | |
| x: relativeX, | |
| y: relativeY, | |
| size, | |
| style: { | |
| '--tx': `${tx}px`, | |
| '--ty': `${ty}px`, | |
| '--r': `${rotation}deg`, | |
| width: `${size}px`, | |
| height: `${size}px`, | |
| left: `${relativeX}px`, | |
| top: `${relativeY}px` | |
| } | |
| }); | |
| } | |
| return newParticles; | |
| }; | |
| /** | |
| * Create pop particles | |
| * @param {number} x - X position | |
| * @param {number} y - Y position | |
| * @param {React.RefObject} containerRef - Reference to container element | |
| * @param {boolean} isMobile - Whether the device is mobile | |
| * @param {React.MutableRefObject} particleId - Reference to particle ID counter | |
| * @returns {Array} - Array of particle objects | |
| */ | |
| export const createPopParticles = (x, y, containerRef, isMobile, particleId) => { | |
| const newParticles = []; | |
| const numParticles = isMobile ? 6 : 8; | |
| // Get container position for relative positioning | |
| const containerRect = containerRef.current.getBoundingClientRect(); | |
| const relativeX = x - containerRect.left; | |
| const relativeY = y - containerRect.top; | |
| for (let i = 0; i < numParticles; i++) { | |
| const angle = (Math.PI * 2 * i) / numParticles; | |
| const velocity = 3 + Math.random() * 2; | |
| const size = isMobile ? 4 + Math.random() * 3 : 6 + Math.random() * 4; | |
| const tx = Math.cos(angle) * (40 + Math.random() * 20); | |
| const ty = Math.sin(angle) * (40 + Math.random() * 20); | |
| const rotation = Math.random() * 360; | |
| newParticles.push({ | |
| id: particleId.current++, | |
| x: relativeX, | |
| y: relativeY, | |
| size, | |
| style: { | |
| '--tx': `${tx}px`, | |
| '--ty': `${ty}px`, | |
| '--r': `${rotation}deg`, | |
| width: `${size}px`, | |
| height: `${size}px`, | |
| left: `${relativeX}px`, | |
| top: `${relativeY}px` | |
| } | |
| }); | |
| } | |
| return newParticles; | |
| }; | |
| export default ParticleEffects; |