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-1md:grid-cols-2lg: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.