Skip to content

Commit bd3b1c3

Browse files
committed
feat: dots on project cards.
1 parent 8663eee commit bd3b1c3

File tree

3 files changed

+101
-8
lines changed

3 files changed

+101
-8
lines changed

src/components/Dot.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, { useEffect, useState } from 'react';
2+
3+
const Dot = ({ id, size, color, initialX, initialY, animationDuration, onAnimationEnd }) => {
4+
const [isVisible, setIsVisible] = useState(true);
5+
6+
useEffect(() => {
7+
const timer = setTimeout(() => {
8+
setIsVisible(false);
9+
onAnimationEnd(id);
10+
}, animationDuration * 1000); // Convert seconds to milliseconds
11+
12+
return () => clearTimeout(timer);
13+
}, [animationDuration, id, onAnimationEnd]);
14+
15+
if (!isVisible) {
16+
return null;
17+
}
18+
19+
const dotStyle = {
20+
position: 'absolute',
21+
left: `${initialX}px`,
22+
top: `${initialY}px`,
23+
width: `${size}px`,
24+
height: `${size}px`,
25+
backgroundColor: color,
26+
opacity: 0.7,
27+
animation: `moveAndFade ${animationDuration}s linear forwards`,
28+
zIndex: 0, // Ensure dots are behind content
29+
};
30+
31+
// Define keyframes dynamically or ensure they are globally available
32+
// For now, we'll assume keyframes are defined in index.css or similar
33+
// This is a placeholder for the actual CSS animation
34+
return (
35+
<div style={dotStyle} />
36+
);
37+
};
38+
39+
export default Dot;

src/components/ProjectCard.js

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,59 @@
1-
import React from 'react';
1+
import React, { useState, useEffect, useRef, useCallback } from 'react';
22
import { Link } from 'react-router-dom';
33
import { FaExternalLinkAlt } from 'react-icons/fa';
4+
import Dot from './Dot'; // Import the Dot component
45

56
const 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>

src/index.css

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,23 @@ code {
131131
}
132132

133133
/* Hide arrows from number inputs */
134-
input[type=number]::-webkit-inner-spin-button,
135-
input[type=number]::-webkit-outer-spin-button {
136-
-webkit-appearance: none;
137-
margin: 0;
134+
input[type=number]::-webkit-inner-spin-button,
135+
input[type=number]::-webkit-outer-spin-button {
136+
-webkit-appearance: none;
137+
margin: 0;
138138
}
139139

140140
input[type=number] {
141141
-moz-appearance: textfield;
142142
}
143+
144+
@keyframes moveAndFade {
145+
0% {
146+
transform: translateY(0);
147+
opacity: 0.7;
148+
}
149+
100% {
150+
transform: translateY(calc(100% + 50px)); /* Move beyond the bottom edge */
151+
opacity: 0;
152+
}
153+
}

0 commit comments

Comments
 (0)