michaelpeskov/components/DogstudioAnimations.tsx

202 lines
5.3 KiB
TypeScript

'use client'
import { useEffect, useRef } from 'react'
import { animateSplit, animateWords } from '@/lib/animateSplit'
// Dynamic imports to avoid SSR issues
let gsap: any
let ScrollTrigger: any
if (typeof window !== 'undefined') {
gsap = require('gsap').gsap
ScrollTrigger = require('gsap/ScrollTrigger').ScrollTrigger
gsap.registerPlugin(ScrollTrigger)
}
export default function DogstudioAnimations() {
const initialized = useRef(false)
useEffect(() => {
if (initialized.current) return
initialized.current = true
// Check if GSAP is available
if (!gsap || !ScrollTrigger) return
// Check for reduced motion
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
if (prefersReducedMotion) return
const ctx = gsap.context(() => {
// Animate main headline with character split
const mainHeadline = document.querySelector('.h1')
if (mainHeadline) {
gsap.set(mainHeadline, { opacity: 1 })
animateSplit(mainHeadline as HTMLElement, {
delay: 0.5,
stagger: 0.02,
duration: 0.8
})
}
// Animate section headings as they come into view
const sectionHeadings = gsap.utils.toArray<HTMLElement>('.h2')
sectionHeadings.forEach((heading) => {
gsap.set(heading, { opacity: 0 })
ScrollTrigger.create({
trigger: heading,
start: 'top 80%',
onEnter: () => {
gsap.set(heading, { opacity: 1 })
animateWords(heading, { stagger: 0.05 })
}
})
})
// Animate cards with stagger
const cards = gsap.utils.toArray<HTMLElement>('.card, .gallery-item, .testimonial')
cards.forEach((card, i) => {
gsap.from(card, {
y: 60,
opacity: 0,
duration: 0.8,
ease: 'power2.out',
scrollTrigger: {
trigger: card,
start: 'top 85%',
toggleActions: 'play none none reverse'
}
})
})
// Animate chips with stagger
const chips = gsap.utils.toArray<HTMLElement>('.chip')
if (chips.length > 0) {
gsap.from(chips, {
scale: 0,
opacity: 0,
duration: 0.4,
stagger: 0.1,
ease: 'back.out(1.7)',
scrollTrigger: {
trigger: chips[0],
start: 'top 80%',
toggleActions: 'play none none reverse'
}
})
}
// Animate gallery items with hover effects
const galleryItems = gsap.utils.toArray<HTMLElement>('.gallery-item')
galleryItems.forEach((item) => {
const img = item.querySelector('img')
if (!img) return
const handleMouseEnter = () => {
gsap.to(img, {
scale: 1.05,
duration: 0.3,
ease: 'power2.out'
})
}
const handleMouseLeave = () => {
gsap.to(img, {
scale: 1,
duration: 0.3,
ease: 'power2.out'
})
}
item.addEventListener('mouseenter', handleMouseEnter)
item.addEventListener('mouseleave', handleMouseLeave)
})
// Animate buttons with magnetic effect
const buttons = gsap.utils.toArray<HTMLElement>('.btn')
buttons.forEach((button) => {
const handleMouseMove = (e: MouseEvent) => {
const rect = button.getBoundingClientRect()
const x = e.clientX - rect.left - rect.width / 2
const y = e.clientY - rect.top - rect.height / 2
gsap.to(button, {
x: x * 0.1,
y: y * 0.1,
duration: 0.3,
ease: 'power2.out'
})
}
const handleMouseLeave = () => {
gsap.to(button, {
x: 0,
y: 0,
duration: 0.5,
ease: 'elastic.out(1, 0.3)'
})
}
button.addEventListener('mousemove', handleMouseMove)
button.addEventListener('mouseleave', handleMouseLeave)
})
// Parallax effect on hero image
const heroImage = document.querySelector('.hero__media')
if (heroImage) {
gsap.to(heroImage, {
yPercent: -20,
ease: 'none',
scrollTrigger: {
trigger: heroImage,
start: 'top bottom',
end: 'bottom top',
scrub: true
}
})
}
// Smooth reveal for lead text
const leadText = document.querySelector('.lead')
if (leadText) {
gsap.from(leadText, {
y: 30,
opacity: 0,
duration: 0.8,
delay: 1.2,
ease: 'power2.out'
})
}
// Animate eyebrow
const eyebrow = document.querySelector('.eyebrow')
if (eyebrow) {
gsap.from(eyebrow, {
y: 20,
opacity: 0,
duration: 0.6,
delay: 0.3,
ease: 'power2.out'
})
}
// Animate hero buttons
const heroButtons = document.querySelectorAll('.hero .btn')
if (heroButtons.length > 0) {
gsap.from(heroButtons, {
y: 20,
opacity: 0,
duration: 0.6,
stagger: 0.1,
delay: 1.5,
ease: 'power2.out'
})
}
})
return () => ctx.revert()
}, [])
return null
}