148 lines
5.0 KiB
TypeScript
148 lines
5.0 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState, useEffect } from 'react';
|
||
|
|
import { Menu, X } from 'lucide-react';
|
||
|
|
import { motion } from 'framer-motion';
|
||
|
|
import { usePathname, useRouter } from 'next/navigation';
|
||
|
|
import Image from 'next/image';
|
||
|
|
|
||
|
|
export function Header() {
|
||
|
|
const pathname = usePathname();
|
||
|
|
const router = useRouter();
|
||
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||
|
|
const [showHeader, setShowHeader] = useState(true);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
let lastScroll = 0;
|
||
|
|
|
||
|
|
// Hide header on scroll down, show on scroll up
|
||
|
|
const handleScroll = () => {
|
||
|
|
const currentScrollY = window.scrollY;
|
||
|
|
|
||
|
|
// Always show header at the top of the page
|
||
|
|
if (currentScrollY < 10) {
|
||
|
|
setShowHeader(true);
|
||
|
|
} else if (currentScrollY > lastScroll) {
|
||
|
|
// Scrolling down
|
||
|
|
setShowHeader(false);
|
||
|
|
} else {
|
||
|
|
// Scrolling up
|
||
|
|
setShowHeader(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
lastScroll = currentScrollY;
|
||
|
|
};
|
||
|
|
|
||
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
||
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
||
|
|
}, []); // Empty dependency array - only set up once
|
||
|
|
|
||
|
|
const handleNavigate = (path: string) => {
|
||
|
|
setMobileMenuOpen(false);
|
||
|
|
router.push(path);
|
||
|
|
};
|
||
|
|
|
||
|
|
const navItems = [
|
||
|
|
{ path: '/calculator', label: 'Calculator' },
|
||
|
|
{ path: '/how-it-works', label: 'How it Works' },
|
||
|
|
{ path: '/about', label: 'About' },
|
||
|
|
{ path: '/contact', label: 'Contact' },
|
||
|
|
];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<header
|
||
|
|
className="glass-nav shadow-luxury z-50 fixed top-0 left-0 right-0 transition-transform duration-300 ease-in-out"
|
||
|
|
style={{
|
||
|
|
transform: showHeader ? 'translate3d(0,0,0)' : 'translate3d(0,-100%,0)',
|
||
|
|
WebkitTransform: showHeader ? 'translate3d(0,0,0)' : 'translate3d(0,-100%,0)',
|
||
|
|
WebkitBackfaceVisibility: 'hidden',
|
||
|
|
backfaceVisibility: 'hidden'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div className="max-w-7xl mx-auto px-4 py-3 sm:px-6 lg:px-8">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<motion.div
|
||
|
|
className="flex items-center space-x-3 cursor-pointer group"
|
||
|
|
onClick={() => handleNavigate('/')}
|
||
|
|
whileHover={{ scale: 1.02 }}
|
||
|
|
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
||
|
|
>
|
||
|
|
<motion.div
|
||
|
|
className="relative h-10 w-10"
|
||
|
|
initial={{ opacity: 0, rotate: -10 }}
|
||
|
|
animate={{ opacity: 1, rotate: 0 }}
|
||
|
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||
|
|
>
|
||
|
|
<Image
|
||
|
|
src="/puffinOffset.webp"
|
||
|
|
alt="Puffin Offset Logo"
|
||
|
|
width={40}
|
||
|
|
height={40}
|
||
|
|
className="transition-transform duration-300 group-hover:scale-110"
|
||
|
|
/>
|
||
|
|
</motion.div>
|
||
|
|
<motion.h1
|
||
|
|
className="text-xl font-bold heading-luxury"
|
||
|
|
initial={{ opacity: 0, x: -20 }}
|
||
|
|
animate={{ opacity: 1, x: 0 }}
|
||
|
|
transition={{ duration: 0.6, delay: 0.4 }}
|
||
|
|
>
|
||
|
|
Puffin Offset
|
||
|
|
</motion.h1>
|
||
|
|
</motion.div>
|
||
|
|
|
||
|
|
{/* Mobile menu button */}
|
||
|
|
<button
|
||
|
|
className="sm:hidden p-2 rounded-md text-gray-600 hover:text-gray-900"
|
||
|
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||
|
|
aria-label="Toggle menu"
|
||
|
|
>
|
||
|
|
{mobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||
|
|
</button>
|
||
|
|
|
||
|
|
{/* Desktop navigation */}
|
||
|
|
<nav className="hidden sm:flex space-x-2">
|
||
|
|
{navItems.map((item, index) => (
|
||
|
|
<motion.button
|
||
|
|
key={item.path}
|
||
|
|
onClick={() => handleNavigate(item.path)}
|
||
|
|
className={`px-4 py-2 rounded-xl font-medium transition-all duration-300 ${
|
||
|
|
pathname === item.path
|
||
|
|
? 'bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-lg'
|
||
|
|
: 'text-slate-600 hover:text-slate-900 hover:bg-white/60'
|
||
|
|
}`}
|
||
|
|
whileHover={{ scale: 1.05 }}
|
||
|
|
whileTap={{ scale: 0.95 }}
|
||
|
|
initial={{ opacity: 0, y: -10 }}
|
||
|
|
animate={{ opacity: 1, y: 0 }}
|
||
|
|
transition={{ duration: 0.4, delay: 0.6 + index * 0.1 }}
|
||
|
|
>
|
||
|
|
{item.label}
|
||
|
|
</motion.button>
|
||
|
|
))}
|
||
|
|
</nav>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Mobile navigation */}
|
||
|
|
{mobileMenuOpen && (
|
||
|
|
<nav className="sm:hidden mt-4 pb-2 space-y-2">
|
||
|
|
{navItems.map((item) => (
|
||
|
|
<button
|
||
|
|
key={item.path}
|
||
|
|
onClick={() => handleNavigate(item.path)}
|
||
|
|
className={`block w-full text-left px-4 py-2 rounded-lg ${
|
||
|
|
pathname === item.path
|
||
|
|
? 'bg-blue-50 text-blue-600 font-semibold'
|
||
|
|
: 'text-gray-600 hover:bg-gray-50'
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
{item.label}
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
</nav>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
);
|
||
|
|
}
|