Skip to content

Commit 2e24561

Browse files
committed
project section is finished
1 parent 7b5bb2d commit 2e24561

File tree

9 files changed

+318
-61
lines changed

9 files changed

+318
-61
lines changed

package-lock.json

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@fortawesome/free-solid-svg-icons": "^7.1.0",
1616
"@fortawesome/react-fontawesome": "^3.1.0",
1717
"bootstrap": "^5.3.8",
18+
"framer-motion": "^12.23.24",
1819
"react": "^19.1.1",
1920
"react-bootstrap": "^2.10.10",
2021
"react-dom": "^19.1.1",

public/imgs/animated1.gif

581 KB
Loading

public/imgs/enemyGenerator.png

30.4 KB
Loading
-1.02 MB
Binary file not shown.

public/imgs/typescript-icon-svgrepo-com.svg

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/App.css

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,126 @@
1+
.gallery-container {
2+
padding: 2rem;
3+
text-align: center;
4+
}
15

6+
.gallery-title {
7+
font-size: 2rem;
8+
margin-bottom: 1.5rem;
9+
}
10+
11+
/* Filter buttons */
12+
.filter-bar {
13+
display: flex;
14+
flex-wrap: wrap;
15+
justify-content: center;
16+
gap: 0.75rem;
17+
margin-bottom: 2rem;
18+
}
19+
20+
.filter-bar button {
21+
background: #444; /* darker gray so it's visible */
22+
border: none;
23+
border-radius: 20px;
24+
padding: 0.4rem 1rem;
25+
color: #fff; /* white text */
26+
font-size: 0.9rem;
27+
cursor: pointer;
28+
transition: background 0.3s ease, transform 0.2s ease;
29+
}
30+
31+
.filter-bar button:hover {
32+
background: #666; /* slightly lighter gray on hover */
33+
transform: scale(1.05);
34+
}
35+
36+
.filter-bar button.active {
37+
background: #00bfff; /* bright blue for active */
38+
color: #111; /* dark text on active */
39+
font-weight: bold;
40+
}
41+
42+
/* Gallery grid */
43+
.gallery-grid {
44+
display: grid;
45+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
46+
gap: 1.5rem;
47+
}
48+
49+
.gallery-item {
50+
position: relative;
51+
overflow: hidden;
52+
border-radius: 1rem;
53+
cursor: pointer;
54+
transition: transform 0.3s ease;
55+
}
56+
57+
.gallery-item:hover {
58+
transform: scale(1.03);
59+
}
60+
61+
.gallery-item img {
62+
width: 100%;
63+
height: 100%;
64+
object-fit: cover;
65+
display: block;
66+
}
67+
68+
.gallery-item .animated {
69+
display: none;
70+
}
71+
72+
/* On hover, swap static with animated image */
73+
.gallery-item:hover .static {
74+
display: none;
75+
}
76+
77+
.gallery-item:hover .animated {
78+
display: block;
79+
}
80+
81+
/* Tag overlay */
82+
.tags {
83+
position: absolute;
84+
bottom: 0;
85+
left: 0;
86+
right: 0;
87+
display: flex;
88+
flex-wrap: wrap;
89+
gap: 0.5rem;
90+
justify-content: center;
91+
padding: 0.75rem;
92+
background: rgba(0, 0, 0, 0.4);
93+
opacity: 0;
94+
transition: opacity 0.3s ease;
95+
}
96+
97+
.gallery-item:hover .tags {
98+
opacity: 1;
99+
}
100+
101+
.tags span {
102+
background: rgba(255, 255, 255, 0.2);
103+
backdrop-filter: blur(4px);
104+
border-radius: 20px;
105+
padding: 0.3rem 0.75rem;
106+
font-size: 0.85rem;
107+
color: #fff;
108+
white-space: nowrap;
109+
}
110+
111+
/* Fade-in animation for filtered items */
112+
.fade-in {
113+
animation: fadeIn 0.4s ease-in-out;
114+
}
115+
116+
@keyframes fadeIn {
117+
from {
118+
opacity: 0;
119+
transform: scale(0.95);
120+
}
121+
to {
122+
opacity: 1;
123+
transform: scale(1);
124+
}
125+
}
2126

src/components/Projects.tsx

Lines changed: 132 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,140 @@
1-
import { Image } from "./Image";
2-
3-
interface ProjectsProps {
4-
data: {
5-
title: string;
6-
largeImage: string;
7-
smallImage: string;
8-
}[];
1+
// import { AnimatePresence, motion } from "framer-motion";
2+
// import { Image } from "./Image";
3+
import { useState } from "react";
4+
import { motion, AnimatePresence } from "framer-motion";
5+
import "../App.css";
6+
// interface ProjectsProps {
7+
// data: {
8+
// title: string;
9+
// largeImage: string;
10+
// smallImage: string;
11+
// }[];
12+
// }
13+
14+
interface Project {
15+
id: number;
16+
title: string;
17+
staticImg: string;
18+
animatedImg: string;
19+
tags: string[];
20+
link: string;
921
}
1022

11-
const Projects = (props: ProjectsProps) => {
23+
const projects: Project[] = [
24+
{
25+
id: 1,
26+
title: "Random Enemy Generator",
27+
staticImg: "/imgs/enemyGenerator.png",
28+
animatedImg: "/imgs/animated1.gif",
29+
tags: ["HTML5", "CSS3", "JavaScript"],
30+
link: "https://jebarpg.github.io/enemyGenerator",
31+
},
32+
// {
33+
// id: 2,
34+
// title: "Node API Service",
35+
// staticImg: "/images/static2.jpg",
36+
// animatedImg: "/images/animated2.gif",
37+
// tags: ["Node.js"],
38+
// },
39+
// {
40+
// id: 3,
41+
// title: "Phaser Game",
42+
// staticImg: "/images/static3.jpg",
43+
// animatedImg: "/images/animated3.gif",
44+
// tags: ["JavaScript"],
45+
// },
46+
// {
47+
// id: 4,
48+
// title: "Fullstack App",
49+
// staticImg: "/images/static4.jpg",
50+
// animatedImg: "/images/animated4.gif",
51+
// tags: ["Node.js"],
52+
// },
53+
54+
// {
55+
// id: 5,
56+
// title: "Fullstack App",
57+
// staticImg: "/imgs/project1.jpg",
58+
// animatedImg: "/imgs/animated1.gif",
59+
// tags: ["Node.js"],
60+
// },
61+
];
62+
63+
// Extract all unique tags from projects
64+
const allTags = Array.from(
65+
new Set(projects.flatMap((project) => project.tags))
66+
).sort();
67+
68+
const Projects = () => {
69+
const [activeTag, setActiveTag] = useState<string>("All");
70+
71+
const filteredProjects =
72+
activeTag === "All"
73+
? projects
74+
: projects.filter((p) => p.tags.includes(activeTag));
75+
1276
return (
1377
<div id="projects" className="text-center">
14-
<div className="container">
15-
<div className="section-title">
16-
<div style={{ padding: "40px" }}></div>
17-
<h2>Projects</h2>
18-
<p>Here are some of the projects I have worked on.</p>
78+
<div className="gallery-container">
79+
<div style={{ padding: "25px" }}></div>
80+
<h1 className="gallery-title">My Projects</h1>
81+
82+
<div className="filter-bar">
83+
<button
84+
className={activeTag === "All" ? "active" : ""}
85+
onClick={() => setActiveTag("All")}
86+
>
87+
All
88+
</button>
89+
{allTags.map((tag) => (
90+
<button
91+
key={tag}
92+
className={activeTag === tag ? "active" : ""}
93+
onClick={() => setActiveTag(tag)}
94+
>
95+
{tag}
96+
</button>
97+
))}
1998
</div>
20-
<div className="row">
21-
<div className="portfolio-items">
22-
{props.data
23-
? props.data.map((d, i) => (
24-
<div
25-
key={`${d.title}-${i}`}
26-
className="col-sm-6 col-md-4 col-lg-4"
27-
>
28-
<Image
29-
title={d.title}
30-
largeImage={d.largeImage}
31-
smallImage={d.smallImage}
32-
/>
33-
</div>
34-
))
35-
: "Loading..."}
36-
</div>
99+
100+
<div className="gallery-grid">
101+
<AnimatePresence>
102+
{filteredProjects.map((project) => (
103+
<motion.div
104+
key={project.id}
105+
layout
106+
initial={{ opacity: 0, scale: 0.95 }}
107+
animate={{ opacity: 1, scale: 1 }}
108+
exit={{ opacity: 0, scale: 0.95 }}
109+
transition={{ duration: 0.3 }}
110+
className="gallery-item"
111+
>
112+
<img
113+
className="static"
114+
src={project.staticImg}
115+
alt={project.title}
116+
loading="lazy"
117+
/>
118+
<a
119+
href={project.link}
120+
target="_blank"
121+
rel="noopener noreferrer"
122+
>
123+
<img
124+
className="animated"
125+
src={project.animatedImg}
126+
alt={`${project.title} animation`}
127+
loading="lazy"
128+
/>
129+
</a>
130+
<div className="tags">
131+
{project.tags.map((tag, index) => (
132+
<span key={index}>{tag}</span>
133+
))}
134+
</div>
135+
</motion.div>
136+
))}
137+
</AnimatePresence>
37138
</div>
38139
</div>
39140
</div>

0 commit comments

Comments
 (0)