Skip to content

Commit 465e01a

Browse files
committed
finalised the design for cards and made it smooth and functional
1 parent 95a68a5 commit 465e01a

File tree

1 file changed

+90
-107
lines changed

1 file changed

+90
-107
lines changed

src/components/Projects.jsx

Lines changed: 90 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,37 @@ const projectsData = [
7575
// ProjectCard component
7676
const ProjectCard = ({ project, index, openModal }) => {
7777
const [isHovered, setIsHovered] = useState(false);
78+
const cardRef = useRef(null);
79+
80+
useEffect(() => {
81+
const observer = new IntersectionObserver(
82+
(entries) => {
83+
entries.forEach((entry) => {
84+
if (entry.isIntersecting) {
85+
entry.target.classList.add(
86+
index % 2 === 0 ? 'animate-slide-in-card-left' : 'animate-slide-in-card-right'
87+
);
88+
observer.unobserve(entry.target);
89+
}
90+
});
91+
},
92+
{ threshold: 0.2 }
93+
);
94+
95+
if (cardRef.current) {
96+
observer.observe(cardRef.current);
97+
}
98+
99+
return () => observer.disconnect();
100+
}, [index]);
78101

79102
return (
80103
<div
104+
ref={cardRef}
81105
className={`group relative overflow-hidden bg-gradient-to-br from-gray-900/90 to-gray-800/90
82106
backdrop-blur-lg rounded-xl shadow-lg transition-all duration-500 hover:shadow-2xl
83107
hover:shadow-blue-500/20 border border-gray-700/50 hover:border-blue-500/50
84-
transform hover:-translate-y-1`}
108+
transform hover:-translate-y-1 opacity-0 flex flex-col h-full`}
85109
onMouseEnter={() => setIsHovered(true)}
86110
onMouseLeave={() => setIsHovered(false)}
87111
>
@@ -102,76 +126,82 @@ const ProjectCard = ({ project, index, openModal }) => {
102126
{/* Project status badge */}
103127
{project.status && (
104128
<span className="absolute top-4 right-4 px-3 py-1.5 text-xs font-semibold rounded-full
105-
bg-green-500/20 text-green-400 backdrop-blur-sm border border-green-500/30">
129+
bg-green-500/20 text-green-400 backdrop-blur-sm border border-green-500/30 animate-scale-in">
106130
{project.status}
107131
</span>
108132
)}
109133
</div>
110134

111-
{/* Content section */}
112-
<div className="relative p-6 space-y-6">
113-
{/* Title and buttons */}
114-
<div className="flex justify-between items-start gap-4">
115-
<h3 className="text-2xl font-bold text-white group-hover:text-blue-400
116-
transition-colors duration-300">
117-
{project.title}
118-
</h3>
119-
<div className="flex gap-2">
120-
<a
121-
href={project.github}
122-
target="_blank"
123-
rel="noopener noreferrer"
124-
className="p-2 rounded-lg bg-gray-700/50 hover:bg-gray-600 transition-all
125-
duration-300 hover:scale-110 hover:shadow-lg hover:shadow-blue-500/20"
126-
>
127-
<Github size={20} className="text-gray-300 hover:text-white" />
128-
</a>
135+
{/* Content wrapper - Added to create consistent spacing */}
136+
<div className="flex flex-col flex-1 p-6">
137+
{/* Main content section */}
138+
<div className="flex flex-col flex-1 space-y-6">
139+
{/* Title and buttons */}
140+
<div className="flex justify-between items-start gap-4">
141+
<h3 className="text-2xl font-bold text-white group-hover:text-blue-400
142+
transition-colors duration-300">
143+
{project.title}
144+
</h3>
145+
<div className="flex gap-2">
146+
<a
147+
href={project.github}
148+
target="_blank"
149+
rel="noopener noreferrer"
150+
className="p-2 rounded-lg bg-gray-700/50 hover:bg-gray-600 transition-all
151+
duration-300 hover:scale-110 hover:shadow-lg hover:shadow-blue-500/20"
152+
>
153+
<Github size={20} className="text-gray-300 hover:text-white" />
154+
</a>
155+
</div>
129156
</div>
130-
</div>
131157

132-
{/* Description */}
133-
<div className="prose prose-invert prose-sm max-w-none">
134-
<ReactMarkdown remarkPlugins={[remarkGfm]} className="line-clamp-3">
135-
{project.description.split('\n')[0]}
136-
</ReactMarkdown>
137-
</div>
158+
{/* Description */}
159+
<div className="prose prose-invert prose-sm max-w-none">
160+
<ReactMarkdown remarkPlugins={[remarkGfm]} className="line-clamp-3">
161+
{project.description.split('\n')[0]}
162+
</ReactMarkdown>
163+
</div>
138164

139-
{/* Tech stack */}
140-
<div className="space-y-3">
141-
<h4 className="text-sm font-medium text-gray-400 uppercase tracking-wider">
142-
Technologies
143-
</h4>
144-
<div className="flex flex-wrap gap-2">
145-
{project.technologies.map((tech, i) => (
146-
<span
147-
key={tech}
148-
className="px-3 py-1.5 text-xs font-medium rounded-full bg-blue-500/10
149-
text-blue-400 border border-blue-500/30 hover:bg-blue-500/20
150-
transition-all duration-300 transform hover:scale-105"
151-
style={{
152-
transitionDelay: `${i * 50}ms`,
153-
animation: isHovered ? `fadeIn 500ms ${i * 50}ms forwards` : 'none',
154-
}}
155-
>
156-
{tech}
157-
</span>
158-
))}
165+
{/* Tech stack */}
166+
<div className="space-y-3">
167+
<h4 className="text-sm font-medium text-gray-400 uppercase tracking-wider">
168+
Technologies
169+
</h4>
170+
<div className="flex flex-wrap gap-2">
171+
{project.technologies.map((tech, i) => (
172+
<span
173+
key={tech}
174+
className="px-3 py-1.5 text-xs font-medium rounded-full bg-blue-500/10
175+
text-blue-400 border border-blue-500/30 hover:bg-blue-500/20
176+
transition-all duration-300 transform hover:scale-105 animate-tech-tag-pop"
177+
style={{
178+
transitionDelay: `${i * 50}ms`,
179+
animationDelay: `${i * 50}ms`,
180+
}}
181+
>
182+
{tech}
183+
</span>
184+
))}
185+
</div>
159186
</div>
160187
</div>
161188

162-
{/* View details button */}
163-
<button
164-
onClick={() => openModal(project)}
165-
className="w-full mt-4 px-4 py-3 flex items-center justify-center gap-2
166-
bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-500
167-
hover:to-blue-400 text-white rounded-lg transition-all duration-300
168-
transform hover:scale-[1.02] hover:shadow-lg hover:shadow-blue-500/25
169-
focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900"
170-
>
171-
<Code size={18} />
172-
View Project Details
173-
<ExternalLink size={18} className="ml-1" />
174-
</button>
189+
{/* Button section - Now in a separate div outside the flex-1 content */}
190+
<div className="pt-6">
191+
<button
192+
onClick={() => openModal(project)}
193+
className="w-full px-4 py-3 flex items-center justify-center gap-2
194+
bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-500
195+
hover:to-blue-400 text-white rounded-lg transition-all duration-300
196+
transform hover:scale-[1.02] hover:shadow-lg hover:shadow-blue-500/25
197+
focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900 animate-fade-up"
198+
style={{ animationDelay: '200ms' }}
199+
>
200+
<Code size={18} />
201+
View Project Details
202+
<ExternalLink size={18} className="ml-1" />
203+
</button>
204+
</div>
175205
</div>
176206
</div>
177207
);
@@ -256,53 +286,6 @@ const Projects = () => {
256286
return () => observer.disconnect();
257287
}, []);
258288

259-
// Project cards animation observer
260-
useEffect(() => {
261-
const projectCards = document.querySelectorAll('.project-card');
262-
const observer = new IntersectionObserver(
263-
(entries) => {
264-
entries.forEach((entry) => {
265-
if (entry.isIntersecting) {
266-
const card = entry.target;
267-
const projectTitle = card.querySelector('h3').textContent;
268-
const project = projectsData.find((p) => p.title === projectTitle);
269-
const index = Array.from(projectCards).indexOf(card);
270-
271-
if (!animatedProjects.includes(project.title)) {
272-
if (index % 2 === 0) {
273-
card.classList.add('animate-slide-in-card-left');
274-
} else {
275-
card.classList.add('animate-slide-in-card-right');
276-
}
277-
278-
const tags = card.querySelectorAll('.tech-tag');
279-
tags.forEach((tag, i) => {
280-
tag.style.transitionDelay = `${(index * 200) + (i * 50)}ms`;
281-
tag.classList.add('opacity-100', 'scale-100');
282-
});
283-
284-
setAnimatedProjects((prev) => [...prev, project.title]);
285-
}
286-
}
287-
});
288-
},
289-
{ threshold: 0.2 }
290-
);
291-
292-
projectCards.forEach((card) => observer.observe(card));
293-
return () => observer.disconnect();
294-
}, [animatedProjects]);
295-
296-
// Helper function to get the first paragraph and format it
297-
const getShortDescription = (description) => {
298-
const firstParagraph = description.split('\n')[0];
299-
return (
300-
<ReactMarkdown remarkPlugins={[remarkGfm]} className="text-gray-300 mb-6 line-clamp-3">
301-
{firstParagraph}
302-
</ReactMarkdown>
303-
);
304-
};
305-
306289
const openModal = (project) => {
307290
setSelectedProject(project);
308291
setModalOpen(true);

0 commit comments

Comments
 (0)