From 3bb0a5e4a908b4284973666f5e7b88140eb6e5d3 Mon Sep 17 00:00:00 2001 From: Jesse Egbosionu Date: Sat, 24 Oct 2026 21:19:22 +0200 Subject: [PATCH] feat: redesign site as a modern developer portfolio - New home page: hero, stats, about, focus areas, stack, projects, contact - Light/dark theme with system default and persisted toggle - Content centralised in data/profile.js - Restyle /rates to match; remove it from nav and projects - Afriex: log a clear error now that its API requires a device id Co-Authored-By: Claude Opus 5.5 (1M context) --- components/AnimatedName.js | 22 +- components/Footer.js | 11 +- components/Header.js | 75 +- components/Icon.js | 86 ++ components/Layout.js | 21 +- components/ProjectCard.js | 48 + components/RateCard.js | 19 +- components/Reveal.js | 37 + components/SectionHeading.js | 14 + components/SocialLinks.js | 44 +- components/ThemeToggle.js | 30 + data/profile.js | 88 ++ pages/_document.js | 19 + pages/api/v1/rates.js | 8 +- pages/index.js | 193 +++- pages/rates.js | 150 +-- styles/globals.scss | 1880 ++++++++++++++++++++++------------ 17 files changed, 1873 insertions(+), 872 deletions(-) create mode 100644 components/Icon.js create mode 100644 components/ProjectCard.js create mode 100644 components/Reveal.js create mode 100644 components/SectionHeading.js create mode 100644 components/ThemeToggle.js create mode 100644 data/profile.js create mode 100644 pages/_document.js diff --git a/components/AnimatedName.js b/components/AnimatedName.js index e146307..ee2f2e8 100644 --- a/components/AnimatedName.js +++ b/components/AnimatedName.js @@ -1,27 +1,33 @@ import { useState, useEffect } from 'react'; +import { profile } from '../data/profile'; -const names = ['πŸ‘¨πŸΏβ€πŸ’Ό Jesse Egbosionu', 'πŸ‘¨πŸΏβ€πŸ’» jesseinit', '😎 jessewithit']; +const names = profile.handles; export default function AnimatedName() { const [currentIndex, setCurrentIndex] = useState(0); const [isAnimating, setIsAnimating] = useState(false); useEffect(() => { + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + + let timeout; const interval = setInterval(() => { setIsAnimating(true); - - setTimeout(() => { + timeout = setTimeout(() => { setCurrentIndex((prevIndex) => (prevIndex + 1) % names.length); setIsAnimating(false); - }, 500); // Half of animation duration for smooth transition - }, 3000); // Change name every 3 seconds + }, 350); + }, 3000); - return () => clearInterval(interval); + return () => { + clearInterval(interval); + clearTimeout(timeout); + }; }, []); return ( - - + + diff --git a/components/Footer.js b/components/Footer.js index 62f872c..becbd03 100644 --- a/components/Footer.js +++ b/components/Footer.js @@ -1,11 +1,14 @@ +import SocialLinks from './SocialLinks'; +import { profile } from '../data/profile'; + export default function Footer() { return (
-
-

- Built with ❀️ +

+

+ Β© {new Date().getFullYear()} {profile.name}. Built with Next.js, shipped on Kubernetes.

-

Β© {new Date().getFullYear()}

+
); diff --git a/components/Header.js b/components/Header.js index d778c16..977745d 100644 --- a/components/Header.js +++ b/components/Header.js @@ -1,31 +1,58 @@ +import { useEffect, useState } from 'react'; import Link from 'next/link'; +import ThemeToggle from './ThemeToggle'; +import Icon from './Icon'; + +const links = [ + { href: '/#about', label: 'About' }, + { href: '/#stack', label: 'Stack' }, + { href: '/#projects', label: 'Projects' }, +]; + +export default function Header() { + const [scrolled, setScrolled] = useState(false); + const [open, setOpen] = useState(false); + + useEffect(() => { + const onScroll = () => setScrolled(window.scrollY > 8); + onScroll(); + window.addEventListener('scroll', onScroll, { passive: true }); + return () => window.removeEventListener('scroll', onScroll); + }, []); -export default function Header({ showNavLinks = true }) { return ( -
-