|
| 1 | +'use client'; |
| 2 | +import React, { useRef } from 'react'; |
| 3 | +import { motion, useInView } from 'framer-motion'; |
| 4 | +import { ChevronLeft, ChevronRight } from 'lucide-react'; |
| 5 | + |
| 6 | +interface Speaker { |
| 7 | + name: string; |
| 8 | + title: string; |
| 9 | + company: string; |
| 10 | + image: string; |
| 11 | +} |
| 12 | + |
| 13 | +const speakers: Speaker[] = [ |
| 14 | + { |
| 15 | + name: 'Speaker Name', |
| 16 | + title: 'Their Info', |
| 17 | + company: 'EXPERT SPEAKER', |
| 18 | + image: '/inctf/assets/images/current_speakers/speaker1.jpeg', |
| 19 | + }, |
| 20 | + { |
| 21 | + name: 'Speaker Name', |
| 22 | + title: 'Their Info', |
| 23 | + company: 'EXPERT SPEAKER', |
| 24 | + image: '/inctf/assets/images/current_speakers/speaker2.jpeg', |
| 25 | + }, |
| 26 | +]; |
| 27 | + |
| 28 | +const SpeakerCard: React.FC<{ speaker: Speaker; index: number }> = ({ speaker, index }) => { |
| 29 | + const ref = useRef<HTMLDivElement>(null); |
| 30 | + const isInView = useInView(ref, { once: true }); |
| 31 | + |
| 32 | + return ( |
| 33 | + <motion.div |
| 34 | + ref={ref} |
| 35 | + className="flex-shrink-0 w-[85vw] sm:w-72 md:w-80 snap-center group" |
| 36 | + initial={{ opacity: 0, y: 30 }} |
| 37 | + animate={isInView ? { opacity: 1, y: 0 } : {}} |
| 38 | + transition={{ duration: 0.5, delay: index * 0.1, ease: 'easeOut' }} |
| 39 | + > |
| 40 | + <div className="relative h-full min-h-[380px] md:min-h-[440px] rounded-lg overflow-hidden border border-sky-400/20 bg-slate-900/60 backdrop-blur-sm transition-all duration-300 group-hover:border-sky-400/80 group-hover:shadow-[0_0_25px_rgba(56,189,248,0.2)] p-5 md:p-8 flex flex-col items-center text-center"> |
| 41 | + |
| 42 | + <div className="absolute top-0 left-0 w-4 h-4 border-t-2 border-l-2 border-sky-400 z-10" /> |
| 43 | + <div className="absolute top-0 right-0 w-4 h-4 border-t-2 border-r-2 border-sky-400 z-10" /> |
| 44 | + |
| 45 | + {/* Circular Photo */} |
| 46 | + <div className="flex justify-center mb-5 md:mb-6 flex-shrink-0"> |
| 47 | + <div className="relative w-32 h-32 md:w-44 md:h-44"> |
| 48 | + {/* Outer gradient ring */} |
| 49 | + <div className="absolute inset-0 rounded-full bg-gradient-to-br from-sky-400 via-purple-500 to-pink-500 p-[3px] group-hover:p-[4px] transition-all duration-300"> |
| 50 | + <div className="w-full h-full rounded-full bg-slate-900 p-[2px]"> |
| 51 | + <img |
| 52 | + src={speaker.image} |
| 53 | + alt={speaker.name} |
| 54 | + className="w-full h-full rounded-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500" |
| 55 | + /> |
| 56 | + </div> |
| 57 | + </div> |
| 58 | + </div> |
| 59 | + </div> |
| 60 | + |
| 61 | + {/* Info */} |
| 62 | + <div className="flex-grow flex flex-col justify-center w-full"> |
| 63 | + <h3 className="font-heading font-bold text-white text-lg md:text-xl leading-tight mb-2 md:mb-3 uppercase"> |
| 64 | + {speaker.name} |
| 65 | + </h3> |
| 66 | + <p className="font-mono text-white/80 text-[11px] md:text-xs mb-2 md:mb-3 uppercase tracking-wider leading-relaxed line-clamp-3 min-h-[2.5rem]"> |
| 67 | + {speaker.title} |
| 68 | + </p> |
| 69 | + <p className="font-mono text-sky-400 text-xs md:text-sm font-semibold tracking-wide mt-auto"> |
| 70 | + {speaker.company} |
| 71 | + </p> |
| 72 | + </div> |
| 73 | + |
| 74 | + {/* Bottom Corner Accents */} |
| 75 | + <div className="absolute bottom-0 left-0 w-4 h-4 border-b-2 border-l-2 border-sky-400/50 z-10" /> |
| 76 | + <div className="absolute bottom-0 right-0 w-4 h-4 border-b-2 border-r-2 border-sky-400/50 z-10" /> |
| 77 | + </div> |
| 78 | + </motion.div> |
| 79 | + ); |
| 80 | +}; |
| 81 | + |
| 82 | +const CurrentSpeakers: React.FC = () => { |
| 83 | + const scrollRef = useRef<HTMLDivElement>(null); |
| 84 | + const titleRef = useRef<HTMLDivElement>(null); |
| 85 | + const isTitleInView = useInView(titleRef, { once: true }); |
| 86 | + |
| 87 | + const scroll = (direction: 'left' | 'right') => { |
| 88 | + if (scrollRef.current) { |
| 89 | + const amount = 320; |
| 90 | + scrollRef.current.scrollBy({ |
| 91 | + left: direction === 'right' ? amount : -amount, |
| 92 | + behavior: 'smooth', |
| 93 | + }); |
| 94 | + } |
| 95 | + }; |
| 96 | + |
| 97 | + return ( |
| 98 | + <section className="relative z-10 w-full py-16 md:py-24 overflow-hidden"> |
| 99 | + {/* Section Header */} |
| 100 | + <div ref={titleRef} className="px-6 md:px-16 mb-10 md:mb-16"> |
| 101 | + <motion.div |
| 102 | + initial={{ opacity: 0, y: 20 }} |
| 103 | + animate={isTitleInView ? { opacity: 1, y: 0 } : {}} |
| 104 | + transition={{ duration: 0.5 }} |
| 105 | + className="flex flex-row items-end justify-between gap-2" |
| 106 | + > |
| 107 | + <div> |
| 108 | + <p className="font-mono text-sky-400 text-xs tracking-[0.3em] uppercase mb-3"> |
| 109 | + [ Expert Speakers ] |
| 110 | + </p> |
| 111 | + <h2 className="font-heading text-white text-3xl md:text-4xl lg:text-5xl font-bold uppercase tracking-wider"> |
| 112 | + Current <span className="text-sky-400">Speakers</span> |
| 113 | + </h2> |
| 114 | + </div> |
| 115 | + <div className="flex items-center gap-3 flex-shrink-0 mb-1"> |
| 116 | + <button |
| 117 | + onClick={() => scroll('left')} |
| 118 | + className="group flex items-center justify-center p-2 border border-sky-400/30 text-sky-400 hover:bg-sky-400/10 hover:border-sky-400 transition-all duration-200 rounded-sm" |
| 119 | + aria-label="Scroll left" |
| 120 | + > |
| 121 | + <ChevronLeft size={20} /> |
| 122 | + </button> |
| 123 | + <button |
| 124 | + onClick={() => scroll('right')} |
| 125 | + className="group flex items-center justify-center p-2 border border-sky-400/30 text-sky-400 hover:bg-sky-400/10 hover:border-sky-400 transition-all duration-200 rounded-sm" |
| 126 | + aria-label="Scroll right" |
| 127 | + > |
| 128 | + <ChevronRight size={20} /> |
| 129 | + </button> |
| 130 | + </div> |
| 131 | + </motion.div> |
| 132 | + |
| 133 | + <div className="mt-6 h-[1px] w-full bg-gradient-to-r from-sky-400/60 via-sky-400/20 to-transparent" /> |
| 134 | + </div> |
| 135 | + |
| 136 | + <div |
| 137 | + ref={scrollRef} |
| 138 | + className="flex gap-6 md:gap-8 px-8 md:px-16 overflow-x-auto snap-x snap-mandatory scrollbar-hide pb-8 pt-4 md:justify-center scroll-smooth" |
| 139 | + style={{ scrollbarWidth: 'none', msOverflowStyle: 'none', WebkitOverflowScrolling: 'touch' }} |
| 140 | + > |
| 141 | + {speakers.map((speaker, index) => ( |
| 142 | + <SpeakerCard key={`${speaker.name}-${index}`} speaker={speaker} index={index} /> |
| 143 | + ))} |
| 144 | + </div> |
| 145 | + </section> |
| 146 | + ); |
| 147 | +}; |
| 148 | + |
| 149 | +export default CurrentSpeakers; |
0 commit comments