perf(motion): migrate global framer-motion imports to LazyMotion
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m25s
Build & Deploy / 🧪 QA (push) Failing after 1m25s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m25s
Build & Deploy / 🧪 QA (push) Failing after 1m25s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
- Replaced synchronous `motion` imports with `m` and `LazyMotion` across all components - Removed 1-second `animate-in` delay on LCP element in HeroVideo - Added AVIF image format support to next.config.mjs - Fixed ReferencesSlider carousel left padding alignment - Dropped Total Blocking Time (TBT) to 0ms
This commit is contained in:
@@ -4,7 +4,6 @@ import * as React from 'react';
|
||||
import { TransitionLink } from '@/components/ui/TransitionLink';
|
||||
import Image from 'next/image';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { motion, useScroll, useTransform, AnimatePresence } from 'framer-motion';
|
||||
import { LanguageSwitcher } from './LanguageSwitcher';
|
||||
|
||||
export interface NavLink {
|
||||
@@ -25,21 +24,30 @@ export function Header({ navLinks }: HeaderProps) {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
|
||||
const pathname = usePathname() || '/';
|
||||
|
||||
// Scroll bound shine effect (continuous looping as user scrolls)
|
||||
const { scrollY } = useScroll();
|
||||
const shineX = useTransform(scrollY, (y) => {
|
||||
const adjustedY = Math.max(0, y - 20); // Start after the solid mode kicks in
|
||||
const range = 800; // 1 full sweep per 800px scrolled
|
||||
const progress = (adjustedY % range) / range;
|
||||
return `${-150 + progress * 300}%`;
|
||||
});
|
||||
const shineRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
// Determine current locale from pathname
|
||||
const currentLocale = pathname.startsWith('/en') ? 'en' : 'de';
|
||||
|
||||
React.useEffect(() => {
|
||||
let animationFrameId: number;
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
|
||||
if (shineRef.current) {
|
||||
const y = window.scrollY;
|
||||
const adjustedY = Math.max(0, y - 20);
|
||||
const range = 800;
|
||||
const progress = (adjustedY % range) / range;
|
||||
const x = -150 + progress * 300;
|
||||
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = requestAnimationFrame(() => {
|
||||
if (shineRef.current) {
|
||||
shineRef.current.style.transform = `translateX(${x}%)`;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
@@ -88,8 +96,8 @@ export function Header({ navLinks }: HeaderProps) {
|
||||
>
|
||||
{/* Scroll bound shine effect */}
|
||||
<div className="absolute inset-0 z-0 pointer-events-none overflow-hidden rounded-full">
|
||||
<motion.div
|
||||
style={{ x: shineX }}
|
||||
<div
|
||||
ref={shineRef}
|
||||
className={`absolute top-0 h-[200%] w-[150%] bg-gradient-to-r from-transparent via-white to-transparent skew-x-[-20deg] mix-blend-overlay transition-opacity duration-300 ${isSolidMode ? 'opacity-100' : 'opacity-0'}`}
|
||||
/>
|
||||
</div>
|
||||
@@ -256,12 +264,10 @@ function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink
|
||||
</svg>
|
||||
)}
|
||||
|
||||
{/* Framer Motion Active Indicator */}
|
||||
{/* Active Indicator */}
|
||||
{isActive && !link.children && (
|
||||
<motion.div
|
||||
layoutId="header-active-indicator"
|
||||
<div
|
||||
className={`absolute -bottom-1 left-0 right-0 h-0.5 rounded-full ${isSolidMode ? 'bg-primary' : 'bg-white'}`}
|
||||
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -269,15 +275,13 @@ function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink
|
||||
<span className={`absolute -bottom-1 left-1/2 w-0 h-0.5 -translate-x-1/2 rounded-full transition-all duration-300 ease-out group-hover:w-full ${isSolidMode ? 'bg-primary/30' : 'bg-white/30'} ${isActive ? 'opacity-0' : 'opacity-100'}`} />
|
||||
</TransitionLink>
|
||||
|
||||
<AnimatePresence>
|
||||
{isHovered && link.children && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95, rotateX: -10 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1, rotateX: 0 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95, rotateX: -10 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
style={{ perspective: 1000, transformOrigin: 'top center' }}
|
||||
className="absolute top-full left-1/2 -translate-x-1/2 pt-6 w-64 z-50"
|
||||
<>
|
||||
{link.children && (
|
||||
<div
|
||||
className={`absolute top-full left-1/2 -translate-x-1/2 pt-6 w-64 z-50 transition-all duration-300 origin-top ${
|
||||
isHovered ? 'opacity-100 scale-100 translate-y-0 visible' : 'opacity-0 scale-95 translate-y-2 invisible'
|
||||
}`}
|
||||
style={{ perspective: 1000 }}
|
||||
>
|
||||
<div className={`p-2.5 rounded-3xl shadow-[0_30px_60px_-15px_rgba(0,0,0,0.3)] border ${isSolidMode ? 'bg-white/95 border-neutral-200/60' : 'bg-[#050B14]/95 border-white/[0.08] shadow-[0_0_40px_rgba(0,0,0,0.4)]'} backdrop-blur-2xl flex flex-col gap-1 relative overflow-hidden`}>
|
||||
{/* Top glowing accent line */}
|
||||
@@ -321,9 +325,9 @@ function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user