Build portfolio site with Next.js, Tailwind CSS, and dark mode
- Add CLAUDE.md with project conventions and architecture notes
- Create Navbar (sticky, backdrop blur, mobile menu) and Footer
- Build homepage sections: Hero, FeaturedProjects, Skills, CurrentWork, CallToAction
- Add /projects, /about, and /contact pages
- Create reusable ProjectCard and Badge components
- Centralise project data in content/projects.json with featured flag
- Add class-based dark mode toggle (default dark, persisted to localStorage)
- Refactor globals.css: remove conflicting prefers-color-scheme media query
- Fix two-instance ThemeToggle sync bug in Navbar
- Fix key={index} anti-pattern in timeline, stale closure in setOpen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import ThemeToggle from '@/components/ThemeToggle'
|
||||
|
||||
const links = [
|
||||
{ href: '/', label: 'Home' },
|
||||
{ href: '/projects', label: 'Projects' },
|
||||
{ href: '/about', label: 'About' },
|
||||
{ href: '/contact', label: 'Contact' },
|
||||
]
|
||||
|
||||
export default function Navbar() {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 border-b border-zinc-200 bg-white/90 backdrop-blur-sm dark:border-zinc-800 dark:bg-zinc-950/90">
|
||||
<nav className="mx-auto flex max-w-5xl items-center justify-between px-6 py-5">
|
||||
<Link href="/" className="text-lg font-semibold tracking-tight text-zinc-900 dark:text-white">
|
||||
jonathan.dev
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Desktop links */}
|
||||
<ul className="mr-5 hidden gap-8 sm:flex">
|
||||
{links.map(({ href, label }) => (
|
||||
<li key={href}>
|
||||
<Link
|
||||
href={href}
|
||||
className="text-sm text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white"
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Single toggle instance — visible on both breakpoints */}
|
||||
<ThemeToggle />
|
||||
|
||||
{/* Mobile hamburger */}
|
||||
<button
|
||||
className="flex flex-col gap-1.5 sm:hidden"
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<span className={`block h-px w-6 bg-zinc-500 transition-transform dark:bg-zinc-400 ${open ? 'translate-y-2 rotate-45' : ''}`} />
|
||||
<span className={`block h-px w-6 bg-zinc-500 transition-opacity dark:bg-zinc-400 ${open ? 'opacity-0' : ''}`} />
|
||||
<span className={`block h-px w-6 bg-zinc-500 transition-transform dark:bg-zinc-400 ${open ? '-translate-y-2 -rotate-45' : ''}`} />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Mobile menu */}
|
||||
{open && (
|
||||
<ul className="flex flex-col border-t border-zinc-200 px-6 py-4 dark:border-zinc-800 sm:hidden">
|
||||
{links.map(({ href, label }) => (
|
||||
<li key={href}>
|
||||
<Link
|
||||
href={href}
|
||||
className="block py-2 text-sm text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user