diff --git a/package-lock.json b/package-lock.json
index 53f5b62..85438d5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
"@types/react": "^19.1.16",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.4",
+ "baseline-browser-mapping": "^2.9.11",
"eslint": "^9.36.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.22",
@@ -2019,9 +2020,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.17",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.17.tgz",
- "integrity": "sha512-j5zJcx6golJYTG6c05LUZ3Z8Gi+M62zRT/ycz4Xq4iCOdpcxwg7ngEYD4KA0eWZC7U17qh/Smq8bYbACJ0ipBA==",
+ "version": "2.9.11",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
+ "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
diff --git a/package.json b/package.json
index 5dac6d1..342f6fc 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"@types/react": "^19.1.16",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.4",
+ "baseline-browser-mapping": "^2.9.11",
"eslint": "^9.36.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.22",
diff --git a/src/App.tsx b/src/App.tsx
index ccf7909..624dff5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,7 +2,8 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
import { Navbar } from "./components/Navbar";
import { Section } from "./components/Section";
import { Hero } from "./components/Hero";
-import { Placeholder } from "./components/Placeholder";
+import { Projects } from "./components/Projects";
+import { Resume } from "./components/Resume";
import { Footer } from "./components/Footer";
export default function App() {
@@ -46,9 +47,9 @@ return (
-
+
-
+
diff --git a/src/assets/img/500nmain-cover-img.jpg b/src/assets/img/500nmain-cover-img.jpg
new file mode 100644
index 0000000..3d393df
Binary files /dev/null and b/src/assets/img/500nmain-cover-img.jpg differ
diff --git a/src/assets/img/500nmain-mobile-cover-img.jpg b/src/assets/img/500nmain-mobile-cover-img.jpg
new file mode 100644
index 0000000..59171c7
Binary files /dev/null and b/src/assets/img/500nmain-mobile-cover-img.jpg differ
diff --git a/src/assets/Jody-mobile.png b/src/assets/img/Jody-mobile.png
similarity index 100%
rename from src/assets/Jody-mobile.png
rename to src/assets/img/Jody-mobile.png
diff --git a/src/assets/Jody.png b/src/assets/img/Jody.png
similarity index 100%
rename from src/assets/Jody.png
rename to src/assets/img/Jody.png
diff --git a/src/assets/img/Skymoney-cover-img.jpg b/src/assets/img/Skymoney-cover-img.jpg
new file mode 100644
index 0000000..62fac20
Binary files /dev/null and b/src/assets/img/Skymoney-cover-img.jpg differ
diff --git a/src/assets/img/email-icon.png b/src/assets/img/email-icon.png
new file mode 100644
index 0000000..2b9b177
Binary files /dev/null and b/src/assets/img/email-icon.png differ
diff --git a/src/assets/img/facebook-icon.png b/src/assets/img/facebook-icon.png
new file mode 100644
index 0000000..b0e5711
Binary files /dev/null and b/src/assets/img/facebook-icon.png differ
diff --git a/src/assets/img/github-icon.png b/src/assets/img/github-icon.png
new file mode 100644
index 0000000..cc84933
Binary files /dev/null and b/src/assets/img/github-icon.png differ
diff --git a/src/assets/img/linkedin-icon.png b/src/assets/img/linkedin-icon.png
new file mode 100644
index 0000000..a10d0bf
Binary files /dev/null and b/src/assets/img/linkedin-icon.png differ
diff --git a/src/assets/img/phone-icon.png b/src/assets/img/phone-icon.png
new file mode 100644
index 0000000..66dbbb2
Binary files /dev/null and b/src/assets/img/phone-icon.png differ
diff --git a/src/assets/img/skymoney-mobile-cover-img.jpg b/src/assets/img/skymoney-mobile-cover-img.jpg
new file mode 100644
index 0000000..589bdbd
Binary files /dev/null and b/src/assets/img/skymoney-mobile-cover-img.jpg differ
diff --git a/src/assets/video/500nmain-mobile-video.mp4 b/src/assets/video/500nmain-mobile-video.mp4
new file mode 100644
index 0000000..5b84a45
Binary files /dev/null and b/src/assets/video/500nmain-mobile-video.mp4 differ
diff --git a/src/assets/video/500nmain-video.mp4 b/src/assets/video/500nmain-video.mp4
new file mode 100644
index 0000000..137b8ff
Binary files /dev/null and b/src/assets/video/500nmain-video.mp4 differ
diff --git a/src/assets/video/Skymoney-mobile-video.mp4 b/src/assets/video/Skymoney-mobile-video.mp4
new file mode 100644
index 0000000..6a840ce
Binary files /dev/null and b/src/assets/video/Skymoney-mobile-video.mp4 differ
diff --git a/src/assets/video/Skymoney-video.mp4 b/src/assets/video/Skymoney-video.mp4
new file mode 100644
index 0000000..c3f1112
Binary files /dev/null and b/src/assets/video/Skymoney-video.mp4 differ
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index 5d00af7..e65952c 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -1,14 +1,23 @@
import React from "react";
+import githubIcon from "../assets/img/github-icon.png";
+import linkedInIcon from "../assets/img/linkedin-icon.png";
+import emailIcon from "../assets/img/email-icon.png";
+import facebookIcon from "../assets/img/facebook-icon.png";
+import phoneIcon from "../assets/img/phone-icon.png";
-type Social = { label: string; href: string; icon?: React.ReactNode };
+const defaultSocials = [
+ { label: "GitHub", href: "https://github.com/Ricearoni1245", icon: githubIcon },
+ { label: "LinkedIn", href: "https://www.linkedin.com/in/jody-holt-9b19b0256", icon: linkedInIcon },
+ { label: "Facebook", href: "https://www.facebook.com/jody.holt.7161/", icon: facebookIcon },
+ { label: "Email", href: "mailto:jholt1008@gmail.com", icon: emailIcon },
+ { label: "Phone", href: "tel:8066542813", icon: phoneIcon },
+];
+
+type Social = { label: string; href: string; icon?: string };
export function Footer({
year = new Date().getFullYear(),
- socials = [
- { label: "GitHub", href: "#" },
- { label: "LinkedIn", href: "#" },
- { label: "Email", href: "#" },
- ],
+ socials = defaultSocials,
showBackToTop = true,
}: {
year?: number;
@@ -28,47 +37,71 @@ export function Footer({
-
-
-
+ Background
+
+
+
+
-
-
- © {year} Jody Holt • All rights reserved
+
- {showBackToTop && (
-
);
}
diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx
index a652d5f..33d3a4e 100644
--- a/src/components/Hero.tsx
+++ b/src/components/Hero.tsx
@@ -1,7 +1,20 @@
import React from "react";
-import profileImage from "../assets/jody.png";
-import jodyMobile from "../assets/Jody-mobile.png";
+import profileImage from "../assets/img/Jody.png";
+import jodyMobile from "../assets/img/Jody-mobile.png";
import { useTheme } from "../hooks/useTheme";
+import githubIcon from "../assets/img/github-icon.png";
+import linkedInIcon from "../assets/img/linkedin-icon.png";
+import emailIcon from "../assets/img/email-icon.png";
+import facebookIcon from "../assets/img/facebook-icon.png";
+import phoneIcon from "../assets/img/phone-icon.png";
+
+const socialLinks = [
+ { label: "GitHub", href: "https://github.com/Ricearoni1245", icon: githubIcon },
+ { label: "LinkedIn", href: "https://www.linkedin.com/in/jody-holt-9b19b0256", icon: linkedInIcon },
+ { label: "Facebook", href: "https://www.facebook.com/jody.holt.7161/", icon: facebookIcon },
+ { label: "Email", href: "mailto:jholt1008@gmail.com", icon: emailIcon },
+ { label: "Phone", href: "tel:8066542813", icon: phoneIcon },
+];
export function Hero() {
const { theme } = useTheme(); // "a" | "b" | "c" | "d" | "e"
return (
@@ -40,21 +53,19 @@ export function Hero() {
- {[
- { label: "GitHub", href: "#" },
- { label: "LinkedIn", href: "#" },
- { label: "Email", href: "#" },
- ].map((a) => (
+ {socialLinks.map((a) => (
-
+
))}
@@ -112,14 +123,12 @@ export function Hero() {
- {[
- { label: "GitHub", href: "#" },
- { label: "LinkedIn", href: "#" },
- { label: "Email", href: "#" },
- ].map((a) => (
+ {socialLinks.map((a) => (
+

))}
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index 0b392a7..c764114 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -15,14 +15,20 @@ export function Navbar({ onNav }: { onNav: (id: string) => void }) {
};
return (
-
-
-
+
+
+
-
+
Jody Holt
@@ -72,13 +78,14 @@ export function Navbar({ onNav }: { onNav: (id: string) => void }) {
{links.map((l) => (
handleNav(l.id)}
>
{l.label}
))}
+ {" "}
diff --git a/src/components/Projects.tsx b/src/components/Projects.tsx
new file mode 100644
index 0000000..829106d
--- /dev/null
+++ b/src/components/Projects.tsx
@@ -0,0 +1,331 @@
+import React, { useState, useEffect } from "react";
+import skymoneycover from "../assets/img/Skymoney-cover-img.jpg";
+import skymoneycoverMobile from "../assets/img/skymoney-mobile-cover-img.jpg";
+import millercover from "../assets/img/500nmain-cover-img.jpg";
+import millercoverMobile from "../assets/img/500nmain-mobile-cover-img.jpg";
+import skymoneyvideo from "../assets/video/Skymoney-video.mp4";
+import skymoneyvideoMobile from "../assets/video/Skymoney-mobile-video.mp4";
+import millervideo from "../assets/video/500nmain-video.mp4";
+import millervideoMobile from "../assets/video/500nmain-mobile-video.mp4";
+
+type Project = {
+ id: string;
+ title: string;
+ description: string;
+ coverImage: string;
+ coverImageMobile: string;
+ video: string;
+ videoMobile: string;
+ techStack: string[];
+ liveUrl?: string;
+ comingSoon?: boolean;
+};
+
+const projects: Project[] = [
+ {
+ id: "skymoney",
+ title: "Skymoney",
+ description:
+ "A budgeting app that simulates your bank account to ensure financial discipline.",
+ coverImage: skymoneycover,
+ coverImageMobile: skymoneycoverMobile,
+ video: skymoneyvideo,
+ videoMobile: skymoneyvideoMobile,
+ techStack: ["React", "TypeScript", "Node.js", "PostgreSQL"],
+ comingSoon: true,
+ },
+ {
+ id: "miller-building",
+ title: "Miller Building Website",
+ description:
+ "A website showcasing the historic Miller Building located in Borger, Texas.",
+ coverImage: millercover,
+ coverImageMobile: millercoverMobile,
+ video: millervideo,
+ videoMobile: millervideoMobile,
+ techStack: ["React", "TypeScript", "Tailwind CSS"],
+ liveUrl: "https://500nmain806.com",
+ },
+];
+
+function VideoModal({
+ isOpen,
+ onClose,
+ video,
+ videoMobile,
+ title,
+}: {
+ isOpen: boolean;
+ onClose: () => void;
+ video: string;
+ videoMobile: string;
+ title: string;
+}) {
+ const [isMobile, setIsMobile] = useState(false);
+
+ useEffect(() => {
+ const checkMobile = () => setIsMobile(window.innerWidth < 768);
+ checkMobile();
+ window.addEventListener("resize", checkMobile);
+ return () => window.removeEventListener("resize", checkMobile);
+ }, []);
+
+ if (!isOpen) return null;
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
{title} Demo
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function ProjectCard({
+ project,
+ onPlayVideo,
+}: {
+ project: Project;
+ onPlayVideo: () => void;
+}) {
+ return (
+
+ {/* Cover Image - Desktop */}
+
+

+ {/* Play Button Overlay */}
+
+
+
+ {/* Coming Soon Badge */}
+ {project.comingSoon && (
+
+ Coming Soon
+
+ )}
+
+
+ {/* Cover Image - Mobile */}
+
+

+ {/* Play Button Overlay */}
+
+
+
+ {/* Coming Soon Badge */}
+ {project.comingSoon && (
+
+ Coming Soon
+
+ )}
+
+
+ {/* Content */}
+
+
{project.title}
+
+ {project.description}
+
+
+ {/* Tech Stack */}
+
+ {project.techStack.map((tech) => (
+
+ {tech}
+
+ ))}
+
+
+ {/* Actions */}
+
+
+
+ );
+}
+
+function MoreToComCard() {
+ return (
+
+
+
+
+
+
More to Come
+
+ Exciting projects in development. Stay tuned!
+
+
+
+ );
+}
+
+export function Projects() {
+ const [activeVideo, setActiveVideo] = useState
(null);
+
+ return (
+
+
+
+ Projects
+
+
+ A showcase of my work — from concept to deployment. Click on any
+ project to watch a demo.
+
+
+
+
+ {projects.map((project) => (
+
setActiveVideo(project)}
+ />
+ ))}
+
+
+
+ {/* Video Modal */}
+
setActiveVideo(null)}
+ video={activeVideo?.video || ""}
+ videoMobile={activeVideo?.videoMobile || ""}
+ title={activeVideo?.title || ""}
+ />
+
+ );
+}
diff --git a/src/components/Resume.tsx b/src/components/Resume.tsx
new file mode 100644
index 0000000..57608fc
--- /dev/null
+++ b/src/components/Resume.tsx
@@ -0,0 +1,167 @@
+import React from "react";
+
+const contactInfo = {
+ location: "Amarillo, TX",
+ phone: "806.654.2813",
+ email: "jholt1008@gmail.com",
+ linkedin: "https://www.linkedin.com/in/jody-holt-9b19b0256",
+};
+
+const summary = `Detail-oriented software developer skilled in building full-stack applications using React, TypeScript, Node/Express, SQL, and Docker. Experienced in designing responsive user interfaces, structuring maintainable front-end architectures, and developing reliable, modular APIs. Strong communicator with proven ability to solve problems quickly, learn new technologies efficiently, and deliver clean, scalable code across multiple projects.`;
+
+const skills = [
+ {
+ category: "Front-End Development",
+ items: ["React", "TypeScript", "Responsive UI/UX", "Component Architecture", "Entity Framework Core", "TailwindCSS"],
+ },
+ {
+ category: "Back-End & APIs",
+ items: ["Node.js", "Express.js", "RESTful API", "Authentication Flows", "Data Validation", "C#", ".NET Core"],
+ },
+ {
+ category: "Database & Data Modeling",
+ items: ["SQL", "PostgreSQL", "CRUD Operations", "Query Optimization", "Object-Oriented Analysis & Design"],
+ },
+ {
+ category: "DevOps & Tools",
+ items: ["Docker Compose", "Git/GitHub", "Software Migration", "Multi-Container Setups"],
+ },
+ {
+ category: "Software Engineering",
+ items: ["Clear Communication", "Modular Code Design", "Collaboration", "Rapid Learning", "Problem-Solving"],
+ },
+];
+
+const accomplishments = [
+ "Meta's Front-End Web Development and Data Engineering certificate programs",
+ "Built responsive React applications featuring structured component trees & dynamic routing",
+ "Designed SQL databases with optimal CRUD operations & well-structured queries",
+ "Containerized full-stack apps with Docker Compose for optimal scaling, resolved network, environment, version control, and dependency issues",
+ "Created reusable UI components and interactive features that improved consistency and flow, user-friendly animations and enticing UX",
+];
+
+const workHistory = [
+ { title: "Sandwich Artist", company: "Subway", location: "Canyon, TX", dates: "2024–Present" },
+ { title: "Head Lifeguard", company: "Johnson Park Youth Center", location: "Borger, TX", dates: "Seasonal 2022–2025" },
+ { title: "Sacker/Grocery Stocker", company: "United Supermarkets", location: "Canyon, TX", dates: "2023–2024" },
+];
+
+const education = [
+ { degree: "M.S. in Computer Information Systems and Business Analytics", school: "West Texas A&M University", date: "May 2027" },
+ { degree: "B.S. in Computer Information Systems", school: "West Texas A&M University", date: "May 2026" },
+];
+
+export function Resume() {
+ return (
+
+ {/* Header */}
+
+
+ {/* Summary */}
+
+
+ {/* Skills */}
+
+ Skills & Strengths
+
+ {skills.map((skill) => (
+
+
{skill.category}
+
+ {skill.items.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ ))}
+
+
+
+ {/* Accomplishments */}
+
+ Professional Accomplishments
+
+ {accomplishments.map((item, i) => (
+ -
+
+ {item}
+
+ ))}
+
+
+
+ {/* Work History */}
+
+ Work History
+
+ {workHistory.map((job, i) => (
+
+
+
{job.title}
+
+ {job.company} — {job.location}
+
+
+
{job.dates}
+
+ ))}
+
+
+
+ {/* Education */}
+
+ Education
+
+ {education.map((edu, i) => (
+
+
+
{edu.degree}
+
{edu.school}
+
+
{edu.date}
+
+ ))}
+
+
+
+ );
+}
+
+function SectionTitle({ children }: { children: React.ReactNode }) {
+ return (
+
+ );
+}
diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx
index 77d2267..de29439 100644
--- a/src/components/ThemeToggle.tsx
+++ b/src/components/ThemeToggle.tsx
@@ -2,6 +2,15 @@
import React from "react";
import { useTheme } from "../hooks/useTheme";
+// Actual primary colors for each theme
+const themeColors: Record = {
+ a: { primary: "#3d8eff", label: "Blue" },
+ b: { primary: "#ff7043", label: "Ember" },
+ c: { primary: "#00a3c4", label: "Teal" },
+ d: { primary: "#7743d8", label: "Violet" },
+ e: { primary: "#00d2a2", label: "Emerald" },
+};
+
export function ThemeToggle({ compact = false }: { compact?: boolean }) {
const { theme, setTheme } = useTheme();
const themes = ["a", "b", "c", "d", "e"] as const;
@@ -55,9 +64,9 @@ export function ThemeToggle({ compact = false }: { compact?: boolean }) {
>
- Theme {t.toUpperCase()}
+ {themeColors[t].label}
))}
diff --git a/src/index.css b/src/index.css
index 956372f..951cd64 100644
--- a/src/index.css
+++ b/src/index.css
@@ -85,29 +85,30 @@ html[data-theme="e"] {
100%
);
}
-}
-/* Desktop override */
-@media (min-width: 768px) {
- .bg-hero {
- background:
- /* small, softer highlight lower than the portrait rim */ radial-gradient(
- 95% 70% at 50% 28%,
- color-mix(in oklab, var(--hero-core) 24%, transparent 82%) 0%,
- transparent 56%
- ),
- /* gentle bottom vignette for depth */
- radial-gradient(
- 130% 90% at 50% 120%,
- rgba(0, 0, 0, 0.32) 0%,
- rgba(0, 0, 0, 0) 58%
+
+ /* Desktop override */
+ @media (min-width: 768px) {
+ .bg-hero {
+ background:
+ /* small, softer highlight lower than the portrait rim */ radial-gradient(
+ 95% 70% at 50% 28%,
+ color-mix(in oklab, var(--hero-core) 24%, transparent 82%) 0%,
+ transparent 56%
),
- /* base linear sweep */
- linear-gradient(
- 185deg,
- #0b0f15 0%,
- var(--color-bg) 40%,
- color-mix(in oklab, var(--hero-core) 12%, var(--color-bg) 88%) 100%
- );
+ /* gentle bottom vignette for depth */
+ radial-gradient(
+ 130% 90% at 50% 120%,
+ rgba(0, 0, 0, 0.32) 0%,
+ rgba(0, 0, 0, 0) 58%
+ ),
+ /* base linear sweep */
+ linear-gradient(
+ 185deg,
+ #0b0f15 0%,
+ var(--color-bg) 40%,
+ color-mix(in oklab, var(--hero-core) 12%, var(--color-bg) 88%) 100%
+ );
+ }
}
}
diff --git a/variable-categories.ts b/variable-categories.ts
new file mode 100644
index 0000000..e69de29