first commit
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useLocale } from '@/lib/i18n/locale-context';
|
||||
import { LanguageToggle } from './LanguageToggle';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export function Navbar() {
|
||||
const { t, locale } = useLocale();
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setScrolled(window.scrollY > 12);
|
||||
onScroll();
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
const links = [
|
||||
{ href: '#services', label: t.nav.services },
|
||||
{ href: '#stack', label: t.nav.stack },
|
||||
{ href: '#expertise', label: t.nav.expertise },
|
||||
{ href: '#portfolio', label: t.nav.portfolio },
|
||||
{ href: '#blog', label: t.nav.blog },
|
||||
{ href: '#contact', label: t.nav.contact },
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
initial={{ y: -24, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
|
||||
className={cn(
|
||||
'fixed inset-x-0 top-0 z-40 transition-colors duration-300',
|
||||
scrolled
|
||||
? 'border-b border-white/5 bg-base-900/70 backdrop-blur-xl'
|
||||
: 'border-b border-transparent',
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-5 sm:px-8">
|
||||
{/* Logo */}
|
||||
<Link href="/" aria-label="Soroush Asadi" className="group flex items-center gap-2.5">
|
||||
<Image
|
||||
src="/logo-mark.svg"
|
||||
alt=""
|
||||
width={32}
|
||||
height={32}
|
||||
priority
|
||||
className="transition-transform duration-300 group-hover:rotate-[8deg]"
|
||||
/>
|
||||
<span className="hidden sm:inline-flex flex-col leading-tight">
|
||||
<span
|
||||
className={cn(
|
||||
'text-[0.95rem] font-semibold tracking-wide text-slate-100',
|
||||
locale === 'fa' ? 'font-fa' : 'font-en',
|
||||
)}
|
||||
>
|
||||
{locale === 'fa' ? 'سروش اسعدی' : 'Soroush Asadi'}
|
||||
</span>
|
||||
<span className="font-mono text-[0.6rem] uppercase tracking-[0.22em] text-slate-500">
|
||||
AI · Architecture
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Center nav */}
|
||||
<nav
|
||||
className="hidden items-center gap-1 rounded-full border border-white/5 bg-white/[0.02] px-2 py-1.5 md:flex"
|
||||
aria-label="primary"
|
||||
>
|
||||
{links.map((l) => (
|
||||
<a
|
||||
key={l.href}
|
||||
href={l.href}
|
||||
className="rounded-full px-3 py-1.5 text-[0.82rem] text-slate-300 transition-colors hover:bg-white/[0.04] hover:text-white"
|
||||
>
|
||||
{l.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Right cluster */}
|
||||
<div className="flex items-center gap-3">
|
||||
<LanguageToggle />
|
||||
<a href="#contact" className="hidden sm:inline-flex btn-primary text-[0.82rem] !px-4 !py-2">
|
||||
{t.nav.book}
|
||||
<ArrowIcon locale={locale} />
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-full border border-white/10 bg-white/[0.02] text-slate-200"
|
||||
aria-label="Toggle menu"
|
||||
aria-expanded={open}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
||||
{open ? (
|
||||
<>
|
||||
<path d="M6 6 L18 18" />
|
||||
<path d="M18 6 L6 18" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<path d="M4 7 H20" />
|
||||
<path d="M4 12 H20" />
|
||||
<path d="M4 17 H20" />
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile dropdown */}
|
||||
{open && (
|
||||
<div className="md:hidden border-t border-white/5 bg-base-900/95 px-5 py-4 backdrop-blur-xl">
|
||||
<nav className="grid gap-1" aria-label="mobile">
|
||||
{links.map((l) => (
|
||||
<a
|
||||
key={l.href}
|
||||
href={l.href}
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded-lg px-3 py-2 text-sm text-slate-300 hover:bg-white/[0.04] hover:text-white"
|
||||
>
|
||||
{l.label}
|
||||
</a>
|
||||
))}
|
||||
<a
|
||||
href="#contact"
|
||||
onClick={() => setOpen(false)}
|
||||
className="mt-2 btn-primary justify-center"
|
||||
>
|
||||
{t.nav.book}
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
</motion.header>
|
||||
);
|
||||
}
|
||||
|
||||
function ArrowIcon({ locale }: { locale: 'fa' | 'en' }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
height="14"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.4"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={locale === 'fa' ? 'rotate-180' : ''}
|
||||
aria-hidden
|
||||
>
|
||||
<path d="M5 12 H19" />
|
||||
<path d="M13 6 L19 12 L13 18" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user