diff --git a/public/projects/AllMail-1.png b/public/projects/AllMail-1.png
new file mode 100644
index 0000000..465ee95
Binary files /dev/null and b/public/projects/AllMail-1.png differ
diff --git a/public/projects/BillBuddy-1.png b/public/projects/BillBuddy-1.png
new file mode 100644
index 0000000..01aae4c
Binary files /dev/null and b/public/projects/BillBuddy-1.png differ
diff --git a/public/projects/TreatRush-1.png b/public/projects/TreatRush-1.png
new file mode 100644
index 0000000..84d5d33
Binary files /dev/null and b/public/projects/TreatRush-1.png differ
diff --git a/public/projects/TreatRush-2.png b/public/projects/TreatRush-2.png
new file mode 100644
index 0000000..4d9d4bd
Binary files /dev/null and b/public/projects/TreatRush-2.png differ
diff --git a/public/projects/TreatRush-3.png b/public/projects/TreatRush-3.png
new file mode 100644
index 0000000..4f0863f
Binary files /dev/null and b/public/projects/TreatRush-3.png differ
diff --git a/public/projects/TreatRush-4.png b/public/projects/TreatRush-4.png
new file mode 100644
index 0000000..06181d2
Binary files /dev/null and b/public/projects/TreatRush-4.png differ
diff --git a/public/projects/TreatRush-5.png b/public/projects/TreatRush-5.png
new file mode 100644
index 0000000..fe91af2
Binary files /dev/null and b/public/projects/TreatRush-5.png differ
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 5f3b89a..471fde4 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -45,7 +45,7 @@ describe("App", () => {
it("has working primary links", () => {
expect(screen.getByRole("link", { name: /view my work/i })).toHaveAttribute(
"href",
- "https://github.com/rxshellg",
+ "#projects",
);
expect(screen.getByRole("link", { name: /get in touch/i })).toHaveAttribute(
"href",
diff --git a/src/components/ui/ProjectCard.css b/src/components/ui/ProjectCard.css
new file mode 100644
index 0000000..03192c4
--- /dev/null
+++ b/src/components/ui/ProjectCard.css
@@ -0,0 +1,135 @@
+.project-card {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--primary);
+ border-radius: 10px;
+ background: #1a0d14;
+ overflow: hidden;
+ height: 100%;
+}
+
+.project-card-window {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 0.6rem;
+ padding: 0.4rem 0.6rem;
+ background: #0d0d0d;
+ border-bottom: 1px solid rgba(242, 119, 165, 0.3);
+}
+
+.project-card-window span {
+ color: var(--primary);
+ font-size: 0.65rem;
+}
+
+.project-card-window span:first-child {
+ transform: translateY(-0.2rem);
+}
+
+.project-card-window span:nth-child(2) {
+ transform: translateY(0.1rem);
+}
+
+.project-card-image {
+ aspect-ratio: 16 / 9;
+ background: #0d0d0d;
+ overflow: hidden;
+ border-bottom: 1px solid rgba(242, 119, 165, 0.3);
+}
+
+.project-card-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
+.project-card-body {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ padding: 1.2rem;
+}
+
+.project-card-title {
+ margin: 0 0 0.5rem;
+ font-size: 1.05rem;
+ color: #fff;
+}
+
+.project-card-description {
+ margin: 0 0 1rem;
+ font-size: 0.85rem;
+ line-height: 1.4;
+ color: rgba(255, 255, 255, 0.65);
+ flex: 1;
+}
+
+.project-card-stack {
+ list-style: none;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin: 0 0 1rem;
+ padding: 0;
+}
+
+.project-card-pill {
+ font-size: 0.7rem;
+ padding: 0.25rem 0.6rem;
+ border: 1px solid rgba(242, 119, 165, 0.4);
+ border-radius: 4px;
+ color: var(--primary);
+ white-space: nowrap;
+}
+
+.project-card-actions {
+ display: flex;
+ gap: 0.6rem;
+}
+
+.project-card-demo {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ background: var(--primary);
+ color: #000;
+ font-weight: 700;
+ font-size: 0.78rem;
+ letter-spacing: 0.04em;
+ text-decoration: none;
+ padding: 0.7rem 1rem;
+ transition: opacity 0.2s;
+}
+
+.project-card-demo:hover {
+ opacity: 0.85;
+}
+
+.project-card-demo--disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+ pointer-events: none;
+}
+
+.project-card-github {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 44px;
+ flex-shrink: 0;
+ border: 1px solid var(--primary);
+ color: var(--primary);
+ text-decoration: none;
+ transition:
+ background 0.2s,
+ color 0.2s;
+}
+
+.project-card-github:hover {
+ background: var(--primary);
+ color: #000;
+}
diff --git a/src/components/ui/ProjectCard.tsx b/src/components/ui/ProjectCard.tsx
new file mode 100644
index 0000000..e98a823
--- /dev/null
+++ b/src/components/ui/ProjectCard.tsx
@@ -0,0 +1,74 @@
+import { FiArrowRight } from "react-icons/fi";
+import { FaGithub } from "react-icons/fa";
+import type { Project } from "../../data/projects";
+import "./ProjectCard.css";
+
+interface ProjectCardProps {
+ project: Project;
+}
+
+function ProjectCard({ project }: ProjectCardProps) {
+ const { title, description, images, techStack, liveDemoUrl, githubUrl } =
+ project;
+
+ return (
+
+
+ _
+ □
+ ✕
+
+
+
+

+
+
+
+
{title}
+
{description}
+
+
+ {techStack.map((tech) => (
+ -
+ {tech}
+
+ ))}
+
+
+
+ {liveDemoUrl ? (
+
+ LIVE DEMO
+
+ ) : (
+
+ LIVE DEMO
+
+ )}
+
+ {githubUrl && (
+
+
+
+ )}
+
+
+
+ );
+}
+
+export default ProjectCard;
diff --git a/src/data/navigation.ts b/src/data/navigation.ts
index 45484d9..99f2eb1 100644
--- a/src/data/navigation.ts
+++ b/src/data/navigation.ts
@@ -1,8 +1,8 @@
export const navigationLinks = [
{ label: "home", href: "#hero", id: "hero" },
{ label: "About", href: "#about", id: "about" },
-// { label: "Experience", href: "#experience", id: "experience" },
-// { label: "Projects", href: "#projects", id: "projects" },
-// { label: "Skills", href: "#skills", id: "skills" },
-// { label: "Contact", href: "#contact", id: "contact" },
+ { label: "Projects", href: "#projects", id: "projects" },
+ // { label: "Skills", href: "#skills", id: "skills" },
+ // { label: "Experience", href: "#experience", id: "experience" },
+ // { label: "Contact", href: "#contact", id: "contact" },
];
diff --git a/src/data/projects.ts b/src/data/projects.ts
new file mode 100644
index 0000000..eb20324
--- /dev/null
+++ b/src/data/projects.ts
@@ -0,0 +1,52 @@
+export interface Project {
+ id: string;
+ title: string;
+ description: string;
+ images: string[];
+ techStack: string[];
+ liveDemoUrl?: string;
+ githubUrl?: string;
+ featured: boolean;
+}
+
+export const projects: Project[] = [
+ {
+ id: "allmail",
+ title: "AllMail",
+ description:
+ "Email workspace that brings multiple inboxes into one clean, centralized dashboard.",
+ images: ["/projects/AllMail-1.png"],
+ techStack: ["TypeScript", "Java", "Spring Boot", "OAuth"],
+ liveDemoUrl: "", // TO-DO: post live demo link
+ githubUrl: "https://github.com/rxshellg/all-mail",
+ featured: true,
+ },
+ {
+ id: "treat-rush",
+ title: "Treat Rush",
+ description:
+ "Small arcade-style game where the player controls a cat, catches falling treats, and avoids spray bottles.",
+ images: [
+ "/projects/TreatRush-1.png",
+ "/projects/TreatRush-2.png",
+ "/projects/TreatRush-3.png",
+ "/projects/TreatRush-4.png",
+ "/projects/TreatRush-5.png",
+ ],
+ techStack: ["Java", "libGDX", "Gradle"],
+ liveDemoUrl: "", // TO-DO: post live demo link
+ githubUrl: "https://github.com/rxshellg/treat-rush",
+ featured: true,
+ },
+ {
+ id: "bill-buddy",
+ title: "Bill Buddy",
+ description:
+ "Receipt-splitting app that lets users upload a receipt, assign the items, and calculate each person’s share.",
+ images: ["/projects/BillBuddy-1.png"],
+ techStack: ["TypeScript", "Node.js", "Cloud Vision API"],
+ liveDemoUrl: "", // TO-DO: post live demo link
+ githubUrl: "https://github.com/rxshellg/bill-buddy",
+ featured: true,
+ },
+];
diff --git a/src/main.tsx b/src/main.tsx
index 8ee76d4..f811882 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -2,6 +2,7 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './styles/global.css'
import './styles/layout.css'
+import './styles/shared.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx
index d0ce8c7..31cdc7f 100644
--- a/src/pages/home/HomePage.tsx
+++ b/src/pages/home/HomePage.tsx
@@ -2,7 +2,7 @@ import AboutSection from "./components/AboutSection";
// import ContactSection from "./components/ContactSection";
// import ExperienceSection from "./components/ExperienceSection";
import HeroSection from "./components/HeroSection";
-// import ProjectsSection from "./components/ProjectsSection";
+import ProjectsSection from "./components/ProjectsSection";
import SiteLayout from "../../components/layout/SiteLayout";
// import SkillsSection from "./components/SkillsSection";
@@ -13,7 +13,7 @@ function HomePage() {
{/* - commented out until built */}
- {/* - commented out until built */}
+
{/* - commented out until built */}
{/* - commented out until built */}
diff --git a/src/pages/home/components/AboutSection.css b/src/pages/home/components/AboutSection.css
index 7e880d7..f136658 100644
--- a/src/pages/home/components/AboutSection.css
+++ b/src/pages/home/components/AboutSection.css
@@ -30,19 +30,8 @@
padding-right: 3rem;
}
-.section-label {
- margin: 0;
- color: var(--primary);
- text-transform: uppercase;
- font-size: 1.2rem;
- font-weight: 800;
- letter-spacing: 0.08em;
- text-decoration: underline;
- text-underline-offset: 0.25rem;
-}
-
.about-heading {
- margin: 1.5rem 0 0.3rem;
+ margin: 1.4rem 0 0.3rem;
font-size: clamp(2.5rem, 4vw, 4.5rem);
letter-spacing: -0.04em;
line-height: 1;
@@ -54,7 +43,7 @@
.about-description {
margin: 0.75rem 0 0;
- font-size: 0.95rem;
+ font-size: 0.9rem;
line-height: 1.3;
}
diff --git a/src/pages/home/components/HeroSection.css b/src/pages/home/components/HeroSection.css
index 91871cb..9a95be8 100644
--- a/src/pages/home/components/HeroSection.css
+++ b/src/pages/home/components/HeroSection.css
@@ -121,7 +121,7 @@
.hero-section {
display: flex;
flex-direction: column;
- height: calc(100svh + var(--navbar-height));
+ height: auto;
overflow: hidden;
}
diff --git a/src/pages/home/components/HeroSection.tsx b/src/pages/home/components/HeroSection.tsx
index deeee1a..88a7b74 100644
--- a/src/pages/home/components/HeroSection.tsx
+++ b/src/pages/home/components/HeroSection.tsx
@@ -20,13 +20,7 @@ function HeroSection() {
- {/*
- -Commented out until built-
- */}
-
+
View my work
diff --git a/src/pages/home/components/ProjectsSection.css b/src/pages/home/components/ProjectsSection.css
new file mode 100644
index 0000000..9567c6d
--- /dev/null
+++ b/src/pages/home/components/ProjectsSection.css
@@ -0,0 +1,58 @@
+.projects-section {
+ background-color: var(--primary);
+ padding: 2rem 3rem;
+}
+
+.projects-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 1.2rem;
+}
+
+.projects-label {
+ color: black;
+}
+
+.projects-view-all {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.75rem;
+ letter-spacing: 0.05em;
+ color: gray;
+ text-decoration: none;
+ transition: color 0.2s;
+}
+
+.projects-view-all:hover {
+ color: black;
+}
+
+.projects-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1.75rem;
+}
+
+@media (max-width: 1024px) {
+ .projects-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (max-width: 768px) {
+ .projects-section {
+ padding: 2.5rem 1.5rem;
+ }
+
+ .projects-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+ }
+
+ .projects-grid {
+ grid-template-columns: 1fr;
+ }
+}
\ No newline at end of file
diff --git a/src/pages/home/components/ProjectsSection.tsx b/src/pages/home/components/ProjectsSection.tsx
index fe2fb8c..a83e578 100644
--- a/src/pages/home/components/ProjectsSection.tsx
+++ b/src/pages/home/components/ProjectsSection.tsx
@@ -1,10 +1,27 @@
+import { FiArrowRight } from "react-icons/fi";
+import ProjectCard from "../../../components/ui/ProjectCard";
+import { projects } from "../../../data/projects";
+import "./ProjectsSection.css";
+
+const featuredProjects = projects.filter((project) => project.featured);
+
function ProjectsSection() {
return (
- <>
-
- >
+
+
+
+
+ {featuredProjects.map((project) => (
+
+ ))}
+
+
);
}
diff --git a/src/styles/shared.css b/src/styles/shared.css
new file mode 100644
index 0000000..76c63a5
--- /dev/null
+++ b/src/styles/shared.css
@@ -0,0 +1,10 @@
+.section-label {
+ margin: 0;
+ color: var(--primary);
+ text-transform: uppercase;
+ font-size: 0.9rem;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+ text-decoration: underline;
+ text-underline-offset: 0.25rem;
+}
\ No newline at end of file