'use client'; import { motion, AnimatePresence } from 'framer-motion'; import { usePathname } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useReducedMotion } from '@/hooks/use-reduced-motion'; const pageVariants = { initial: { opacity: 0, y: 8, scale: 0.98, }, animate: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.4, ease: [0.25, 1, 0.5, 1] as const, staggerChildren: 0.05, }, }, exit: { opacity: 0, y: -8, scale: 0.98, transition: { duration: 0.2, ease: [0.22, 1, 0.36, 1] as const, }, }, }; export function PageTransition({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const shouldReduceMotion = useReducedMotion(); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); // SSR 和首次 hydration 阶段直接渲染子内容,避免 framer-motion DOM 差异导致 hydration mismatch if (!mounted || shouldReduceMotion) { return <>{children}; } return ( {children} ); } const sectionVariants = { hidden: { opacity: 0, y: 20 }, visible: (i: number) => ({ opacity: 1, y: 0, transition: { delay: i * 0.08, duration: 0.5, ease: [0.25, 1, 0.5, 1] as const, }, }), }; export function StaggerChildren({ children, className = '', staggerCount = 4 }: { children: React.ReactNode; className?: string; staggerCount?: number; }) { const shouldReduceMotion = useReducedMotion(); if (shouldReduceMotion) { return
{children}
; } return ( {Array.isArray(children) ? children.map((child, i) => ( {child} )) : children } ); }