Design. Develop. Deliver.
-
+
Driven by a genuine passion for creation through code.
-
+
+
-
+
+
-
-
-
-
-
+
Hello, I’m Jody Holt
-
+
Turning concepts into clean, functional code.
-
It’s What I Do.
+
+
-
I would love to connect!
-
-
+
{[
{ label: "GitHub", href: "#" },
{ label: "LinkedIn", href: "#" },
@@ -51,7 +49,10 @@ export function Hero() {
key={a.label}
href={a.href}
aria-label={a.label}
- className="inline-flex h-12 w-12 items-center justify-center rounded-lg border border-secondary/70 bg-secondary/20 text-text hover:border-primary hover:text-primary transition"
+ className="inline-flex h-12 w-12 items-center justify-center rounded-lg
+ border border-secondary/70 bg-secondary/20 text-text anim-base icon-hover
+ hover:border-primary hover:text-primary focus:outline-none focus-visible:ring-2
+ focus-visible:ring-primary/60"
>
@@ -59,79 +60,58 @@ export function Hero() {
+ {/*DESKTOP*/}
+ {/*____________________________________________________________________________________________________*/}
+
+
+ Design. Develop. Deliver.
+
+
+ Driven by a genuine passion for creation through code.
+
-
-
-
-
-
-
-
-
- {/*DESKTOP*/}
-{/*____________________________________________________________________________________________________*/}
-
-
-
-
-
-
-
+
- Design. Develop. Deliver.
-
-
-
- Driven by a genuine passion for creation through code.
-
-
+ Hello,
+
- Hello, I’m Jody Holt
+ I’m Jody Holt
Turning concepts into clean, functional code.
-
- It’s What I Do.
-
-
-
- I would love to connect!
-
-
-
+
+
+
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index 4fa6f1d..0b392a7 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -15,12 +15,14 @@ export function Navbar({ onNav }: { onNav: (id: string) => void }) {
};
return (
-
-
-
-
+
+
+
-
+
Jody Holt
@@ -29,12 +31,11 @@ export function Navbar({ onNav }: { onNav: (id: string) => void }) {
-
{links.map((l) => (
handleNav(l.id)}
>
{l.label}
@@ -43,7 +44,6 @@ export function Navbar({ onNav }: { onNav: (id: string) => void }) {
-
void }) {
-
diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx
index 3a0abfe..77d2267 100644
--- a/src/components/ThemeToggle.tsx
+++ b/src/components/ThemeToggle.tsx
@@ -1,3 +1,4 @@
+// ThemeToggle.tsx
import React from "react";
import { useTheme } from "../hooks/useTheme";
@@ -5,35 +6,56 @@ export function ThemeToggle({ compact = false }: { compact?: boolean }) {
const { theme, setTheme } = useTheme();
const themes = ["a", "b", "c", "d", "e"] as const;
+ const crossfadeTo = (next: typeof themes[number]) => {
+ // 1) capture current hero computed background (all layers resolved)
+ const hero = document.querySelector
(".bg-hero");
+ const prevBg = hero ? getComputedStyle(hero).backgroundImage : "";
+
+ // 2) stash it in a CSS var & flag crossfade
+ const root = document.documentElement;
+ root.style.setProperty("--hero-bg-prev", prevBg);
+ root.classList.add("hero-xfade");
+
+ // 3) switch theme (your existing logic)
+ setTheme(next as any);
+
+ // 4) remove crossfade flag after the animation
+ window.setTimeout(() => {
+ root.classList.remove("hero-xfade");
+ root.style.removeProperty("--hero-bg-prev");
+ }, 600); // a bit > .55s animation
+ };
+
return (
-
-
- {compact ? "Theme" : "Toggle Theme"}
-
+
+ {compact ? "Theme" : "Toggle Theme"}
▾
+
{themes.map((t) => (
setTheme(t as any)}
- className={`w-full rounded px-3 py-2 text-left hover:bg-secondary/60 ${
+ onClick={() => crossfadeTo(t)}
+ className={`w-full rounded px-3 py-2 text-left hover:bg-secondary/60 anim-base ${
theme === t ? "outline outline-1 outline-primary" : ""
}`}
>
Theme {t.toUpperCase()}
diff --git a/src/index.css b/src/index.css
index c936146..956372f 100644
--- a/src/index.css
+++ b/src/index.css
@@ -48,7 +48,7 @@ html[data-theme="d"] {
--color-bg: #0f1014;
--color-secondary: #1d1f24;
--color-text: #eaecef;
- --color-primary: #6c78ff;
+ --color-primary: #7743d8;
--color-tertiary: #a97bff;
--color-contrast: #9ca3af;
}
@@ -69,7 +69,7 @@ html[data-theme="e"] {
@layer utilities {
/* Mobile / default */
- .bg-hero {
+ .bg-hero {
background:
/* Top-right radial accent, similar to desktop */ radial-gradient(
120% 100% at 80% 10%,
@@ -78,10 +78,10 @@ html[data-theme="e"] {
),
/* Slight linear sweep from top to bottom */
linear-gradient(
- 180deg,
+ 120deg,
#0a0d13 0%,
- var(--color-bg) 40%,
- color-mix(in oklab, var(--color-primary) 10%, var(--color-bg) 90%)
+ var(--color-bg) 20%,
+ color-mix(in oklab, var(--color-primary) 15%, var(--color-bg) 90%)
100%
);
}
@@ -92,7 +92,7 @@ html[data-theme="e"] {
background:
/* small, softer highlight lower than the portrait rim */ radial-gradient(
95% 70% at 50% 28%,
- color-mix(in oklab, var(--hero-core) 18%, transparent 82%) 0%,
+ color-mix(in oklab, var(--hero-core) 24%, transparent 82%) 0%,
transparent 56%
),
/* gentle bottom vignette for depth */
@@ -116,12 +116,12 @@ html[data-theme="e"] {
html[data-theme="a"] .bg-hero {
background: radial-gradient(
135% 120% at 80% 48%,
- color-mix(in oklab, var(--color-primary) 65%, black 35%) 0%,
- color-mix(in oklab, var(--color-primary) 45%, black 55%) 38%,
- transparent 74%
+ color-mix(in oklab, var(--color-primary) 50%, black 35%) 0%,
+ color-mix(in oklab, var(--color-primary) 70%, black 55%) 38%,
+ transparent 90%
),
linear-gradient(
- 165deg,
+ 230deg,
#080b10 0%,
color-mix(in oklab, var(--color-bg) 70%, black 30%) 46%,
#0a1324 100%
@@ -131,27 +131,27 @@ html[data-theme="e"] {
html[data-theme="b"] .bg-hero {
background: radial-gradient(
140% 110% at 76% 46%,
- color-mix(in oklab, var(--color-primary) 60%, black 40%) 0%,
- color-mix(in oklab, var(--color-primary) 40%, black 60%) 36%,
- transparent 70%
+ color-mix(in oklab, var(--color-primary) 50%, black 40%) 0%,
+ color-mix(in oklab, var(--color-primary) 70%, black 60%) 36%,
+ transparent 82%
),
linear-gradient(
- 185deg,
+ 230deg,
#140c0b 0%,
- var(--color-bg) 40%,
- color-mix(in oklab, var(--color-tertiary) 6%, var(--color-bg) 94%) 100%
+ var(--color-bg) 20%,
+ color-mix(in oklab, var(--color-secondary) 6%, var(--color-bg) 94%) 100%
);
}
/* Theme C – teal/cyan */
html[data-theme="c"] .bg-hero {
background: radial-gradient(
140% 120% at 76% 48%,
- color-mix(in oklab, var(--color-primary) 58%, black 42%) 0%,
- color-mix(in oklab, var(--color-primary) 40%, black 60%) 36%,
- transparent 72%
+ color-mix(in oklab, var(--color-primary) 50%, black 42%) 0%,
+ color-mix(in oklab, var(--color-primary) 70%, black 60%) 36%,
+ transparent 82%
),
linear-gradient(
- 165deg,
+ 230deg,
#081016 0%,
color-mix(in oklab, var(--color-bg) 62%, black 38%) 44%,
#0a1822 100%
@@ -162,12 +162,12 @@ html[data-theme="e"] {
html[data-theme="d"] .bg-hero {
background: radial-gradient(
135% 120% at 80% 48%,
- color-mix(in oklab, var(--color-primary) 60%, black 40%) 0%,
- color-mix(in oklab, var(--color-primary) 38%, black 62%) 36%,
- transparent 72%
+ color-mix(in oklab, var(--color-primary) 50%, black 40%) 0%,
+ color-mix(in oklab, var(--color-primary) 70%, black 62%) 36%,
+ transparent 82%
),
linear-gradient(
- 165deg,
+ 230deg,
#090a10 0%,
color-mix(in oklab, var(--color-bg) 68%, black 32%) 46%,
#111328 100%
@@ -178,12 +178,12 @@ html[data-theme="e"] {
html[data-theme="e"] .bg-hero {
background: radial-gradient(
140% 120% at 78% 48%,
- color-mix(in oklab, var(--color-primary) 58%, black 42%) 0%,
- color-mix(in oklab, var(--color-primary) 38%, black 62%) 34%,
- transparent 70%
+ color-mix(in oklab, var(--color-primary) 50%, black 42%) 0%,
+ color-mix(in oklab, var(--color-primary) 70%, black 62%) 34%,
+ transparent 82%
),
linear-gradient(
- 165deg,
+ 230deg,
#07100e 0%,
color-mix(in oklab, var(--color-bg) 64%, black 36%) 44%,
#0a1c1a 100%
@@ -198,12 +198,19 @@ html[data-theme="e"] {
pointer-events: none;
/* left-to-right fade of darkness */
background: linear-gradient(
- 90deg,
+ 270deg,
rgba(0, 0, 0, 0.5) 0%,
- rgba(0, 0, 0, 0.34) 30%,
- rgba(0, 0, 0, 0.18) 42%,
+ rgba(0, 0, 0, 0.34) 16%,
+ rgba(0, 0, 0, 0.18) 35%,
rgba(0, 0, 0, 0) 50%
- );
+ ),
+ linear-gradient(
+ 270deg,
+ rgba(0,0,0,0.6) 0%,
+ rgba(0,0,0,0.48) 25%,
+ rgba(0,0,0,0.1) 60%
+ );
+ ;
z-index: 0;
}
/* keep content above the overlay */
@@ -231,4 +238,53 @@ html[data-theme="e"] {
);
box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.35);
}
-}
\ No newline at end of file
+}
+
+/* ── Keyframes ─────────────────────────────────────────── */
+/* ── Keyframes (unchanged) ───────────────────────────── */
+@keyframes ui-fade-in { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:translateY(0)} }
+@keyframes ui-pop-in { from{opacity:0;transform:scale(.96);filter:blur(2px)} to{opacity:1;transform:scale(1);filter:blur(0)} }
+@keyframes ui-fade { from{opacity:0} to{opacity:1} }
+/* Old→new gradient crossfade */
+@keyframes hero-xfade-out { from { opacity: 1 } to { opacity: 0 } }
+
+/* When html has .hero-xfade, paint the OLD gradient on ::after and fade it out */
+html.hero-xfade .bg-hero {
+ position: relative; /* anchor overlay */
+}
+html.hero-xfade .bg-hero::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: 2; /* above everything but below menus if needed */
+ pointer-events: none;
+ background-image: var(--hero-bg-prev, none);
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+ animation: hero-xfade-out .55s ease-out forwards; /* match your new durations */
+}
+
+/* Ensure the base gradient is behind content as usual */
+.bg-hero > * { position: relative; z-index: 3; }
+
+
+/* Respect reduced motion (unchanged) */
+@media (prefers-reduced-motion: reduce) {
+ * { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
+}
+
+/* ── Helpers (longer durations) ──────────────────────── */
+.anim-base { transition: transform .24s ease, opacity .24s ease, filter .24s ease, color .24s ease, background-color .24s ease, border-color .24s ease; }
+.anim-fade-in{ animation: ui-fade-in .55s cubic-bezier(.22,.61,.36,1) both; } /* 550ms */
+.anim-pop-in { animation: ui-pop-in .48s cubic-bezier(.22,.61,.36,1) both; } /* 480ms */
+.anim-fade { animation: ui-fade .45s ease-out both; } /* 450ms */
+
+/* Optional: slightly gentler hover/tap */
+.hover-pop:hover { transform: translateY(-2px) scale(1.03); transition-duration: .24s; }
+.hover-pop:active { transform: translateY(0) scale(.98); transition-duration: .14s; }
+
+/* ── Theme fade-on-switch (keeps it simple) ──────────── */
+/* When the theme changes (html[data-theme] switches), the hero softly fades in */
+html[data-theme] .bg-hero { animation: ui-fade .5s ease-out both; } /* 500ms */
+