1
- /* eslint-disable @next/next/no-img-element */
2
- "use client" ;
1
+ "use client"
3
2
4
- import { useEffect , useRef , useState } from "react" ;
5
- import { gsap } from "gsap" ;
6
- import { ScrollTrigger } from "gsap/ScrollTrigger" ;
7
- import Lenis from "@studio-freight/lenis" ;
8
- import { useGSAP } from "@gsap/react" ;
9
- import { tedxsjecAssetsPrefix } from "@/lib/utils" ;
10
-
11
- gsap . registerPlugin ( ScrollTrigger ) ;
3
+ import { useEffect , useRef , useState } from "react"
4
+ import { gsap } from "gsap"
5
+ import { ScrollTrigger } from "gsap/ScrollTrigger"
6
+ import Lenis from "@studio-freight/lenis"
7
+ import { useGSAP } from "@gsap/react"
8
+ import { tedxsjecAssetsPrefix } from "@/lib/utils"
9
+ import { Dialog , DialogContent } from "@/components/ui/dialog"
10
+ gsap . registerPlugin ( ScrollTrigger )
12
11
13
12
interface PerformerSection {
14
- images : string [ ] ;
15
- name : String ;
16
- profession : string ;
17
- description : string ;
13
+ images : string [ ]
14
+ name : string
15
+ profession : string
16
+ description : string
18
17
}
19
18
20
19
const performerSections : PerformerSection [ ] = [
21
20
{
22
21
name : "Yukthi Udupa" ,
23
22
profession : "Bharatanatyam artist" ,
24
23
description :
25
- "Yukthi Udupa, a passionate Bharatanatyam artist, began her journey at 12 under Guru Vid Smt. Pravitha Ashok at Nritya Vasantha Natyalaya® Kundapura. She completed her “Vidwath” exams with distinction and earned the Karnataka State Music and Dance Scholarship. Yukthi has won numerous awards, including “Natya Sammohini,” “Yuva Kala Prashasti,” and the “Kalashree Award,” excelling in international, national, and state-level competitions. Her Bharatanatyam Arangetram was a celebrated display of her technical skill and expressive artistry. Yukthi is also a ‘B’ grade Doordarshan artist, inspiring young dancers and honoring Bharatanatyam's legacy." ,
24
+ "Yukthi Udupa, a passionate Bharatanatyam artist, began her journey at 12 under Guru Vid Smt. Pravitha Ashok at Nritya Vasantha Natyalaya® Kundapura. She completed her exams with distinction and earned the Karnataka State Music and Dance Scholarship. Yukthi has won numerous awards, including and the excelling in international, national, and state-level competitions. Her Bharatanatyam Arangetram was a celebrated display of her technical skill and expressive artistry. Yukthi is also a 'B' grade Doordarshan artist, inspiring young dancers and honoring Bharatanatyam's legacy." ,
26
25
images : [
27
26
`${ tedxsjecAssetsPrefix } /performers/Yukthi1.avif` ,
28
- // `${tedxsjecAssetsPrefix}/performers/Yukthi1.avif`,
29
- // `${tedxsjecAssetsPrefix}/performers/Yukthi1.avif`,
30
- // `${tedxsjecAssetsPrefix}/performers/Yukthi2.avif`,
31
27
] ,
32
28
} ,
33
- // {
34
- // name: "Agasthyam Kalaripayattu",
35
- // profession: "Martial Arts Institution",
36
- // description:
37
- // "Agasthyam Kalaripayattu, a premier martial arts institution, preserves and teaches the ancient art of Kalaripayattu from Kerala, India. Founded and led by Gurukkal S Mahesh, Agasthyam continues a legacy over 129 years old, deeply rooted in traditional combat techniques, self-defense, weaponry, and spiritual growth. This renowned school offers rigorous training that builds agility, strength, and resilience, merging physical discipline with profound cultural heritage. Agasthyam is a respected destination for those who seek not only to master Kalaripayattu but also to forge a deeper, spiritual connection to this timeless art form.",
38
- // images: [
39
- // `${tedxsjecAssetsPrefix}/performers/Agasthyam1.avif`,
40
- // // `${tedxsjecAssetsPrefix}/performers/Agasthyam2.avif`,
41
- // // `${tedxsjecAssetsPrefix}/performers/Agasthyam3.avif`,
42
- // ],
43
- // },
44
- ] ;
45
-
46
- export default function Performers ( ) {
47
- const containerRef = useRef < HTMLDivElement > ( null ) ;
48
- const [ currentImageIndices , setCurrentImageIndices ] = useState < number [ ] > (
49
- performerSections . map ( ( ) => 0 )
50
- ) ;
51
- const intervalRefs = useRef < ( NodeJS . Timeout | null ) [ ] > ( [ ] ) ;
29
+ ]
30
+
31
+ export default function Component ( ) {
32
+ const containerRef = useRef < HTMLDivElement > ( null )
33
+ const [ currentImageIndices , setCurrentImageIndices ] = useState < number [ ] > ( performerSections . map ( ( ) => 0 ) )
34
+ const intervalRefs = useRef < ( NodeJS . Timeout | null ) [ ] > ( [ ] )
35
+ const [ selectedSection , setSelectedSection ] = useState < PerformerSection | null > ( null )
36
+ const [ isLargeScreen , setIsLargeScreen ] = useState ( false )
52
37
53
38
useGSAP ( ( ) => {
54
- const lenis = new Lenis ( { lerp : 0.07 } ) ;
39
+ const lenis = new Lenis ( { lerp : 0.07 } )
55
40
56
- lenis . on ( "scroll" , ScrollTrigger . update ) ;
41
+ lenis . on ( "scroll" , ScrollTrigger . update )
57
42
58
43
gsap . ticker . add ( ( time ) => {
59
- lenis . raf ( time * 1000 ) ;
60
- } ) ;
44
+ lenis . raf ( time * 1000 )
45
+ } )
61
46
62
47
gsap . utils
63
48
. toArray < HTMLDivElement > ( ".img-container" )
64
49
. forEach ( ( container ) => {
65
- const img = container . querySelector ( "img" ) ;
50
+ const img = container . querySelector ( "img" )
66
51
67
52
if ( img ) {
68
53
gsap . fromTo (
@@ -78,96 +63,139 @@ export default function Performers() {
78
63
end : "bottom top" ,
79
64
} ,
80
65
}
81
- ) ;
66
+ )
82
67
}
83
- } ) ;
84
-
85
- // Set up hover animations for description
86
- gsap . utils
87
- . toArray < HTMLDivElement > ( ".performer-section" )
88
- . forEach ( ( section ) => {
89
- const description = section . querySelector ( ".description" ) ;
90
- const tl = gsap . timeline ( { paused : true } ) ;
91
-
92
- tl . fromTo (
93
- description ,
94
- { yPercent : 100 , opacity : 0 } ,
95
- { yPercent : 0 , opacity : 1 , duration : 0.3 , ease : "power2.out" }
96
- ) ;
97
-
98
- section . addEventListener ( "mouseenter" , ( ) => tl . play ( ) ) ;
99
- section . addEventListener ( "mouseleave" , ( ) => tl . reverse ( ) ) ;
100
- } ) ;
68
+ } )
69
+
70
+ // Set up hover animations for description on desktop
71
+ const mm = gsap . matchMedia ( )
72
+
73
+ mm . add ( "(min-width: 1200px)" , ( ) => {
74
+ setIsLargeScreen ( true )
75
+ gsap . utils
76
+ . toArray < HTMLDivElement > ( ".performer-section" )
77
+ . forEach ( ( section ) => {
78
+ const description = section . querySelector ( ".description" )
79
+ const tl = gsap . timeline ( { paused : true } )
80
+
81
+ tl . fromTo (
82
+ description ,
83
+ { yPercent : 100 , opacity : 0 } ,
84
+ { yPercent : 0 , opacity : 1 , duration : 0.3 , ease : "power2.out" }
85
+ )
86
+
87
+ section . addEventListener ( "mouseenter" , ( ) => tl . play ( ) )
88
+ section . addEventListener ( "mouseleave" , ( ) => tl . reverse ( ) )
89
+ } )
90
+ } )
91
+
92
+ mm . add ( "(max-width: 1200px)" , ( ) => {
93
+ setIsLargeScreen ( false )
94
+ } )
101
95
102
96
return ( ) => {
103
- lenis . destroy ( ) ;
104
- ScrollTrigger . getAll ( ) . forEach ( ( st ) => st . kill ( ) ) ;
105
- } ;
106
- } , [ ] ) ;
97
+ lenis . destroy ( )
98
+ ScrollTrigger . getAll ( ) . forEach ( ( st ) => st . kill ( ) )
99
+ mm . revert ( )
100
+ }
101
+ } , [ ] )
107
102
108
103
useEffect ( ( ) => {
109
104
performerSections . forEach ( ( _ , index ) => {
110
105
intervalRefs . current [ index ] = setInterval ( ( ) => {
111
106
setCurrentImageIndices ( ( prevIndices ) => {
112
- const newIndices = [ ...prevIndices ] ;
107
+ const newIndices = [ ...prevIndices ]
113
108
newIndices [ index ] =
114
- ( newIndices [ index ] + 1 ) % performerSections [ index ] . images . length ;
115
- return newIndices ;
116
- } ) ;
117
- } , 2500 + index * 1000 ) ; // Stagger the intervals to make the changes less synchronized
118
- } ) ;
109
+ ( newIndices [ index ] + 1 ) % performerSections [ index ] . images . length
110
+ return newIndices
111
+ } )
112
+ } , 2500 + index * 1000 )
113
+ } )
119
114
120
115
return ( ) => {
121
- // eslint-disable-next-line react-hooks/exhaustive-deps
122
116
intervalRefs . current . forEach ( ( interval ) => {
123
- if ( interval ) clearInterval ( interval ) ;
124
- } ) ;
125
- } ;
126
- } , [ ] ) ;
117
+ if ( interval ) clearInterval ( interval )
118
+ } )
119
+ }
120
+ } , [ ] )
121
+
122
+ const handleSectionClick = ( section : PerformerSection ) => {
123
+ if ( ! isLargeScreen ) {
124
+ setSelectedSection ( section )
125
+ }
126
+ }
127
+
128
+ // Effect to disable body scroll when dialog is open
129
+ useEffect ( ( ) => {
130
+ if ( selectedSection ) {
131
+ document . body . classList . add ( "no-scroll" )
132
+ } else {
133
+ document . body . classList . remove ( "no-scroll" )
134
+ }
135
+
136
+ return ( ) => document . body . classList . remove ( "no-scroll" )
137
+ } , [ selectedSection ] )
127
138
128
139
return (
129
- < div ref = { containerRef } className = "overflow-hidden" >
130
- { performerSections . map ( ( section , sectionIndex ) => (
131
- < section
132
- key = { sectionIndex }
133
- className = "flex md:max-w-[1200px] items-center justify-center relative mx-auto px-4 my-16 first:mt-0 last:mb-0"
134
- aria-labelledby = { `section-title-${ sectionIndex } ` }
135
- >
136
- < div className = "relative w-full aspect-[16/9] overflow-hidden img-container performer-section" >
137
- { section . images . map ( ( image , imageIndex ) => (
140
+ < Dialog open = { ! ! selectedSection } onOpenChange = { ( open ) => ! open && setSelectedSection ( null ) } >
141
+ < div ref = { containerRef } className = "overflow-hidden " >
142
+ { performerSections . map ( ( section , sectionIndex ) => (
143
+ < section
144
+ key = { sectionIndex }
145
+ className = "flex md:max-w-[1200px] items-center justify-center relative mx-auto px-4 my-24 first:mt-0 last:mb-0"
146
+ aria-labelledby = { `section-title-${ sectionIndex } ` }
147
+ >
148
+ < div
149
+ className = { `relative w-full aspect-[16/9] overflow-hidden img-container performer-section ${ ! isLargeScreen ? 'cursor-pointer' : '' } ` }
150
+ onClick = { ( ) => handleSectionClick ( section ) }
151
+ >
152
+ { section . images . map ( ( image , imageIndex ) => (
153
+ < img
154
+ key = { imageIndex }
155
+ src = { image }
156
+ alt = { `Performer section ${ sectionIndex + 1 } , slide ${ imageIndex + 1 } of ${ section . images . length } ` }
157
+ className = { `absolute inset-0 w-full h-full object-cover transition-opacity duration-1000 ${ imageIndex === currentImageIndices [ sectionIndex ] ? "opacity-100" : "opacity-0" } ` }
158
+ aria-hidden = { imageIndex !== currentImageIndices [ sectionIndex ] }
159
+ />
160
+ ) ) }
161
+ < div className = "absolute inset-0 bg-black bg-opacity-40 flex flex-col justify-end p-8" >
162
+ < h2
163
+ id = { `section-title-${ sectionIndex } ` }
164
+ className = "text-3xl md:text-5xl lg:text-6xl font-bold text-white mb-2"
165
+ >
166
+ { section . name }
167
+ </ h2 >
168
+ < p className = "text-xl md:text-2xl text-white italic" >
169
+ { section . profession }
170
+ </ p >
171
+ </ div >
172
+ < div className = "description absolute inset-0 bg-black bg-opacity-75 flex items-center justify-center p-8 opacity-0 pointer-events-none lg:pointer-events-auto" >
173
+ < p className = "text-white text-lg md:text-xl lg:text-2xl text-center" >
174
+ { section . description }
175
+ </ p >
176
+ </ div >
177
+ </ div >
178
+ </ section >
179
+ ) ) }
180
+ </ div >
181
+ { ! isLargeScreen && selectedSection && (
182
+ < DialogContent className = "rounded-md sm:max-w-[calc(95vw-15px)] max-w-[calc(100vw-15px)] max-h-[calc(100vh-15px)] overflow-hidden flex z-[999] items-center justify-center p-2" >
183
+ < div className = "flex flex-col md:flex-col gap-6 max-h-full overflow-y-auto p-2" >
184
+ < div className = "" >
138
185
< img
139
- key = { imageIndex }
140
- src = { image }
141
- alt = { `Performer section ${ sectionIndex + 1 } , slide ${
142
- imageIndex + 1
143
- } of ${ section . images . length } `}
144
- className = { `absolute inset-0 w-full h-full object-cover transition-opacity duration-1000 ${
145
- imageIndex === currentImageIndices [ sectionIndex ]
146
- ? "opacity-100"
147
- : "opacity-0"
148
- } `}
149
- aria-hidden = { imageIndex !== currentImageIndices [ sectionIndex ] }
186
+ src = { selectedSection . images [ 0 ] }
187
+ alt = { `${ selectedSection . name } - ${ selectedSection . profession } ` }
188
+ className = "w-full h-auto object-cover rounded-lg"
150
189
/>
151
- ) ) }
152
- < div className = "absolute inset-0 bg-black bg-opacity-40 flex flex-col justify-end p-8" >
153
- < h2
154
- id = { `section-title-${ sectionIndex } ` }
155
- className = "text-3xl md:text-5xl lg:text-6xl font-bold text-white mb-2"
156
- >
157
- { section . name }
158
- </ h2 >
159
- < p className = "text-xl md:text-2xl text-white italic" >
160
- { section . profession }
161
- </ p >
162
190
</ div >
163
- < div className = "description absolute inset-0 bg-black bg-opacity-75 flex items-center justify-center p-8 opacity-0 " >
164
- < p className = "text-white text-lg md:text-xl lg:text-2xl text-center" >
165
- { section . description }
166
- </ p >
191
+ < div className = "" >
192
+ < h2 className = "text-2xl font-bold mb-2" > { selectedSection . name } </ h2 >
193
+ < p className = "text-xl italic mb-4" > { selectedSection . profession } </ p >
194
+ < p className = "text-base" > { selectedSection . description } < /p >
167
195
</ div >
168
196
</ div >
169
- </ section >
170
- ) ) }
171
- </ div >
172
- ) ;
197
+ </ DialogContent >
198
+ ) }
199
+ </ Dialog >
200
+ )
173
201
}
0 commit comments