1+ console . clear ( ) ;
2+
3+ const { gsap, imagesLoaded } = window ;
4+
5+ const buttons = {
6+ prev : document . querySelector ( ".btn--left" ) ,
7+ next : document . querySelector ( ".btn--right" ) ,
8+ } ;
9+ const cardsContainerEl = document . querySelector ( ".cards__wrapper" ) ;
10+ const appBgContainerEl = document . querySelector ( ".app__bg" ) ;
11+
12+ const cardInfosContainerEl = document . querySelector ( ".info__wrapper" ) ;
13+
14+ buttons . next . addEventListener ( "click" , ( ) => swapCards ( "right" ) ) ;
15+
16+ buttons . prev . addEventListener ( "click" , ( ) => swapCards ( "left" ) ) ;
17+
18+ function swapCards ( direction ) {
19+ const currentCardEl = cardsContainerEl . querySelector ( ".current--card" ) ;
20+ const previousCardEl = cardsContainerEl . querySelector ( ".previous--card" ) ;
21+ const nextCardEl = cardsContainerEl . querySelector ( ".next--card" ) ;
22+
23+ const currentBgImageEl = appBgContainerEl . querySelector ( ".current--image" ) ;
24+ const previousBgImageEl = appBgContainerEl . querySelector ( ".previous--image" ) ;
25+ const nextBgImageEl = appBgContainerEl . querySelector ( ".next--image" ) ;
26+
27+ changeInfo ( direction ) ;
28+ swapCardsClass ( ) ;
29+
30+ removeCardEvents ( currentCardEl ) ;
31+
32+ function swapCardsClass ( ) {
33+ currentCardEl . classList . remove ( "current--card" ) ;
34+ previousCardEl . classList . remove ( "previous--card" ) ;
35+ nextCardEl . classList . remove ( "next--card" ) ;
36+
37+ currentBgImageEl . classList . remove ( "current--image" ) ;
38+ previousBgImageEl . classList . remove ( "previous--image" ) ;
39+ nextBgImageEl . classList . remove ( "next--image" ) ;
40+
41+ currentCardEl . style . zIndex = "50" ;
42+ currentBgImageEl . style . zIndex = "-2" ;
43+
44+ if ( direction === "right" ) {
45+ previousCardEl . style . zIndex = "20" ;
46+ nextCardEl . style . zIndex = "30" ;
47+
48+ nextBgImageEl . style . zIndex = "-1" ;
49+
50+ currentCardEl . classList . add ( "previous--card" ) ;
51+ previousCardEl . classList . add ( "next--card" ) ;
52+ nextCardEl . classList . add ( "current--card" ) ;
53+
54+ currentBgImageEl . classList . add ( "previous--image" ) ;
55+ previousBgImageEl . classList . add ( "next--image" ) ;
56+ nextBgImageEl . classList . add ( "current--image" ) ;
57+ } else if ( direction === "left" ) {
58+ previousCardEl . style . zIndex = "30" ;
59+ nextCardEl . style . zIndex = "20" ;
60+
61+ previousBgImageEl . style . zIndex = "-1" ;
62+
63+ currentCardEl . classList . add ( "next--card" ) ;
64+ previousCardEl . classList . add ( "current--card" ) ;
65+ nextCardEl . classList . add ( "previous--card" ) ;
66+
67+ currentBgImageEl . classList . add ( "next--image" ) ;
68+ previousBgImageEl . classList . add ( "current--image" ) ;
69+ nextBgImageEl . classList . add ( "previous--image" ) ;
70+ }
71+ }
72+ }
73+
74+ function changeInfo ( direction ) {
75+ let currentInfoEl = cardInfosContainerEl . querySelector ( ".current--info" ) ;
76+ let previousInfoEl = cardInfosContainerEl . querySelector ( ".previous--info" ) ;
77+ let nextInfoEl = cardInfosContainerEl . querySelector ( ".next--info" ) ;
78+
79+ gsap . timeline ( )
80+ . to ( [ buttons . prev , buttons . next ] , {
81+ duration : 0.2 ,
82+ opacity : 0.5 ,
83+ pointerEvents : "none" ,
84+ } )
85+ . to (
86+ currentInfoEl . querySelectorAll ( ".text" ) ,
87+ {
88+ duration : 0.4 ,
89+ stagger : 0.1 ,
90+ translateY : "-120px" ,
91+ opacity : 0 ,
92+ } ,
93+ "-="
94+ )
95+ . call ( ( ) => {
96+ swapInfosClass ( direction ) ;
97+ } )
98+ . call ( ( ) => initCardEvents ( ) )
99+ . fromTo (
100+ direction === "right"
101+ ? nextInfoEl . querySelectorAll ( ".text" )
102+ : previousInfoEl . querySelectorAll ( ".text" ) ,
103+ {
104+ opacity : 0 ,
105+ translateY : "40px" ,
106+ } ,
107+ {
108+ duration : 0.4 ,
109+ stagger : 0.1 ,
110+ translateY : "0px" ,
111+ opacity : 1 ,
112+ }
113+ )
114+ . to ( [ buttons . prev , buttons . next ] , {
115+ duration : 0.2 ,
116+ opacity : 1 ,
117+ pointerEvents : "all" ,
118+ } ) ;
119+
120+ function swapInfosClass ( ) {
121+ currentInfoEl . classList . remove ( "current--info" ) ;
122+ previousInfoEl . classList . remove ( "previous--info" ) ;
123+ nextInfoEl . classList . remove ( "next--info" ) ;
124+
125+ if ( direction === "right" ) {
126+ currentInfoEl . classList . add ( "previous--info" ) ;
127+ nextInfoEl . classList . add ( "current--info" ) ;
128+ previousInfoEl . classList . add ( "next--info" ) ;
129+ } else if ( direction === "left" ) {
130+ currentInfoEl . classList . add ( "next--info" ) ;
131+ nextInfoEl . classList . add ( "previous--info" ) ;
132+ previousInfoEl . classList . add ( "current--info" ) ;
133+ }
134+ }
135+ }
136+
137+ function updateCard ( e ) {
138+ const card = e . currentTarget ;
139+ const box = card . getBoundingClientRect ( ) ;
140+ const centerPosition = {
141+ x : box . left + box . width / 2 ,
142+ y : box . top + box . height / 2 ,
143+ } ;
144+ let angle = Math . atan2 ( e . pageX - centerPosition . x , 0 ) * ( 35 / Math . PI ) ;
145+ gsap . set ( card , {
146+ "--current-card-rotation-offset" : `${ angle } deg` ,
147+ } ) ;
148+ const currentInfoEl = cardInfosContainerEl . querySelector ( ".current--info" ) ;
149+ gsap . set ( currentInfoEl , {
150+ rotateY : `${ angle } deg` ,
151+ } ) ;
152+ }
153+
154+ function resetCardTransforms ( e ) {
155+ const card = e . currentTarget ;
156+ const currentInfoEl = cardInfosContainerEl . querySelector ( ".current--info" ) ;
157+ gsap . set ( card , {
158+ "--current-card-rotation-offset" : 0 ,
159+ } ) ;
160+ gsap . set ( currentInfoEl , {
161+ rotateY : 0 ,
162+ } ) ;
163+ }
164+
165+ function initCardEvents ( ) {
166+ const currentCardEl = cardsContainerEl . querySelector ( ".current--card" ) ;
167+ currentCardEl . addEventListener ( "pointermove" , updateCard ) ;
168+ currentCardEl . addEventListener ( "pointerout" , ( e ) => {
169+ resetCardTransforms ( e ) ;
170+ } ) ;
171+ }
172+
173+ initCardEvents ( ) ;
174+
175+ function removeCardEvents ( card ) {
176+ card . removeEventListener ( "pointermove" , updateCard ) ;
177+ }
178+
179+ function init ( ) {
180+
181+ let tl = gsap . timeline ( ) ;
182+
183+ tl . to ( cardsContainerEl . children , {
184+ delay : 0.15 ,
185+ duration : 0.5 ,
186+ stagger : {
187+ ease : "power4.inOut" ,
188+ from : "right" ,
189+ amount : 0.1 ,
190+ } ,
191+ "--card-translateY-offset" : "0%" ,
192+ } )
193+ . to ( cardInfosContainerEl . querySelector ( ".current--info" ) . querySelectorAll ( ".text" ) , {
194+ delay : 0.5 ,
195+ duration : 0.4 ,
196+ stagger : 0.1 ,
197+ opacity : 1 ,
198+ translateY : 0 ,
199+ } )
200+ . to (
201+ [ buttons . prev , buttons . next ] ,
202+ {
203+ duration : 0.4 ,
204+ opacity : 1 ,
205+ pointerEvents : "all" ,
206+ } ,
207+ "-=0.4"
208+ ) ;
209+ }
210+
211+ const waitForImages = ( ) => {
212+ const images = [ ...document . querySelectorAll ( "img" ) ] ;
213+ const totalImages = images . length ;
214+ let loadedImages = 0 ;
215+ const loaderEl = document . querySelector ( ".loader span" ) ;
216+
217+ gsap . set ( cardsContainerEl . children , {
218+ "--card-translateY-offset" : "100vh" ,
219+ } ) ;
220+ gsap . set ( cardInfosContainerEl . querySelector ( ".current--info" ) . querySelectorAll ( ".text" ) , {
221+ translateY : "40px" ,
222+ opacity : 0 ,
223+ } ) ;
224+ gsap . set ( [ buttons . prev , buttons . next ] , {
225+ pointerEvents : "none" ,
226+ opacity : "0" ,
227+ } ) ;
228+
229+ images . forEach ( ( image ) => {
230+ imagesLoaded ( image , ( instance ) => {
231+ if ( instance . isComplete ) {
232+ loadedImages ++ ;
233+ let loadProgress = loadedImages / totalImages ;
234+
235+ gsap . to ( loaderEl , {
236+ duration : 1 ,
237+ scaleX : loadProgress ,
238+ backgroundColor : `hsl(${ loadProgress * 120 } , 100%, 50%` ,
239+ } ) ;
240+
241+ if ( totalImages == loadedImages ) {
242+ gsap . timeline ( )
243+ . to ( ".loading__wrapper" , {
244+ duration : 0.8 ,
245+ opacity : 0 ,
246+ pointerEvents : "none" ,
247+ } )
248+ . call ( ( ) => init ( ) ) ;
249+ }
250+ }
251+ } ) ;
252+ } ) ;
253+ } ;
254+
255+ waitForImages ( ) ;
0 commit comments