'use client' import { useEffect, useRef } from 'react' import { gsap } from 'gsap' import { ScrollTrigger } from 'gsap/ScrollTrigger' if (typeof window !== 'undefined') { gsap.registerPlugin(ScrollTrigger) } const stats = [ { number: 5, suffix: '/5', label: 'Star Rating' }, { number: 100, suffix: '%', label: 'Recommendation' }, { number: 20, suffix: '+', label: 'Bookings' }, { number: 1000, suffix: '+', label: 'KM Travel Radius' } ] const logos = [ 'SAT.1', 'WDR', 'ZDF', 'Amazon Prime Video', 'Mercedes-Benz AG', 'Materna TMT', 'IHK', 'Lexus' ] export default function SocialProof() { const containerRef = useRef(null) const statsRef = useRef(null) const logosRef = useRef(null) useEffect(() => { const container = containerRef.current const statsContainer = statsRef.current const logosContainer = logosRef.current if (!container || !statsContainer || !logosContainer) return // Check for reduced motion const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches const ctx = gsap.context(() => { // Stats animation const statElements = gsap.utils.toArray('.stat-item') statElements.forEach((stat, i) => { const numberEl = stat.querySelector('.stat-number') as HTMLElement const labelEl = stat.querySelector('.stat-label') as HTMLElement if (prefersReducedMotion) { // Simple fade for reduced motion gsap.from(stat, { opacity: 0, y: 20, duration: 0.6, delay: i * 0.1, scrollTrigger: { trigger: statsContainer, start: 'top 80%', toggleActions: 'play none none reverse' } }) } else { // Count-up animation const targetNumber = parseInt(numberEl.textContent || '0') const suffix = numberEl.dataset.suffix || '' gsap.set(stat, { opacity: 0, y: 30 }) const tl = gsap.timeline({ scrollTrigger: { trigger: statsContainer, start: 'top 70%', toggleActions: 'play none none reverse' } }) tl.to(stat, { opacity: 1, y: 0, duration: 0.6, delay: i * 0.1, ease: 'power2.out' }) .to({ value: 0 }, { value: targetNumber, duration: 1.5, ease: 'power2.out', onUpdate: function() { const currentValue = Math.round(this.targets()[0].value) numberEl.textContent = currentValue + suffix } }, '-=0.3') } }) // Logos animation const logoElements = gsap.utils.toArray('.logo-item') if (prefersReducedMotion) { gsap.from(logoElements, { opacity: 0, duration: 0.8, stagger: 0.1, scrollTrigger: { trigger: logosContainer, start: 'top 80%', toggleActions: 'play none none reverse' } }) } else { gsap.from(logoElements, { opacity: 0, x: -30, duration: 0.6, stagger: 0.1, ease: 'power2.out', scrollTrigger: { trigger: logosContainer, start: 'top 80%', toggleActions: 'play none none reverse' } }) // Continuous marquee effect const marqueeWidth = logosContainer.scrollWidth const containerWidth = logosContainer.offsetWidth if (marqueeWidth > containerWidth) { gsap.to('.logos-track', { x: -(marqueeWidth - containerWidth), duration: 20, ease: 'none', repeat: -1, yoyo: true }) } } }, container) return () => ctx.revert() }, []) return (
{/* Stats */}

Trusted by Many

Numbers that speak for themselves

{stats.map((stat, i) => (
0{stat.suffix}
{stat.label}
))}
{/* Logos */}

As Seen On & Trusted By

{logos.map((logo, i) => (
{logo}
))}
) }