1- import React from 'react' ;
1+ import React , { useState , useEffect , useRef , useCallback } from 'react' ;
22import { Link } from 'react-router-dom' ;
33import { FaExternalLinkAlt } from 'react-icons/fa' ;
4+ import Dot from './Dot' ; // Import the Dot component
45
56const ProjectCard = ( { project, size = 1 } ) => {
67 const colSpanClass =
78 size === 2 ? 'md:col-span-2' : size === 3 ? 'md:col-span-3' : 'col-span-1' ;
89
10+ const [ dots , setDots ] = useState ( [ ] ) ;
11+ const cardRef = useRef ( null ) ;
12+ const dotIdRef = useRef ( 0 ) ;
13+
14+ const handleAnimationEnd = useCallback ( ( id ) => {
15+ setDots ( ( prevDots ) => prevDots . filter ( ( dot ) => dot . id !== id ) ) ;
16+ } , [ ] ) ;
17+
18+ useEffect ( ( ) => {
19+ const spawnDot = ( ) => {
20+ if ( cardRef . current && dots . length < 20 ) {
21+ const cardRect = cardRef . current . getBoundingClientRect ( ) ;
22+ const newDot = {
23+ id : dotIdRef . current ++ ,
24+ size : Math . floor ( Math . random ( ) * 5 ) + 3 ,
25+ color : `hsl(0, ${ Math . floor ( Math . random ( ) * 30 ) + 70 } %, ${ Math . floor ( Math . random ( ) * 20 ) + 60 } %)` , // Tones of red
26+ initialX : Math . random ( ) * cardRect . width ,
27+ initialY : - 5 , // Start slightly off-screen top
28+ animationDuration : Math . random ( ) * 3 + 2 , // Duration between 2 and 5 seconds
29+ } ;
30+ setDots ( ( prevDots ) => [ ...prevDots , newDot ] ) ;
31+ }
32+ } ;
33+
34+ const interval = setInterval ( spawnDot , 500 ) ; // Spawn a new dot every 0.5 seconds
35+
36+ return ( ) => clearInterval ( interval ) ;
37+ } , [ dots . length ] ) ;
38+
939 return (
1040 < div
11- className = { `block bg-gray-500/10 p-6 rounded-lg shadow-lg hover:bg-gray-500/20 transition-colors border border-gray-700/50 cursor-pointer flex flex-col ${ colSpanClass } ` }
41+ ref = { cardRef }
42+ className = { `block bg-gray-500/10 p-6 rounded-lg shadow-lg hover:bg-gray-500/20 transition-colors border border-gray-700/50 cursor-pointer flex flex-col relative overflow-hidden ${ colSpanClass } ` }
1243 >
13- < Link to = { `/projects/${ project . slug } ` } className = "flex flex-col flex-grow" >
44+ { dots . map ( ( dot ) => (
45+ < Dot
46+ key = { dot . id }
47+ id = { dot . id }
48+ size = { dot . size }
49+ color = { dot . color }
50+ initialX = { dot . initialX }
51+ initialY = { dot . initialY }
52+ animationDuration = { dot . animationDuration }
53+ onAnimationEnd = { handleAnimationEnd }
54+ />
55+ ) ) }
56+ < Link to = { `/projects/${ project . slug } ` } className = "flex flex-col flex-grow relative z-10" >
1457 < h3 className = "text-xl font-semibold text-white" > { project . title } </ h3 >
1558 < p className = "mt-2 text-gray-400 flex-grow" > { project . description } </ p >
1659 </ Link >
@@ -19,7 +62,7 @@ const ProjectCard = ({ project, size = 1 }) => {
1962 href = { project . link }
2063 target = "_blank"
2164 rel = "noopener noreferrer"
22- className = "mt-4 inline-block text-red-500 hover:text-red-300 transition-colors mt-auto flex items-center"
65+ className = "mt-4 inline-block text-red-500 hover:text-red-300 transition-colors mt-auto flex items-center relative z-10 "
2366 >
2467 View Project < FaExternalLinkAlt className = "ml-1" size = { 12 } />
2568 </ a >
0 commit comments