This commit is contained in:
2026-01-26 22:19:13 +01:00
parent 615757963c
commit b43b1c4314

View File

@@ -2,6 +2,7 @@
import Link from 'next/link'; import Link from 'next/link';
import Image from 'next/image'; import Image from 'next/image';
import { motion } from 'framer-motion';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { Button } from './ui'; import { Button } from './ui';
@@ -70,79 +71,169 @@ export default function Header() {
return ( return (
<> <>
<header className={headerClass}> <motion.header
className={headerClass}
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
>
<div className="container mx-auto px-4 md:px-12 lg:px-16 max-w-7xl flex items-center justify-between"> <div className="container mx-auto px-4 md:px-12 lg:px-16 max-w-7xl flex items-center justify-between">
<Link href={`/${currentLocale}`} className="flex-shrink-0 group touch-target"> <motion.div
<Image className="flex-shrink-0 group touch-target"
src={logoSrc} initial={{ scale: 0.8, opacity: 0 }}
alt={t('home')} animate={{ scale: 1, opacity: 1 }}
width={120} transition={{ duration: 0.6, ease: "easeOut", delay: 0.1 }}
height={120} >
className="h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110" <Link href={`/${currentLocale}`}>
priority <Image
unoptimized src={logoSrc}
/> alt={t('home')}
</Link> width={120}
height={120}
className="h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110"
priority
unoptimized
/>
</Link>
</motion.div>
<div className="flex items-center gap-4 md:gap-12"> <motion.div
<nav className="hidden lg:flex items-center space-x-10"> className="flex items-center gap-4 md:gap-12"
{menuItems.map((item) => ( initial="hidden"
<Link animate="visible"
key={item.href} variants={{
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`} visible: {
className={cn( transition: {
textColorClass, staggerChildren: 0.08,
"hover:text-accent font-bold transition-all text-base md:text-lg tracking-tight relative group" delayChildren: 0.3
)} }
}
}}
>
<motion.nav
className="hidden lg:flex items-center space-x-10"
variants={navVariants}
>
{menuItems.map((item, idx) => (
<motion.div
key={item.href}
variants={navLinkVariants}
custom={idx}
> >
{item.label} <Link
<span className="absolute -bottom-2 left-0 w-0 h-1 bg-accent transition-all duration-300 group-hover:w-full rounded-full" /> href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
</Link> className={cn(
textColorClass,
"hover:text-accent font-bold transition-all text-base md:text-lg tracking-tight relative group inline-block"
)}
>
{item.label}
<span className="absolute -bottom-2 left-0 w-0 h-1 bg-accent transition-all duration-300 group-hover:w-full rounded-full" />
</Link>
</motion.div>
))} ))}
</nav> </motion.nav>
<div className={cn("hidden lg:flex items-center space-x-8", textColorClass)}> <motion.div
<div className="flex items-center space-x-4 text-xs md:text-sm font-extrabold tracking-widest uppercase"> className={cn("hidden lg:flex items-center space-x-8", textColorClass)}
<Link variants={headerRightVariants}
href={getPathForLocale('en')} >
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`} <motion.div
> className="flex items-center space-x-4 text-xs md:text-sm font-extrabold tracking-widest uppercase"
EN initial={{ opacity: 0, x: 20 }}
</Link> animate={{ opacity: 1, x: 0 }}
<div className="w-px h-4 bg-current opacity-20" /> transition={{ duration: 0.5, delay: 0.6 }}
<Link
href={getPathForLocale('de')}
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
>
DE
</Link>
</div>
<Button
href={`/${currentLocale}/contact`}
variant="white"
size="md"
className="px-8 shadow-xl"
> >
{t('contact')} <motion.div
</Button> initial={{ opacity: 0, scale: 0.8 }}
</div> animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.65 }}
>
<Link
href={getPathForLocale('en')}
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
>
EN
</Link>
</motion.div>
<motion.div
className="w-px h-4 bg-current opacity-20"
initial={{ scaleY: 0 }}
animate={{ scaleY: 1 }}
transition={{ duration: 0.4, delay: 0.7 }}
/>
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.75 }}
>
<Link
href={getPathForLocale('de')}
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
>
DE
</Link>
</motion.div>
</motion.div>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.6, type: "spring", stiffness: 400, delay: 0.7 }}
>
<Button
href={`/${currentLocale}/contact`}
variant="white"
size="md"
className="px-8 shadow-xl"
>
{t('contact')}
</Button>
</motion.div>
</motion.div>
{/* Mobile Menu Button */} {/* Mobile Menu Button */}
<button <motion.button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className={cn("lg:hidden touch-target p-2 rounded-xl bg-white/10 border border-white/20 z-50", textColorClass)} className={cn("lg:hidden touch-target p-2 rounded-xl bg-white/10 border border-white/20 z-50", textColorClass)}
aria-label={t('toggleMenu')} aria-label={t('toggleMenu')}
initial={{ scale: 0.8, opacity: 0, rotate: -180 }}
animate={{ scale: 1, opacity: 1, rotate: 0 }}
transition={{ duration: 0.6, type: "spring", stiffness: 300, damping: 20, delay: 0.5 }}
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
> >
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <motion.svg
className="w-7 h-7"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 0.3, delay: 0.6 }}
>
{isMobileMenuOpen ? ( {isMobileMenuOpen ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <motion.path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.4, delay: 0.7 }}
/>
) : ( ) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> <motion.path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.4, delay: 0.7 }}
/>
)} )}
</svg> </motion.svg>
</button> </motion.button>
</div> </motion.div>
</div> </div>
{/* Mobile Menu Overlay */} {/* Mobile Menu Overlay */}
@@ -150,58 +241,157 @@ export default function Header() {
"fixed inset-0 bg-primary z-40 lg:hidden transition-all duration-500 ease-in-out flex flex-col", "fixed inset-0 bg-primary z-40 lg:hidden transition-all duration-500 ease-in-out flex flex-col",
isMobileMenuOpen ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-full pointer-events-none" isMobileMenuOpen ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-full pointer-events-none"
)}> )}>
<div className="flex-grow flex flex-col justify-center items-center p-8 space-y-8"> <motion.div
className="flex-grow flex flex-col justify-center items-center p-8 space-y-8"
initial="closed"
animate={isMobileMenuOpen ? "open" : "closed"}
variants={{
open: {
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
}}
>
{menuItems.map((item, idx) => ( {menuItems.map((item, idx) => (
<Link <motion.div
key={item.href} key={item.href}
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`} variants={{
className={cn( closed: { opacity: 0, y: 50, scale: 0.9 },
"text-2xl md:text-3xl font-extrabold text-white hover:text-accent transition-all transform", open: {
isMobileMenuOpen ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0" opacity: 1,
)} y: 0,
style={{ transitionDelay: `${idx * 100}ms` }} scale: 1,
transition: {
duration: 0.6,
ease: "easeOut",
delay: idx * 0.08
}
}
}}
> >
{item.label} <Link
</Link> href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
className="text-2xl md:text-3xl font-extrabold text-white hover:text-accent transition-all transform block py-4"
>
{item.label}
</Link>
</motion.div>
))} ))}
<div className={cn( <motion.div
"pt-8 border-t border-white/10 w-full flex flex-col items-center space-y-8 transition-all duration-500 delay-500", className="pt-8 border-t border-white/10 w-full flex flex-col items-center space-y-8"
isMobileMenuOpen ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0" initial={{ opacity: 0, y: 30 }}
)}> animate={isMobileMenuOpen ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
<div className="flex items-center space-x-8 text-lg md:text-xl font-extrabold tracking-widest uppercase text-white"> transition={{ duration: 0.5, delay: 0.8 }}
<Link >
href={getPathForLocale('en')} <motion.div
className={`hover:text-accent transition-colors ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`} className="flex items-center space-x-8 text-lg md:text-xl font-extrabold tracking-widest uppercase text-white"
> initial={{ opacity: 0, scale: 0.8 }}
EN animate={{ opacity: 1, scale: 1 }}
</Link> transition={{ duration: 0.4, delay: 0.9 }}
<div className="w-px h-6 bg-white/20" />
<Link
href={getPathForLocale('de')}
className={`hover:text-accent transition-colors ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
>
DE
</Link>
</div>
<Button
href={`/${currentLocale}/contact`}
variant="accent"
size="lg"
className="w-full max-w-xs py-6 text-lg md:text-xl shadow-2xl"
> >
{t('contact')} <motion.div
</Button> initial={{ opacity: 0 }}
</div> animate={{ opacity: 1 }}
</div> transition={{ duration: 0.3, delay: 1.0 }}
>
<Link
href={getPathForLocale('en')}
className={`hover:text-accent transition-colors ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
>
EN
</Link>
</motion.div>
<motion.div
className="w-px h-6 bg-white/20"
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 0.4, delay: 1.05 }}
/>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3, delay: 1.1 }}
>
<Link
href={getPathForLocale('de')}
className={`hover:text-accent transition-colors ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
>
DE
</Link>
</motion.div>
</motion.div>
<motion.div
initial={{ scale: 0.9, opacity: 0, y: 20 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 400, damping: 20, delay: 1.2 }}
>
<Button
href={`/${currentLocale}/contact`}
variant="accent"
size="lg"
className="w-full max-w-xs py-6 text-lg md:text-xl shadow-2xl"
>
{t('contact')}
</Button>
</motion.div>
</motion.div>
{/* Bottom Branding */} {/* Bottom Branding */}
<div className="p-12 flex justify-center opacity-20"> <motion.div
<Image src="/logo-white.svg" alt={t('home')} width={80} height={80} unoptimized /> className="p-12 flex justify-center opacity-20"
</div> initial={{ opacity: 0, scale: 0.8 }}
animate={isMobileMenuOpen ? { opacity: 0.2, scale: 1 } : { opacity: 0, scale: 0.8 }}
transition={{ duration: 0.5, delay: 1.4 }}
>
<motion.div
initial={{ scale: 0.5 }}
animate={{ scale: 1 }}
transition={{ type: "spring", stiffness: 300, delay: 1.5 }}
>
<Image src="/logo-white.svg" alt={t('home')} width={80} height={80} unoptimized />
</motion.div>
</motion.div>
</motion.div>
</div> </div>
</header> </motion.header>
</> </>
); );
} }
const navVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.06,
delayChildren: 0.1
}
}
} as const;
const navLinkVariants = {
hidden: { opacity: 0, y: 20, scale: 0.9 },
visible: (idx: number) => ({
opacity: 1,
y: 0,
scale: 1,
transition: {
duration: 0.5,
ease: [0.25, 0.46, 0.45, 0.94],
delay: idx * 0.06
}
})
} as const;
const headerRightVariants = {
hidden: { opacity: 0, x: 30 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.6, ease: "easeOut" }
}
} as const;