'use client'
import { useEffect, useRef, useState } from 'react'
import { motion, useInView, useAnimation } from 'framer-motion'
// Scroll-triggered animation hook
export function useScrollAnimation(threshold = 0.1, once = true) {
const ref = useRef(null)
const isInView = useInView(ref, { threshold, once })
const controls = useAnimation()
useEffect(() => {
if (isInView) {
controls.start('visible')
}
}, [isInView, controls])
return { ref, controls }
}
// Fade up animation component
export function FadeUp({ children, delay = 0, className = '' }: {
children: React.ReactNode
delay?: number
className?: string
}) {
const { ref, controls } = useScrollAnimation()
return (
{children}
)
}
// Stagger children animation
export function StaggerContainer({ children, className = '', staggerDelay = 0.1 }: {
children: React.ReactNode
className?: string
staggerDelay?: number
}) {
const { ref, controls } = useScrollAnimation()
return (
{children}
)
}
// Individual stagger item
export function StaggerItem({ children, className = '' }: {
children: React.ReactNode
className?: string
}) {
return (
{children}
)
}
// Scale in animation
export function ScaleIn({ children, delay = 0, className = '' }: {
children: React.ReactNode
delay?: number
className?: string
}) {
const { ref, controls } = useScrollAnimation()
return (
{children}
)
}
// Slide in from left
export function SlideInLeft({ children, delay = 0, className = '' }: {
children: React.ReactNode
delay?: number
className?: string
}) {
const { ref, controls } = useScrollAnimation()
return (
{children}
)
}
// Slide in from right
export function SlideInRight({ children, delay = 0, className = '' }: {
children: React.ReactNode
delay?: number
className?: string
}) {
const { ref, controls } = useScrollAnimation()
return (
{children}
)
}
// Hover card with micro-interactions
export function HoverCard({ children, className = '' }: {
children: React.ReactNode
className?: string
}) {
return (
{children}
)
}
// Magnetic button effect
export function MagneticButton({ children, className = '', strength = 0.3 }: {
children: React.ReactNode
className?: string
strength?: number
}) {
const ref = useRef(null)
const handleMouseMove = (e: React.MouseEvent) => {
if (!ref.current) return
const rect = ref.current.getBoundingClientRect()
const x = e.clientX - rect.left - rect.width / 2
const y = e.clientY - rect.top - rect.height / 2
ref.current.style.transform = `translate(${x * strength}px, ${y * strength}px)`
}
const handleMouseLeave = () => {
if (!ref.current) return
ref.current.style.transform = 'translate(0px, 0px)'
}
return (
{children}
)
}
// Counter animation
export function CountUp({ end, duration = 2, suffix = '' }: {
end: number
duration?: number
suffix?: string
}) {
const ref = useRef(null)
const isInView = useInView(ref, { threshold: 0.1, once: true })
const [count, setCount] = useState(0)
useEffect(() => {
if (!isInView) return
let startTime: number
let animationFrame: number
const animate = (timestamp: number) => {
if (!startTime) startTime = timestamp
const progress = Math.min((timestamp - startTime) / (duration * 1000), 1)
setCount(Math.floor(progress * end))
if (progress < 1) {
animationFrame = requestAnimationFrame(animate)
}
}
animationFrame = requestAnimationFrame(animate)
return () => {
if (animationFrame) {
cancelAnimationFrame(animationFrame)
}
}
}, [isInView, end, duration])
return (
{count}{suffix}
)
}
// Parallax effect
export function ParallaxElement({ children, speed = 0.5, className = '' }: {
children: React.ReactNode
speed?: number
className?: string
}) {
const ref = useRef(null)
useEffect(() => {
const element = ref.current
if (!element) return
const handleScroll = () => {
const scrolled = window.pageYOffset
const rect = element.getBoundingClientRect()
const elementTop = rect.top + scrolled
const elementHeight = rect.height
const windowHeight = window.innerHeight
if (scrolled + windowHeight > elementTop && scrolled < elementTop + elementHeight) {
const yPos = -(scrolled - elementTop) * speed
element.style.transform = `translateY(${yPos}px)`
}
}
let ticking = false
const throttledScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
handleScroll()
ticking = false
})
ticking = true
}
}
window.addEventListener('scroll', throttledScroll, { passive: true })
return () => window.removeEventListener('scroll', throttledScroll)
}, [speed])
return (
{children}
)
}