'use client'; import { motion, useScroll, useTransform, useSpring, type Variants, type HTMLMotionProps } from 'framer-motion'; import { useRef, type ReactNode } from 'react'; export const scrollRevealVariants: Variants = { hidden: { opacity: 0, y: 50, scale: 0.95, }, visible: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1], }, }, }; export const inkRevealVariants: Variants = { hidden: { opacity: 0, scale: 0.8, filter: 'blur(10px)', }, visible: { opacity: 1, scale: 1, filter: 'blur(0px)', transition: { duration: 1, ease: [0.16, 1, 0.3, 1], }, }, }; export const slideInLeftVariants: Variants = { hidden: { opacity: 0, x: -50, }, visible: { opacity: 1, x: 0, transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1], }, }, }; export const slideInRightVariants: Variants = { hidden: { opacity: 0, x: 50, }, visible: { opacity: 1, x: 0, transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1], }, }, }; interface ScrollRevealProps extends HTMLMotionProps<'div'> { children: ReactNode; className?: string; delay?: number; variants?: Variants; once?: boolean; threshold?: number; } export function ScrollReveal({ children, className = '', delay = 0, variants: _variants = scrollRevealVariants, once = true, threshold = 0.1, ...props }: ScrollRevealProps) { const ref = useRef(null); const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'start center'], }); const opacity = useTransform(scrollYProgress, [0, threshold], [0, 1]); const y = useTransform(scrollYProgress, [0, threshold], [50, 0]); const scale = useTransform(scrollYProgress, [0, threshold], [0.95, 1]); const smoothOpacity = useSpring(opacity, { stiffness: 100, damping: 30 }); const smoothY = useSpring(y, { stiffness: 100, damping: 30 }); const smoothScale = useSpring(scale, { stiffness: 100, damping: 30 }); return ( {children} ); } interface ParallaxSectionProps extends HTMLMotionProps<'section'> { children: ReactNode; className?: string; speed?: number; } export function ParallaxSection({ children, className = '', speed = 0.5, ...props }: ParallaxSectionProps) { const ref = useRef(null); const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'], }); const y = useTransform(scrollYProgress, [0, 1], ['0%', `${speed * 100}%`]); const smoothY = useSpring(y, { stiffness: 100, damping: 30 }); return ( {children} ); } interface ParallaxImageProps { src: string; alt: string; className?: string; speed?: number; } export function ParallaxImage({ src, alt, className = '', speed = 0.3 }: ParallaxImageProps) { const ref = useRef(null); const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'], }); const y = useTransform(scrollYProgress, [0, 1], [`${-speed * 50}%`, `${speed * 50}%`]); const scale = useTransform(scrollYProgress, [0, 0.5, 1], [1.1, 1, 1.1]); return (
); } interface ScaleOnScrollProps extends HTMLMotionProps<'div'> { children: ReactNode; className?: string; minScale?: number; maxScale?: number; } export function ScaleOnScroll({ children, className = '', minScale = 0.8, maxScale = 1, ...props }: ScaleOnScrollProps) { const ref = useRef(null); const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'center center'], }); const scale = useTransform(scrollYProgress, [0, 1], [minScale, maxScale]); const smoothScale = useSpring(scale, { stiffness: 100, damping: 30 }); return ( {children} ); } interface FadeOnScrollProps extends HTMLMotionProps<'div'> { children: ReactNode; className?: string; direction?: 'up' | 'down' | 'left' | 'right'; distance?: number; } export function FadeOnScroll({ children, className = '', direction = 'up', distance = 50, ...props }: FadeOnScrollProps) { const ref = useRef(null); const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'center center'], }); const transformUp = useTransform(scrollYProgress, [0, 1], [distance, 0]); const transformDown = useTransform(scrollYProgress, [0, 1], [-distance, 0]); const transformLeft = useTransform(scrollYProgress, [0, 1], [distance, 0]); const transformRight = useTransform(scrollYProgress, [0, 1], [-distance, 0]); const getTransform = () => { switch (direction) { case 'up': return transformUp; case 'down': return transformDown; case 'left': return transformLeft; case 'right': return transformRight; default: return transformUp; } }; const transform = getTransform(); const opacity = useTransform(scrollYProgress, [0, 0.5], [0, 1]); const smoothTransform = useSpring(transform, { stiffness: 100, damping: 30 }); const smoothOpacity = useSpring(opacity, { stiffness: 100, damping: 30 }); const style = direction === 'left' || direction === 'right' ? { x: smoothTransform, opacity: smoothOpacity } : { y: smoothTransform, opacity: smoothOpacity }; return ( {children} ); } interface StaggerRevealProps extends HTMLMotionProps<'div'> { children: ReactNode; className?: string; staggerDelay?: number; containerDelay?: number; } export function StaggerReveal({ children, className = '', staggerDelay = 0.1, containerDelay = 0, ...props }: StaggerRevealProps) { const containerVariants: Variants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: staggerDelay, delayChildren: containerDelay, }, }, }; return ( {children} ); } interface StaggerItemProps extends HTMLMotionProps<'div'> { children: ReactNode; className?: string; } export function StaggerItem({ children, className = '', ...props }: StaggerItemProps) { const itemVariants: Variants = { hidden: { opacity: 0, y: 30, scale: 0.95, }, visible: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1], }, }, }; return ( {children} ); } interface TextRevealProps extends HTMLMotionProps<'div'> { text: string; className?: string; delay?: number; } export function TextReveal({ text, className = '', delay = 0, ...props }: TextRevealProps) { const words = text.split(' '); const containerVariants: Variants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.05, delayChildren: delay, }, }, }; const wordVariants: Variants = { hidden: { opacity: 0, y: 20, }, visible: { opacity: 1, y: 0, transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1], }, }, }; return ( {words.map((word, index) => ( {word} ))} ); } interface ProgressIndicatorProps { className?: string; color?: string; } export function ProgressIndicator({ className = '', color = 'var(--color-brand-primary)' }: ProgressIndicatorProps) { const { scrollYProgress } = useScroll(); const scaleX = useSpring(scrollYProgress, { stiffness: 100, damping: 30 }); return ( ); } interface ScrollTriggeredCounterProps { end: number; duration?: number; prefix?: string; suffix?: string; className?: string; } export function ScrollTriggeredCounter({ end, duration: _duration = 2, prefix = '', suffix = '', className = '', }: ScrollTriggeredCounterProps) { const ref = useRef(null); const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'center center'], }); const displayNumber = useTransform(scrollYProgress, [0, 1], [0, end]); const rounded = useTransform(displayNumber, (latest) => Math.round(latest)); return ( {prefix} {rounded} {suffix} ); }