Goal
Create Projects: at least three project cards with title, description, tech tags, link, and hover motion.
Projects.jsx
function Projects() {
const projects = [
{
title: "Campus Events App",
description:
"A React app for browsing campus events with filters and bookmarks.",
tags: ["React", "Tailwind", "API"],
link: "#",
},
{
title: "Expense Tracker",
description:
"Track spending with charts, categories, and local storage persistence.",
tags: ["JavaScript", "Chart.js"],
link: "#",
},
{
title: "Recipe Finder",
description:
"Search recipes from a public API and save favorites to a list.",
tags: ["React", "Fetch", "CSS"],
link: "#",
},
];
return (
<section id="projects" className="bg-slate-50 py-20 dark:bg-slate-950">
<div className="mx-auto max-w-5xl px-6">
<h2 className="mb-2 text-3xl font-bold text-slate-900 dark:text-slate-100">
Projects
</h2>
<p className="mb-10 max-w-2xl text-slate-600 dark:text-slate-400">
Selected work from class projects and side experiments.
</p>
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
{projects.map((project) => (
<article
key={project.title}
className="group flex flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm transition hover:-translate-y-1 hover:shadow-md dark:border-slate-700 dark:bg-slate-800"
>
<div className="aspect-video bg-gradient-to-br from-blue-500 to-cyan-400 transition duration-300 group-hover:scale-105" />
<div className="flex flex-1 flex-col p-6">
<h3 className="mb-2 text-xl font-semibold text-slate-900 transition group-hover:text-blue-600 dark:text-slate-100 dark:group-hover:text-blue-400">
{project.title}
</h3>
<p className="mb-4 flex-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
{project.description}
</p>
<ul className="mb-4 flex flex-wrap gap-2">
{project.tags.map((tag) => (
<li
key={tag}
className="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700 dark:bg-slate-800 dark:text-slate-200"
>
{tag}
</li>
))}
</ul>
<a
href={project.link}
className="text-sm font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
View project →
</a>
</div>
</article>
))}
</div>
</div>
</section>
);
}
export default Projects;
Swap in your real project titles, descriptions, and links (GitHub or live demos).
Tailwind chunks explained: section + grid
| Class | Meaning |
|---|---|
bg-slate-50 / dark:bg-slate-950 |
Soft band different from Skills |
grid + gap-8 |
Card grid with roomy gaps |
grid-cols-1 → md:grid-cols-2 → lg:grid-cols-3 |
1 / 2 / 3 columns by breakpoint |
Tailwind chunks explained: interactive card
| Class | Meaning |
|---|---|
group |
Marks the card so children can react with group-hover: |
flex flex-col |
Stack image, body, and link vertically |
overflow-hidden |
Clip the image zoom so corners stay rounded |
rounded-2xl border … shadow-sm |
Card chrome |
transition |
Animate transform and shadow |
hover:-translate-y-1 |
Lift the card slightly on hover |
hover:shadow-md |
Stronger shadow while lifted |
dark:bg-slate-800 |
Card face in dark mode |
Tailwind chunks explained: media placeholder
| Class | Meaning |
|---|---|
aspect-video |
Keep a 16:9 media area without a real image |
bg-gradient-to-br |
Diagonal gradient background |
from-blue-500 to-cyan-400 |
Gradient stops |
duration-300 |
300ms zoom animation |
group-hover:scale-105 |
Zoom the media when the card is hovered |
Later you can replace the gradient div with an <img className="aspect-video w-full object-cover …">.
Tailwind chunks explained: card body
| Class | Meaning |
|---|---|
flex-1 flex-col |
Grow the body so the link sits near the bottom |
flex-1 on the description |
Pushes tags/link down when heights differ |
group-hover:text-blue-600 |
Title color shift tied to card hover |
rounded-full |
Pill-shaped tags |
bg-slate-100 px-3 py-1 text-xs |
Compact tag chip |
flex flex-wrap gap-2 |
Tag row that wraps cleanly |
text-blue-600 hover:text-blue-700 |
Link accent with hover |
Wire into App.jsx
import Navbar from "./components/Navbar";
import Hero from "./components/Hero";
import Skills from "./components/Skills";
import Projects from "./components/Projects";
function App() {
return (
<div className="min-h-screen bg-surface font-sans text-ink antialiased dark:bg-slate-950 dark:text-slate-100">
<Navbar />
<main>
<Hero />
<Skills />
<Projects />
</main>
</div>
);
}
export default App;
Check
- At least 3 projects render
- Hover lifts the card and tints the title
- Tags wrap without overflowing
- Hero View projects and nav Projects both land here
Next: build the Contact form with focus styles.