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({ - -
- {socials.map((s) => ( - document.getElementById("home")?.scrollIntoView({ behavior: "smooth" })} > - {s.icon ?? ( - - )} - - ))} -
- + Background + + + + -
-
- © {year} Jody Holt • All rights reserved +
+ {socials.map((s) => ( + + {s.icon ? ( + {s.label} + ) : ( + + )} + + ))} +
- {showBackToTop && ( - + )} +
+
+ Icons by{" "} + - Back to top - - )} -
+ Icons8 + + ); } 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) => ( - + {a.label} ))}
@@ -112,14 +123,12 @@ export function Hero() {
- {[ - { label: "GitHub", href: "#" }, - { label: "LinkedIn", href: "#" }, - { label: "Email", href: "#" }, - ].map((a) => ( + {socialLinks.map((a) => ( + {a.label} ))}
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) => ( ))}
+ {" "}
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 */} +
+ {project.title} + {/* Play Button Overlay */} + + {/* Coming Soon Badge */} + {project.comingSoon && ( +
+ Coming Soon +
+ )} +
+ + {/* Cover Image - Mobile */} +
+ {project.title} + {/* Play Button Overlay */} + + {/* Coming Soon Badge */} + {project.comingSoon && ( +
+ Coming Soon +
+ )} +
+ + {/* Content */} +
+

{project.title}

+

+ {project.description} +

+ + {/* Tech Stack */} +
+ {project.techStack.map((tech) => ( + + {tech} + + ))} +
+ + {/* Actions */} +
+ + {project.liveUrl && ( + + + + + + + Visit Site + + )} +
+
+
+ ); +} + +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 */} +
+

Resume

+
+ {contactInfo.location} + + + {contactInfo.phone} + + + + {contactInfo.email} + +
+
+ + {/* Summary */} +
+ Summary +

{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 ( +
+

{children}

+
+
+ ); +} 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