284 lines
14 KiB
TypeScript
284 lines
14 KiB
TypeScript
'use client';import React, { useEffect, useRef, useState } from 'react'
|
|
|
|
|
|
|
|
import { useState } from 'react';interface Testimonial { text: string; author: string }
|
|
|
|
|
|
|
|
const testimonials = [const testimonials: Testimonial[] = [
|
|
|
|
{ { text: 'Esta probablemente sea la plataforma de transmisión más fácil de usar que conozco...', author: 'Bomeca Trotter' },
|
|
|
|
id: 1, { text: 'Uso AvanzaCast desde hace mucho tiempo y sigo eligiéndolo...', author: 'Krissy Buck' },
|
|
|
|
name: 'María González', { text: 'Hace dos años que uso este sistema y me encanta!', author: 'Joy Ann Lajeret' },
|
|
|
|
role: 'Content Creator', { text: 'La integración con múltiples plataformas es perfecta...', author: 'Carlos Mendoza' },
|
|
|
|
company: '@MariaStreams', { text: 'Como creadora de contenido, necesitaba una herramienta confiable...', author: 'María González' }
|
|
|
|
avatar: '👩💼',]
|
|
|
|
rating: 5,
|
|
|
|
comment:export default function TestimonialsSection() {
|
|
|
|
'AvanzaCast transformó completamente mi forma de hacer streaming. Antes necesitaba software complicado y equipos caros. Ahora transmito en 4K a YouTube, Twitch y Facebook simultáneamente desde mi navegador.', const scrollRef = useRef<HTMLDivElement | null>(null)
|
|
|
|
stats: { streams: 250, viewers: '50K+' }, const [isAutoPlay, setIsAutoPlay] = useState(true)
|
|
|
|
videoThumb: '🎬', const multiplier = 12
|
|
|
|
}, const duplicatedTestimonials = Array.from({ length: multiplier }, () => testimonials).flat()
|
|
|
|
{
|
|
|
|
id: 2, const scrollLeft = () => { if (scrollRef.current) scrollRef.current.scrollBy({ left: -400, behavior: 'smooth' }) }
|
|
|
|
name: 'Carlos Ramírez', const scrollRight = () => { if (scrollRef.current) scrollRef.current.scrollBy({ left: 400, behavior: 'smooth' }) }
|
|
|
|
role: 'CEO',
|
|
|
|
company: 'TechStartup Inc.', useEffect(() => {
|
|
|
|
avatar: '👨💻', if (scrollRef.current) {
|
|
|
|
rating: 5, const singleSetWidth = testimonials.length * 400
|
|
|
|
comment: const middleStart = singleSetWidth * Math.floor(multiplier / 2)
|
|
|
|
'Para nuestros webinars corporativos, AvanzaCast es indispensable. La calidad de video es impecable, podemos invitar hasta 10 participantes remotos y el chat unificado nos permite interactuar con audiencias de múltiples plataformas.', scrollRef.current.scrollLeft = middleStart
|
|
|
|
stats: { streams: 120, viewers: '100K+' }, }
|
|
|
|
videoThumb: '📹', }, [])
|
|
|
|
},
|
|
|
|
{ useEffect(() => {
|
|
|
|
id: 3, if (!isAutoPlay || !scrollRef.current) return
|
|
|
|
name: 'Ana Martínez', const interval = setInterval(() => scrollRight(), 4000)
|
|
|
|
role: 'Educadora Online', return () => clearInterval(interval)
|
|
|
|
company: 'Academia Digital', }, [isAutoPlay])
|
|
|
|
avatar: '👩🏫',
|
|
|
|
rating: 5, useEffect(() => {
|
|
|
|
comment: let scrollTimeout: any
|
|
|
|
'Mis clases en vivo han mejorado increíblemente. Los estudiantes pueden unirse desde cualquier plataforma y la grabación automática en la nube me permite crear contenido on-demand sin esfuerzo adicional.', const container = scrollRef.current
|
|
|
|
stats: { streams: 300, viewers: '25K+' }, const handleScroll = () => {
|
|
|
|
videoThumb: '🎓', if (!container) return
|
|
|
|
}, const singleSetWidth = testimonials.length * 400
|
|
|
|
]; const totalWidth = singleSetWidth * multiplier
|
|
|
|
const middleStart = singleSetWidth * Math.floor(multiplier / 2)
|
|
|
|
export default function TestimonialsSection() { const tolerance = 100
|
|
|
|
const [activeTestimonial, setActiveTestimonial] = useState(0); requestAnimationFrame(() => {
|
|
|
|
if (container.scrollLeft <= tolerance) container.scrollLeft = middleStart
|
|
|
|
return ( else if (container.scrollLeft >= totalWidth - container.clientWidth - tolerance) container.scrollLeft = middleStart
|
|
|
|
<section className="py-20 bg-white dark:bg-gray-900"> })
|
|
|
|
<div className="container mx-auto px-4"> }
|
|
|
|
<div className="text-center mb-16">
|
|
|
|
<span className="inline-flex items-center gap-2 text-purple-600 dark:text-purple-400 font-semibold mb-4"> if (container) {
|
|
|
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> const debounced = () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(handleScroll, 150) }
|
|
|
|
<path d="M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.56 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z" /> container.addEventListener('scroll', debounced, { passive: true })
|
|
|
|
</svg> return () => { container.removeEventListener('scroll', debounced as any); clearTimeout(scrollTimeout) }
|
|
|
|
Lo que Dicen Nuestros Usuarios }
|
|
|
|
</span> }, [])
|
|
|
|
<h2 className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-4">
|
|
|
|
Historias de Éxito Reales return (
|
|
|
|
</h2> <section className="bg-white py-20">
|
|
|
|
<p className="text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
|
|
|
Miles de creadores, empresas y educadores confían en AvanzaCast para sus transmisiones en vivo <h2 className="text-3xl lg:text-5xl font-black text-gray-900 mb-16">
|
|
|
|
</p> Ya se crearon más de 60 millones de transmisiones y grabaciones en AvanzaCast
|
|
|
|
</div> </h2>
|
|
|
|
|
|
|
|
{/* Main Testimonial Showcase */} <div className="relative w-full">
|
|
|
|
<div className="max-w-6xl mx-auto mb-12"> <button onClick={scrollLeft} className="absolute left-4 top-1/2 -translate-y-1/2 z-10 w-12 h-12 rounded-full bg-white shadow-lg border border-gray-200 flex items-center justify-center hover:bg-gray-50 cursor-pointer transition-all">
|
|
|
|
<div className="bg-gradient-to-br from-purple-50 to-blue-50 dark:from-purple-900/20 dark:to-blue-900/20 rounded-3xl p-8 md:p-12 shadow-2xl border border-purple-200 dark:border-purple-800"> <svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7"/></svg>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center"> </button>
|
|
|
|
{/* Video Demo Preview */}
|
|
|
|
<div className="relative"> <button onClick={scrollRight} className="absolute right-4 top-1/2 -translate-y-1/2 z-10 w-12 h-12 rounded-full bg-white shadow-lg border border-gray-200 flex items-center justify-center hover:bg-gray-50 cursor-pointer transition-all">
|
|
|
|
<div className="aspect-video bg-gradient-to-br from-purple-600 to-blue-600 rounded-2xl overflow-hidden shadow-xl"> <svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7"/></svg>
|
|
|
|
<div className="absolute inset-0 flex items-center justify-center"> </button>
|
|
|
|
<div className="text-center">
|
|
|
|
<div className="text-8xl mb-4 animate-float"> <div ref={scrollRef} className="flex space-x-8 overflow-x-auto scrollbar-hide carousel-smooth pb-4 px-16" onMouseEnter={() => setIsAutoPlay(false)} onMouseLeave={() => setIsAutoPlay(true)}>
|
|
|
|
{testimonials[activeTestimonial].videoThumb} {duplicatedTestimonials.map((testimonial, index) => {
|
|
|
|
</div> const setNumber = Math.floor(index / testimonials.length)
|
|
|
|
<button className="bg-white/20 backdrop-blur-lg hover:bg-white/30 text-white px-6 py-3 rounded-full font-semibold flex items-center gap-2 mx-auto transition-all duration-300 hover:scale-105"> const itemIndex = index % testimonials.length
|
|
|
|
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"> return (
|
|
|
|
<path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" /> <div key={`testimonial-set-${setNumber}-item-${itemIndex}-${testimonial.author}`} className="flex-shrink-0 w-80">
|
|
|
|
</svg> <div className="bg-gray-50 p-8 rounded-2xl h-full">
|
|
|
|
Ver Demo <p className="text-gray-700 italic mb-6 leading-relaxed text-sm">“{testimonial.text}”</p>
|
|
|
|
</button> <p className="font-semibold text-gray-900">{testimonial.author}</p>
|
|
|
|
</div> </div>
|
|
|
|
</div> </div>
|
|
|
|
</div> )
|
|
|
|
{/* Stats Badge */} })}
|
|
|
|
<div className="absolute -bottom-4 left-1/2 transform -translate-x-1/2 bg-white dark:bg-gray-800 px-6 py-3 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 flex gap-6"> </div>
|
|
|
|
<div className="text-center"> </div>
|
|
|
|
<p className="text-2xl font-bold text-purple-600">
|
|
|
|
{testimonials[activeTestimonial].stats.streams} <div className="flex justify-center space-x-4 mt-8">
|
|
|
|
</p> <button onClick={() => setIsAutoPlay(!isAutoPlay)} className="text-sm text-gray-500 hover:text-gray-700 flex items-center space-x-1 transition-colors">
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400">Streams</p> {isAutoPlay ? (
|
|
|
|
</div> <>
|
|
|
|
<div className="text-center"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 9v6m4-6v6"/></svg>
|
|
|
|
<p className="text-2xl font-bold text-blue-600"> <span>Pausar auto-scroll</span>
|
|
|
|
{testimonials[activeTestimonial].stats.viewers} </>
|
|
|
|
</p> ) : (
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400">Viewers</p> <>
|
|
|
|
</div> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M15 14h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
|
|
</div> <span>Reanudar auto-scroll</span>
|
|
|
|
</div> </>
|
|
|
|
)}
|
|
|
|
{/* Testimonial Content */} </button>
|
|
|
|
<div> </div>
|
|
|
|
<div className="flex gap-1 mb-4"> </div>
|
|
|
|
{[...Array(testimonials[activeTestimonial].rating)].map((_, i) => ( </section>
|
|
|
|
<svg )
|
|
|
|
key={i}}
|
|
|
|
className="w-6 h-6 text-yellow-400"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
|
</svg>
|
|
))}
|
|
</div>
|
|
<p className="text-xl text-gray-700 dark:text-gray-300 mb-6 italic">
|
|
"{testimonials[activeTestimonial].comment}"
|
|
</p>
|
|
<div className="flex items-center gap-4">
|
|
<div className="text-5xl">{testimonials[activeTestimonial].avatar}</div>
|
|
<div>
|
|
<h4 className="text-lg font-bold text-gray-900 dark:text-white">
|
|
{testimonials[activeTestimonial].name}
|
|
</h4>
|
|
<p className="text-gray-600 dark:text-gray-400">
|
|
{testimonials[activeTestimonial].role}
|
|
</p>
|
|
<p className="text-purple-600 dark:text-purple-400 text-sm">
|
|
{testimonials[activeTestimonial].company}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Testimonial Selector */}
|
|
<div className="flex justify-center gap-4">
|
|
{testimonials.map((testimonial, index) => (
|
|
<button
|
|
key={testimonial.id}
|
|
onClick={() => setActiveTestimonial(index)}
|
|
className={`px-6 py-3 rounded-xl font-semibold transition-all duration-300 ${
|
|
activeTestimonial === index
|
|
? 'bg-gradient-to-r from-purple-600 to-blue-600 text-white shadow-lg scale-105'
|
|
: 'bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-700'
|
|
}`}
|
|
>
|
|
{testimonial.avatar} {testimonial.name.split(' ')[0]}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Trust Indicators */}
|
|
<div className="mt-16 grid grid-cols-2 md:grid-cols-4 gap-8 text-center">
|
|
{[
|
|
{ value: '50K+', label: 'Usuarios Activos' },
|
|
{ value: '2M+', label: 'Horas Transmitidas' },
|
|
{ value: '4.9/5', label: 'Rating Promedio' },
|
|
{ value: '99.9%', label: 'Uptime Garantizado' },
|
|
].map((stat, i) => (
|
|
<div key={i}>
|
|
<p className="text-3xl md:text-4xl font-bold text-purple-600 dark:text-purple-400 mb-2">
|
|
{stat.value}
|
|
</p>
|
|
<p className="text-gray-600 dark:text-gray-400">{stat.label}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|