11'use client' ;
22import React , { useRef } from 'react' ;
3- import { motion , useInView } from 'framer-motion' ;
4- import { ChevronLeft , ChevronRight } from 'lucide-react' ;
3+ import { motion , AnimatePresence } from 'framer-motion' ;
4+ import { ChevronLeft , ChevronRight , X } from 'lucide-react' ;
5+ import gsap from 'gsap' ;
6+ import { useGSAP } from '@gsap/react' ;
7+ import { ScrollTrigger } from 'gsap/ScrollTrigger' ;
8+
9+ if ( typeof window !== 'undefined' ) {
10+ gsap . registerPlugin ( ScrollTrigger ) ;
11+ }
512
613interface Speaker {
714 name : string ;
815 title : string ;
916 company : string ;
1017 image : string ;
18+ bio ?: string ;
1119}
1220
1321const speakers : Speaker [ ] = [
1422 {
15- name : 'Speaker Name' ,
16- title : 'Their Info' ,
17- company : 'EXPERT SPEAKER' ,
18- image : '/inctf/assets/images/current_speakers/speaker1.jpeg' ,
23+ name : 'Sreepriya C' ,
24+ title : '101 to Product Security Incident Response (PSIRT)' ,
25+ company : 'Siemens' ,
26+ image : '/inctf/assets/images/current_speakers/Sreepriya_C.jpeg' ,
27+ bio : `Sreepriya Chalakkal is a Product Security Incident Response Team (PSIRT) engineer at Siemens, where she works on vulnerability handling and securing large-scale industrial and enterprise systems. Her expertise spans telecommunication security, protocol analysis, and real-world network infrastructures.\n\nPrior to this, she worked as a security researcher at ERNW GmbH, focusing on mobile and telecom security, including technologies like VoLTE and core network protocols.\n\nShe is a former core member and mentor of team bi0s and a strong advocate for diversity in cybersecurity. She founded Team Shakti and is a key organizer of ShaktiCon, contributing to building a more inclusive global security community.`
1928 } ,
2029 {
21- name : 'Speaker Name' ,
22- title : 'Their Info' ,
23- company : 'EXPERT SPEAKER' ,
24- image : '/inctf/assets/images/current_speakers/speaker2.jpeg' ,
30+ name : 'Abhishek JM' ,
31+ title : 'Lead Security Engineer at CRED' ,
32+ company : 'Cred' ,
33+ image : '/inctf/assets/images/current_speakers/Abhishek_JM.jpeg' ,
34+ bio : `Abhishek JM is a Lead Security Engineer at CRED, specializing in mobile security and application security for large-scale fintech systems. He is also a trainer at 7ASecurity, where he has delivered hands-on training at global conferences including OWASP AppSec New Zealand, 44Con, and ThreatCon.\n\nWith extensive experience in offensive security, Abhishek leads security research projects such as Adhrit and EVABS, focusing on advanced mobile and application security testing. His work has been presented at premier venues like Black Hat (Asia, US, Europe) and OWASP Seasides, with his tool Adhrit gaining industry recognition.\n\nHe is an active contributor to the security community, regularly speaking at conferences and meetups, and has also served as a trainer for international security programs, helping mentor the next generation of cybersecurity professionals.`
2535 } ,
2636] ;
2737
28- const SpeakerCard : React . FC < { speaker : Speaker ; index : number } > = ( { speaker, index } ) => {
29- const ref = useRef < HTMLDivElement > ( null ) ;
30- const isInView = useInView ( ref , { once : true } ) ;
38+ const SpeakerCard : React . FC < { speaker : Speaker ; index : number ; onClick : ( ) => void } > = ( { speaker, index, onClick } ) => {
39+ const isClickable = ! ! speaker . bio ;
3140
3241 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' } }
42+ < div
43+ className = { `speaker-card flex-shrink-0 w-[85vw] sm:w-72 md:w-80 snap-center group opacity-0 ${ isClickable ? 'cursor-pointer' : 'cursor-default' } ` }
44+ onClick = { isClickable ? onClick : undefined }
3945 >
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" >
46+ < div className = { `relative h-full min-h-[380px] md:min-h-[440px] rounded-lg overflow-hidden border border-sky-400/20 bg-slate-900/40 backdrop-blur-sm transition-all duration-300 p-5 md:p-8 flex flex-col items-center text-center ${ isClickable ? 'group-hover:border-sky-400/80 group-hover:shadow-[0_0_25px_rgba(56,189,248,0.15)]' : ''
47+ } `} >
4148
4249 < div className = "absolute top-0 left-0 w-4 h-4 border-t-2 border-l-2 border-sky-400 z-10" />
4350 < div className = "absolute top-0 right-0 w-4 h-4 border-t-2 border-r-2 border-sky-400 z-10" />
@@ -69,20 +76,80 @@ const SpeakerCard: React.FC<{ speaker: Speaker; index: number }> = ({ speaker, i
6976 < p className = "font-mono text-sky-400 text-xs md:text-sm font-semibold tracking-wide mt-auto" >
7077 { speaker . company }
7178 </ p >
79+ { isClickable && (
80+ < div className = "mt-4 opacity-0 group-hover:opacity-100 transition-opacity duration-300" >
81+ < span className = "font-mono text-[9px] text-sky-400 border border-sky-400/30 px-3 py-1 rounded-full uppercase tracking-widest" >
82+ View Intel
83+ </ span >
84+ </ div >
85+ ) }
7286 </ div >
7387
7488 { /* Bottom Corner Accents */ }
7589 < div className = "absolute bottom-0 left-0 w-4 h-4 border-b-2 border-l-2 border-sky-400/50 z-10" />
7690 < div className = "absolute bottom-0 right-0 w-4 h-4 border-b-2 border-r-2 border-sky-400/50 z-10" />
7791 </ div >
78- </ motion . div >
92+ </ div >
7993 ) ;
8094} ;
8195
8296const CurrentSpeakers : React . FC = ( ) => {
97+ const containerRef = useRef < HTMLDivElement > ( null ) ;
8398 const scrollRef = useRef < HTMLDivElement > ( null ) ;
84- const titleRef = useRef < HTMLDivElement > ( null ) ;
85- const isTitleInView = useInView ( titleRef , { once : true } ) ;
99+ const [ selectedSpeaker , setSelectedSpeaker ] = React . useState < Speaker | null > ( null ) ;
100+
101+ useGSAP ( ( ) => {
102+ if ( ! containerRef . current ) return ;
103+
104+ const tl = gsap . timeline ( {
105+ scrollTrigger : {
106+ trigger : containerRef . current ,
107+ start : 'top 85%' ,
108+ toggleActions : 'play none none none'
109+ }
110+ } ) ;
111+
112+ // Animate Title
113+ tl . fromTo ( '.speakers-header' ,
114+ { opacity : 0 , y : 30 } ,
115+ { opacity : 1 , y : 0 , duration : 0.8 , ease : 'power3.out' }
116+ ) ;
117+
118+ // Stagger Cards
119+ tl . fromTo ( '.speaker-card' ,
120+ {
121+ opacity : 0 ,
122+ y : 40 ,
123+ scale : 0.95
124+ } ,
125+ {
126+ opacity : 1 ,
127+ y : 0 ,
128+ scale : 1 ,
129+ duration : 0.8 ,
130+ stagger : 0.1 ,
131+ ease : 'power3.out'
132+ } ,
133+ '-=0.6'
134+ ) ;
135+ } , { scope : containerRef } ) ;
136+
137+ React . useEffect ( ( ) => {
138+ const header = document . querySelector ( 'header' ) ;
139+ if ( selectedSpeaker ) {
140+ document . body . style . overflow = 'hidden' ;
141+ if ( window . innerWidth <= 768 && header ) {
142+ header . style . display = 'none' ;
143+ }
144+ } else {
145+ document . body . style . overflow = 'unset' ;
146+ if ( header ) header . style . display = 'flex' ;
147+ }
148+ return ( ) => {
149+ document . body . style . overflow = 'unset' ;
150+ if ( header ) header . style . display = 'flex' ;
151+ } ;
152+ } , [ selectedSpeaker ] ) ;
86153
87154 const scroll = ( direction : 'left' | 'right' ) => {
88155 if ( scrollRef . current ) {
@@ -95,15 +162,10 @@ const CurrentSpeakers: React.FC = () => {
95162 } ;
96163
97164 return (
98- < section className = "relative z-10 w-full py-16 md:py-24 overflow-hidden" >
165+ < section className = "relative z-10 w-full py-16 md:py-24 overflow-hidden" ref = { containerRef } >
99166 { /* 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- >
167+ < div className = "speakers-header px-6 md:px-16 mb-10 md:mb-16 opacity-0" >
168+ < div className = "flex flex-row items-end justify-between gap-2" >
107169 < div >
108170 < p className = "font-mono text-sky-400 text-xs tracking-[0.3em] uppercase mb-3" >
109171 [ Expert Speakers ]
@@ -128,7 +190,7 @@ const CurrentSpeakers: React.FC = () => {
128190 < ChevronRight size = { 20 } />
129191 </ button >
130192 </ div >
131- </ motion . div >
193+ </ div >
132194
133195 < div className = "mt-6 h-[1px] w-full bg-gradient-to-r from-sky-400/60 via-sky-400/20 to-transparent" />
134196 </ div >
@@ -139,9 +201,85 @@ const CurrentSpeakers: React.FC = () => {
139201 style = { { scrollbarWidth : 'none' , msOverflowStyle : 'none' , WebkitOverflowScrolling : 'touch' } }
140202 >
141203 { speakers . map ( ( speaker , index ) => (
142- < SpeakerCard key = { `${ speaker . name } -${ index } ` } speaker = { speaker } index = { index } />
204+ < SpeakerCard
205+ key = { `${ speaker . name } -${ index } ` }
206+ speaker = { speaker }
207+ index = { index }
208+ onClick = { ( ) => setSelectedSpeaker ( speaker ) }
209+ />
143210 ) ) }
144211 </ div >
212+
213+ { /* Speaker Bio Modal */ }
214+ < AnimatePresence >
215+ { selectedSpeaker && (
216+ < div className = "fixed inset-0 z-[100] flex items-center justify-center p-4 sm:p-6 md:p-10 text-left" >
217+ { /* Backdrop */ }
218+ < motion . div
219+ initial = { { opacity : 0 } }
220+ animate = { { opacity : 1 } }
221+ exit = { { opacity : 0 } }
222+ onClick = { ( ) => setSelectedSpeaker ( null ) }
223+ className = "absolute inset-0 bg-black/90 backdrop-blur-xl"
224+ />
225+
226+ { /* Modal Content */ }
227+ < motion . div
228+ initial = { { opacity : 0 , scale : 0.95 , y : 20 } }
229+ animate = { { opacity : 1 , scale : 1 , y : 0 } }
230+ exit = { { opacity : 0 , scale : 0.95 , y : 20 } }
231+ className = "relative w-full max-w-4xl bg-slate-900 border border-sky-400/30 rounded-lg overflow-y-auto md:overflow-hidden shadow-[0_0_80px_rgba(0,0,0,0.8),0_0_30px_rgba(56,189,248,0.1)] flex flex-col md:flex-row max-h-[90vh] md:max-h-[85vh]"
232+ >
233+ { /* Tactical Accents */ }
234+ < div className = "absolute top-0 left-0 w-8 h-8 border-t-2 border-l-2 border-sky-400 m-4 z-10" />
235+ < div className = "absolute bottom-0 right-0 w-8 h-8 border-b-2 border-r-2 border-sky-400 m-4 z-10" />
236+
237+ < button
238+ onClick = { ( ) => setSelectedSpeaker ( null ) }
239+ className = "absolute top-6 right-6 z-20 p-2 text-sky-400 hover:text-white hover:bg-sky-400/20 transition-all rounded-md"
240+ >
241+ < X size = { 24 } />
242+ </ button >
243+
244+ { /* Left Side: Photo & Quick Info */ }
245+ < div className = "w-full md:w-1/3 bg-slate-950 p-8 flex flex-col items-center justify-center border-b md:border-b-0 md:border-r border-sky-400/20" >
246+ < div className = "relative w-32 h-32 md:w-48 md:h-48 mb-6" >
247+ < div className = "absolute inset-0 rounded-full bg-gradient-to-br from-sky-400 via-purple-500 to-pink-500 p-[3px]" >
248+ < div className = "w-full h-full rounded-full bg-slate-900 p-[2px]" >
249+ < img
250+ src = { selectedSpeaker . image }
251+ alt = { selectedSpeaker . name }
252+ className = "w-full h-full rounded-full object-cover grayscale hover:grayscale-0 transition-all duration-500"
253+ />
254+ </ div >
255+ </ div >
256+ </ div >
257+ < h4 className = "text-white font-heading font-bold text-xl md:text-2xl text-center mb-1 uppercase" >
258+ { selectedSpeaker . name }
259+ </ h4 >
260+ < p className = "text-sky-400 font-mono text-sm uppercase tracking-widest text-center mb-4" >
261+ { selectedSpeaker . company }
262+ </ p >
263+ < div className = "h-px w-24 bg-sky-400/30" />
264+ </ div >
265+
266+ { /* Right Side: Bio & Mission Intel */ }
267+ < div className = "w-full md:w-2/3 p-8 md:p-12 overflow-y-auto" >
268+ < div className = "mb-8" >
269+ < h5 className = "text-white font-heading text-lg md:text-xl font-semibold mb-6 uppercase tracking-wider border-b border-sky-400/10 pb-4" >
270+ { selectedSpeaker . title }
271+ </ h5 >
272+ < div className = "prose prose-invert max-w-none" >
273+ < p className = "text-white/80 font-mono text-sm leading-relaxed whitespace-pre-line mb-4" >
274+ { selectedSpeaker . bio }
275+ </ p >
276+ </ div >
277+ </ div >
278+ </ div >
279+ </ motion . div >
280+ </ div >
281+ ) }
282+ </ AnimatePresence >
145283 </ section >
146284 ) ;
147285} ;
0 commit comments