208 lines
6.4 KiB
TypeScript
208 lines
6.4 KiB
TypeScript
'use client'
|
|
import { useEffect, useRef } from 'react'
|
|
import { gsap } from 'gsap'
|
|
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
|
import Image from 'next/image'
|
|
|
|
if (typeof window !== 'undefined') {
|
|
gsap.registerPlugin(ScrollTrigger)
|
|
}
|
|
|
|
interface Feature {
|
|
title: string
|
|
description: string
|
|
image: string
|
|
icon: string
|
|
}
|
|
|
|
const features: Feature[] = [
|
|
{
|
|
title: "Close-Up Magic",
|
|
description: "Intimate table-to-table performances that break the ice and create unforgettable moments for your guests.",
|
|
image: "https://images.eventpeppers.com/sites/default/files/imagecache/lightbox-preview/images/13234/michael-peskov-magier-taschendieb-450253.jpeg",
|
|
icon: "✨"
|
|
},
|
|
{
|
|
title: "Stage Shows",
|
|
description: "Grand illusions and interactive performances that captivate entire audiences with wonder and amazement.",
|
|
image: "https://images.eventpeppers.com/sites/default/files/imagecache/lightbox-preview/images/13234/michael-peskov-magier-taschendieb-450255.jpeg",
|
|
icon: "🎭"
|
|
},
|
|
{
|
|
title: "Pickpocket Act",
|
|
description: "Masterful sleight of hand that entertains while teaching guests how to protect themselves from real pickpockets.",
|
|
image: "https://images.eventpeppers.com/sites/default/files/imagecache/lightbox-preview/images/13234/michael-peskov-magier-taschendieb-450254.jpeg",
|
|
icon: "🎩"
|
|
},
|
|
{
|
|
title: "Corporate Events",
|
|
description: "Professional entertainment that elevates business gatherings and creates memorable experiences for clients.",
|
|
image: "https://images.eventpeppers.com/sites/default/files/imagecache/lightbox-preview/images/13234/michael-peskov-magier-taschendieb-450256.jpeg",
|
|
icon: "💼"
|
|
}
|
|
]
|
|
|
|
export default function FeatureCards() {
|
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
const container = containerRef.current
|
|
if (!container) return
|
|
|
|
// Check for reduced motion
|
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
|
|
const ctx = gsap.context(() => {
|
|
const cards = gsap.utils.toArray<HTMLElement>('.feature-card')
|
|
|
|
cards.forEach((card, i) => {
|
|
const image = card.querySelector('.feature-image')
|
|
const content = card.querySelector('.feature-content')
|
|
|
|
if (prefersReducedMotion) {
|
|
// Simple fade for reduced motion
|
|
gsap.from(card, {
|
|
opacity: 0,
|
|
y: 30,
|
|
duration: 0.6,
|
|
delay: i * 0.1,
|
|
scrollTrigger: {
|
|
trigger: card,
|
|
start: 'top 80%',
|
|
toggleActions: 'play none none reverse'
|
|
}
|
|
})
|
|
} else {
|
|
// Full animation with perspective and clip-mask
|
|
gsap.set(card, {
|
|
rotateX: 15,
|
|
rotateY: 5,
|
|
transformPerspective: 1000,
|
|
transformOrigin: 'center center'
|
|
})
|
|
|
|
gsap.set(image, {
|
|
clipPath: 'inset(0 0 100% 0)'
|
|
})
|
|
|
|
gsap.set(content, {
|
|
opacity: 0,
|
|
y: 50
|
|
})
|
|
|
|
const tl = gsap.timeline({
|
|
scrollTrigger: {
|
|
trigger: card,
|
|
start: 'top 75%',
|
|
toggleActions: 'play none none reverse'
|
|
}
|
|
})
|
|
|
|
tl.to(card, {
|
|
rotateX: 0,
|
|
rotateY: 0,
|
|
duration: 0.8,
|
|
ease: 'power2.out'
|
|
})
|
|
.to(image, {
|
|
clipPath: 'inset(0 0 0% 0)',
|
|
duration: 0.6,
|
|
ease: 'power2.out'
|
|
}, '-=0.6')
|
|
.to(content, {
|
|
opacity: 1,
|
|
y: 0,
|
|
duration: 0.6,
|
|
ease: 'power2.out'
|
|
}, '-=0.4')
|
|
|
|
// Hover effects
|
|
const handleMouseEnter = () => {
|
|
gsap.to(card, {
|
|
rotateX: -5,
|
|
rotateY: 5,
|
|
scale: 1.02,
|
|
duration: 0.3,
|
|
ease: 'power2.out'
|
|
})
|
|
|
|
gsap.to(image, {
|
|
scale: 1.1,
|
|
duration: 0.3,
|
|
ease: 'power2.out'
|
|
})
|
|
}
|
|
|
|
const handleMouseLeave = () => {
|
|
gsap.to(card, {
|
|
rotateX: 0,
|
|
rotateY: 0,
|
|
scale: 1,
|
|
duration: 0.3,
|
|
ease: 'power2.out'
|
|
})
|
|
|
|
gsap.to(image, {
|
|
scale: 1,
|
|
duration: 0.3,
|
|
ease: 'power2.out'
|
|
})
|
|
}
|
|
|
|
card.addEventListener('mouseenter', handleMouseEnter)
|
|
card.addEventListener('mouseleave', handleMouseLeave)
|
|
}
|
|
})
|
|
}, container)
|
|
|
|
return () => ctx.revert()
|
|
}, [])
|
|
|
|
return (
|
|
<section ref={containerRef} className="py-32 px-6 bg-slate-900">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="text-center mb-20">
|
|
<h2 className="text-5xl md:text-6xl font-bold text-white mb-6">
|
|
Magical Experiences
|
|
</h2>
|
|
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
|
|
From intimate gatherings to grand celebrations, discover the perfect magical experience for your event.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
{features.map((feature, i) => (
|
|
<div
|
|
key={i}
|
|
className="feature-card group relative bg-slate-800/50 rounded-2xl overflow-hidden backdrop-blur-sm border border-slate-700/50"
|
|
style={{ willChange: 'transform' }}
|
|
>
|
|
<div className="feature-image relative h-64 overflow-hidden">
|
|
<Image
|
|
src={feature.image}
|
|
alt={feature.title}
|
|
fill
|
|
className="object-cover transition-transform duration-300"
|
|
style={{ willChange: 'transform' }}
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-slate-900/80 to-transparent" />
|
|
<div className="absolute top-4 left-4 text-3xl">
|
|
{feature.icon}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="feature-content p-8">
|
|
<h3 className="text-2xl font-bold text-white mb-4">
|
|
{feature.title}
|
|
</h3>
|
|
<p className="text-gray-300 leading-relaxed">
|
|
{feature.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)
|
|
} |