├── .DS_Store
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── assets
├── .DS_Store
├── img1.webp
├── img10.webp
├── img11.webp
├── img12.webp
├── img13.webp
├── img14.webp
├── img15.webp
├── img16.webp
├── img17.webp
├── img18.webp
├── img19.webp
├── img2.webp
├── img20.webp
├── img21.webp
├── img22.webp
├── img23.webp
├── img24.webp
├── img25.webp
├── img26.webp
├── img27.webp
├── img28.webp
├── img29.webp
├── img3.webp
├── img30.webp
├── img31.webp
├── img32.webp
├── img33.webp
├── img34.webp
├── img35.webp
├── img36.webp
├── img37.webp
├── img38.webp
├── img39.webp
├── img4.webp
├── img40.webp
├── img41.webp
├── img42.webp
├── img43.webp
├── img44.webp
├── img45.webp
├── img46.webp
├── img47.webp
├── img48.webp
├── img49.webp
├── img5.webp
├── img50.webp
├── img51.webp
├── img52.webp
├── img53.webp
├── img54.webp
├── img55.webp
├── img56.webp
├── img57.webp
├── img58.webp
├── img59.webp
├── img6.webp
├── img60.webp
├── img61.webp
├── img62.webp
├── img63.webp
├── img64.webp
├── img65.webp
├── img66.webp
├── img67.webp
├── img68.webp
├── img69.webp
├── img7.webp
├── img70.webp
├── img71.webp
├── img72.webp
├── img8.webp
└── img9.webp
├── css
└── base.css
├── index.html
└── js
├── .DS_Store
├── ScrollSmoother.min.js
├── ScrollToPlugin.min.js
├── ScrollTrigger.min.js
├── SplitText.min.js
├── gsap.min.js
├── imagesloaded.pkgd.min.js
├── index.js
└── utils.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/.DS_Store
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | .parcel-cache
4 | package-lock.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2009 - 2024 [Codrops](https://tympanus.net/codrops)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Om-Scroll 3D Carousel
2 |
3 | A scroll based 3D carousel animation with a page transition effect.
4 |
5 | 
6 |
7 | [Article on Codrops](https://tympanus.net/codrops/?p=93330)
8 |
9 | [Demo](https://tympanus.net/Development/3DCarousel/)
10 |
11 | ## Installation
12 |
13 | Run this demo on a [local server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server).
14 |
15 | ## Credits
16 |
17 | - Images generated with [Midjourney](https://midjourney.com)
18 |
19 | ## Misc
20 |
21 | Follow Codrops: [Bluesky](https://bsky.app/profile/codrops.bsky.social), [Facebook](http://www.facebook.com/codrops), [GitHub](https://github.com/codrops), [Instagram](https://www.instagram.com/codropsss/), [X](http://www.x.com/codrops)
22 |
23 | ## License
24 |
25 | [MIT](LICENSE)
26 |
--------------------------------------------------------------------------------
/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/.DS_Store
--------------------------------------------------------------------------------
/assets/img1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img1.webp
--------------------------------------------------------------------------------
/assets/img10.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img10.webp
--------------------------------------------------------------------------------
/assets/img11.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img11.webp
--------------------------------------------------------------------------------
/assets/img12.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img12.webp
--------------------------------------------------------------------------------
/assets/img13.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img13.webp
--------------------------------------------------------------------------------
/assets/img14.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img14.webp
--------------------------------------------------------------------------------
/assets/img15.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img15.webp
--------------------------------------------------------------------------------
/assets/img16.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img16.webp
--------------------------------------------------------------------------------
/assets/img17.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img17.webp
--------------------------------------------------------------------------------
/assets/img18.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img18.webp
--------------------------------------------------------------------------------
/assets/img19.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img19.webp
--------------------------------------------------------------------------------
/assets/img2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img2.webp
--------------------------------------------------------------------------------
/assets/img20.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img20.webp
--------------------------------------------------------------------------------
/assets/img21.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img21.webp
--------------------------------------------------------------------------------
/assets/img22.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img22.webp
--------------------------------------------------------------------------------
/assets/img23.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img23.webp
--------------------------------------------------------------------------------
/assets/img24.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img24.webp
--------------------------------------------------------------------------------
/assets/img25.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img25.webp
--------------------------------------------------------------------------------
/assets/img26.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img26.webp
--------------------------------------------------------------------------------
/assets/img27.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img27.webp
--------------------------------------------------------------------------------
/assets/img28.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img28.webp
--------------------------------------------------------------------------------
/assets/img29.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img29.webp
--------------------------------------------------------------------------------
/assets/img3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img3.webp
--------------------------------------------------------------------------------
/assets/img30.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img30.webp
--------------------------------------------------------------------------------
/assets/img31.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img31.webp
--------------------------------------------------------------------------------
/assets/img32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img32.webp
--------------------------------------------------------------------------------
/assets/img33.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img33.webp
--------------------------------------------------------------------------------
/assets/img34.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img34.webp
--------------------------------------------------------------------------------
/assets/img35.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img35.webp
--------------------------------------------------------------------------------
/assets/img36.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img36.webp
--------------------------------------------------------------------------------
/assets/img37.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img37.webp
--------------------------------------------------------------------------------
/assets/img38.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img38.webp
--------------------------------------------------------------------------------
/assets/img39.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img39.webp
--------------------------------------------------------------------------------
/assets/img4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img4.webp
--------------------------------------------------------------------------------
/assets/img40.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img40.webp
--------------------------------------------------------------------------------
/assets/img41.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img41.webp
--------------------------------------------------------------------------------
/assets/img42.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img42.webp
--------------------------------------------------------------------------------
/assets/img43.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img43.webp
--------------------------------------------------------------------------------
/assets/img44.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img44.webp
--------------------------------------------------------------------------------
/assets/img45.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img45.webp
--------------------------------------------------------------------------------
/assets/img46.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img46.webp
--------------------------------------------------------------------------------
/assets/img47.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img47.webp
--------------------------------------------------------------------------------
/assets/img48.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img48.webp
--------------------------------------------------------------------------------
/assets/img49.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img49.webp
--------------------------------------------------------------------------------
/assets/img5.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img5.webp
--------------------------------------------------------------------------------
/assets/img50.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img50.webp
--------------------------------------------------------------------------------
/assets/img51.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img51.webp
--------------------------------------------------------------------------------
/assets/img52.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img52.webp
--------------------------------------------------------------------------------
/assets/img53.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img53.webp
--------------------------------------------------------------------------------
/assets/img54.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img54.webp
--------------------------------------------------------------------------------
/assets/img55.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img55.webp
--------------------------------------------------------------------------------
/assets/img56.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img56.webp
--------------------------------------------------------------------------------
/assets/img57.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img57.webp
--------------------------------------------------------------------------------
/assets/img58.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img58.webp
--------------------------------------------------------------------------------
/assets/img59.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img59.webp
--------------------------------------------------------------------------------
/assets/img6.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img6.webp
--------------------------------------------------------------------------------
/assets/img60.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img60.webp
--------------------------------------------------------------------------------
/assets/img61.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img61.webp
--------------------------------------------------------------------------------
/assets/img62.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img62.webp
--------------------------------------------------------------------------------
/assets/img63.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img63.webp
--------------------------------------------------------------------------------
/assets/img64.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img64.webp
--------------------------------------------------------------------------------
/assets/img65.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img65.webp
--------------------------------------------------------------------------------
/assets/img66.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img66.webp
--------------------------------------------------------------------------------
/assets/img67.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img67.webp
--------------------------------------------------------------------------------
/assets/img68.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img68.webp
--------------------------------------------------------------------------------
/assets/img69.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img69.webp
--------------------------------------------------------------------------------
/assets/img7.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img7.webp
--------------------------------------------------------------------------------
/assets/img70.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img70.webp
--------------------------------------------------------------------------------
/assets/img71.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img71.webp
--------------------------------------------------------------------------------
/assets/img72.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img72.webp
--------------------------------------------------------------------------------
/assets/img8.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img8.webp
--------------------------------------------------------------------------------
/assets/img9.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/assets/img9.webp
--------------------------------------------------------------------------------
/css/base.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | font-size: 14px;
9 | --color-text: #fff;
10 | --color-bg: #0f0e0e;
11 | --color-link: #ffffff;
12 | --color-link-hover: rgba(255, 255, 255, 0.6);
13 | --page-padding: 0.5rem;
14 | --aspect: 4/5;
15 | --grid-item-height: 20vh;
16 | --c-gap: 3rem;
17 | --r-gap: 3rem;
18 | --column: 80px;
19 | --column-count: 3;
20 | --border-radius: 4px;
21 | }
22 |
23 | html {
24 | height: 100%;
25 | overflow-x: hidden;
26 | }
27 |
28 | body {
29 | height: 100%;
30 | width: 100%;
31 | font-family: 'forma-djr-mono', sans-serif;
32 | text-transform: uppercase;
33 | margin: 0;
34 | color: var(--color-text);
35 | background-color: var(--color-bg);
36 | -webkit-font-smoothing: antialiased;
37 | -moz-osx-font-smoothing: grayscale;
38 | }
39 |
40 | @media (scripting: enabled) {
41 | .loading {
42 | &::before,
43 | &::after {
44 | content: '';
45 | position: fixed;
46 | z-index: 10000;
47 | }
48 |
49 | &::before {
50 | top: 0;
51 | left: 0;
52 | width: 100%;
53 | height: 100%;
54 | background: var(--color-bg);
55 | }
56 |
57 | &::after {
58 | top: 50%;
59 | left: 50%;
60 | width: 100px;
61 | height: 1px;
62 | margin: 0 0 0 -50px;
63 | background: var(--color-link);
64 | animation: loaderAnim 1.5s ease-in-out infinite alternate forwards;
65 | }
66 | }
67 | }
68 |
69 | @keyframes loaderAnim {
70 | 0% {
71 | transform: scaleX(0);
72 | transform-origin: 0% 50%;
73 | }
74 |
75 | 50% {
76 | transform: scaleX(1);
77 | transform-origin: 0% 50%;
78 | }
79 |
80 | 50.1% {
81 | transform: scaleX(1);
82 | transform-origin: 100% 50%;
83 | }
84 |
85 | 100% {
86 | transform: scaleX(0);
87 | transform-origin: 100% 50%;
88 | }
89 | }
90 |
91 | a {
92 | text-decoration: none;
93 | color: var(--color-link);
94 | outline: none;
95 | cursor: pointer;
96 | transition: all 0.3s ease;
97 |
98 | &:hover {
99 | text-decoration: none;
100 | color: var(--color-link-hover);
101 | }
102 |
103 | &:focus {
104 | outline: none;
105 | background: lightgrey;
106 |
107 | &:not(:focus-visible) {
108 | background: transparent;
109 | }
110 |
111 | &:focus-visible {
112 | outline: 2px solid red;
113 | background: transparent;
114 | }
115 | }
116 | }
117 |
118 | .line {
119 | display: inline-block;
120 | overflow: hidden;
121 | position: relative;
122 | vertical-align: top;
123 |
124 | &::before {
125 | background: var(--color-link-hover);
126 | bottom: 0;
127 | content: '';
128 | height: 1px;
129 | left: 0;
130 | position: absolute;
131 | transition: transform 0.4s ease;
132 | width: 100%;
133 | transform: scaleX(0);
134 | transform-origin: right center;
135 | }
136 |
137 | &:hover::before {
138 | transform: scaleX(1);
139 | transform-origin: left center;
140 | }
141 | }
142 |
143 | .frame {
144 | display: grid;
145 | z-index: 1000;
146 | width: 100%;
147 | height: 100vh;
148 | position: fixed;
149 | grid-column-gap: var(--c-gap);
150 | grid-row-gap: 0.5rem;
151 | pointer-events: none;
152 | justify-items: start;
153 | padding: var(--page-padding);
154 | align-content: space-between;
155 | justify-content: space-between;
156 | align-items: end;
157 | grid-template-areas: 'title links' 'tags sponsor';
158 |
159 | #cdawrap {
160 | justify-self: start;
161 | grid-area: sponsor;
162 | max-width: 230px;
163 | text-align: right;
164 | }
165 |
166 | a,
167 | button {
168 | pointer-events: auto;
169 | color: var(--color-text);
170 | }
171 |
172 | .frame__title {
173 | font-size: inherit;
174 | margin: 0;
175 | font-weight: inherit;
176 | grid-area: title;
177 | text-align: right;
178 | }
179 |
180 | .frame__tags,
181 | .frame__links {
182 | grid-area: tags;
183 | display: flex;
184 | gap: 1rem;
185 | align-items: start;
186 | }
187 |
188 | .frame__links {
189 | grid-area: links;
190 | justify-self: end;
191 | }
192 |
193 | &.frame--footer {
194 | display: flex;
195 | min-height: 300px;
196 | align-items: end;
197 | justify-content: space-between;
198 | }
199 | }
200 |
201 | .scene {
202 | perspective: 900px;
203 | position: relative;
204 | height: 100vh;
205 | display: flex;
206 | justify-content: center;
207 | align-items: center;
208 | }
209 |
210 | .scene__title {
211 | position: relative;
212 | z-index: 10;
213 | margin: 0;
214 | }
215 |
216 | .scene__title a {
217 | display: block;
218 | }
219 |
220 | .scene__title span {
221 | display: inline-block;
222 | }
223 |
224 | .scene__title .char {
225 | display: inline-block;
226 | transform-style: preserve-3d;
227 | transform-origin: 50% 0%;
228 | }
229 |
230 | .carousel {
231 | width: 400px;
232 | height: 500px;
233 | top: 50%;
234 | left: 50%;
235 | margin: -250px 0 0 -200px;
236 | position: absolute;
237 | transform-style: preserve-3d;
238 | will-change: transform;
239 | transform: translateZ(-550px) rotateY(0deg);
240 | }
241 |
242 | .scene:nth-child(even) .carousel {
243 | transform: translateZ(-550px) rotateY(45deg);
244 | }
245 |
246 | .carousel__cell {
247 | position: absolute;
248 | width: 350px;
249 | height: 420px;
250 | left: 0;
251 | top: 0;
252 | transform-style: preserve-3d;
253 | }
254 |
255 | .card {
256 | width: 100%;
257 | height: 100%;
258 | position: relative;
259 | transform-style: preserve-3d;
260 | }
261 |
262 | .card__face {
263 | width: 100%;
264 | height: 100%;
265 | position: absolute;
266 | backface-visibility: hidden;
267 | background-image: var(--img);
268 | background-size: cover;
269 | }
270 |
271 | .card__face img {
272 | width: 100%;
273 | height: 100%;
274 | object-fit: cover;
275 | border-radius: var(--border-radius);
276 | }
277 |
278 | .card__face--back {
279 | transform: rotateY(180deg);
280 | }
281 |
282 | .preview {
283 | position: fixed;
284 | top: 0;
285 | left: 0;
286 | width: 100%;
287 | height: 100%;
288 | padding: 0 15vw;
289 | display: grid;
290 | align-content: center;
291 | justify-items: center;
292 | grid-row-gap: 1rem;
293 | opacity: 0;
294 | pointer-events: none;
295 | }
296 |
297 | .preview__header {
298 | display: flex;
299 | width: 100%;
300 | align-items: flex-end;
301 | justify-content: space-between;
302 | line-height: 1;
303 | }
304 |
305 | .preview__title {
306 | margin: 0;
307 | }
308 |
309 | .preview__close {
310 | background: none;
311 | text-transform: inherit;
312 | border: 0;
313 | padding: 0;
314 | margin: 0;
315 | font: inherit;
316 | cursor: pointer;
317 | color: var(--color-close);
318 | &:hover,
319 | &:focus {
320 | outline: none;
321 | color: var(--color-link-hover);
322 | }
323 | }
324 |
325 | .grid {
326 | padding: 1rem 0;
327 | display: grid;
328 | grid-template-columns: repeat(var(--column-count), minmax(var(--column), 1fr));
329 | grid-column-gap: var(--c-gap);
330 | grid-row-gap: var(--r-gap);
331 | width: 100%;
332 | perspective: 900px;
333 | }
334 |
335 | .grid__item {
336 | margin: 0;
337 | padding: 0;
338 | display: flex;
339 | flex-direction: column;
340 | gap: 0.25rem;
341 | will-change: transform, clip-path;
342 | overflow: hidden;
343 | border-radius: var(--border-radius);
344 | position: relative;
345 | transform-style: preserve-3d;
346 | }
347 |
348 | .grid__item-image {
349 | width: 100%;
350 | aspect-ratio: var(--aspect);
351 | background-size: 100%;
352 | background-position: 50% 50%;
353 | transition: opacity 0.15s cubic-bezier(0.2, 0, 0.2, 1);
354 | }
355 |
356 | .grid__item:hover .grid__item-image {
357 | opacity: 0.7;
358 | }
359 |
360 | .grid__item-caption h3 {
361 | font-size: 1rem;
362 | font-weight: 400;
363 | margin: 0;
364 | }
365 |
366 | @media screen and (min-width: 65em) {
367 | :root {
368 | --column-count: 6;
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | On-Scroll 3D Carousel | Codrops
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
31 |
32 |
33 |
34 |
65 |
66 |
97 |
98 |
99 |
102 |
103 |
109 |
115 |
121 |
127 |
128 |
129 |
130 |
131 |
134 |
135 |
141 |
147 |
153 |
159 |
160 |
161 |
162 |
163 |
166 |
167 |
173 |
179 |
185 |
191 |
197 |
203 |
204 |
205 |
206 |
207 |
210 |
211 |
217 |
223 |
229 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
245 |
246 |
247 |
248 |
249 | Kai Vega
250 |
251 |
252 |
253 |
254 |
255 | Riven Juno
256 |
257 |
258 |
259 |
260 |
261 | Lex Orion
262 |
263 |
264 |
265 |
266 |
267 | Ash Kairos
268 |
269 |
270 |
271 |
272 |
273 | Juno Sol
274 |
275 |
276 |
277 |
278 |
279 | Soren Nyx
280 |
281 |
282 |
283 |
284 |
285 | Quinn Axon
286 |
287 |
288 |
289 |
290 |
291 | Zara Voss
292 |
293 |
294 |
295 |
296 |
297 | Hale B.
298 |
299 |
300 |
301 |
302 |
303 | Gundra Wex
304 |
305 |
306 |
307 |
308 |
309 | Extra One
310 |
311 |
312 |
313 |
314 |
315 | Extra Two
316 |
317 |
318 |
319 |
320 |
321 |
322 |
326 |
327 |
328 |
329 |
330 | Arlo Quinn
331 |
332 |
333 |
334 |
335 |
336 | Vera Kline
337 |
338 |
339 |
340 |
341 |
342 | Juno Vale
343 |
344 |
345 |
346 |
347 |
348 | Ember Dash
349 |
350 |
351 |
352 |
353 |
354 | Rylee Voss
355 |
356 |
357 |
358 |
359 |
360 | Harlow Nova
361 |
362 |
363 |
364 |
365 |
366 | Blake Lune
367 |
368 |
369 |
370 |
371 |
372 | Zephyr Kade
373 |
374 |
375 |
376 |
377 |
378 | Indigo Rae
379 |
380 |
381 |
382 |
383 |
384 | Kairo Jett
385 |
386 |
387 |
388 |
389 |
390 | Extra One
391 |
392 |
393 |
394 |
395 |
396 | Extra Two
397 |
398 |
399 |
400 |
401 |
402 |
403 |
407 |
408 |
409 |
410 |
411 | Luca Raine
412 |
413 |
414 |
415 |
416 |
417 | Rory Vale
418 |
419 |
420 |
421 |
422 |
423 | Sable Zev
424 |
425 |
426 |
427 |
428 |
429 | Ellis Nova
430 |
431 |
432 |
433 |
434 |
435 | Wren Asher
436 |
437 |
438 |
439 |
440 |
441 | Zane Sky
442 |
443 |
444 |
445 |
446 |
447 | Rowan Juno
448 |
449 |
450 |
451 |
452 |
453 | Fenix Blade
454 |
455 |
456 |
457 |
458 |
459 | Alix Storm
460 |
461 |
462 |
463 |
464 |
465 | Nova Ray
466 |
467 |
468 |
469 |
470 |
471 | Extra One
472 |
473 |
474 |
475 |
476 |
477 | Extra Two
478 |
479 |
480 |
481 |
482 |
483 |
484 |
488 |
489 |
490 |
491 |
492 | Aeris Flint
493 |
494 |
495 |
496 |
497 |
498 | Jett Voss
499 |
500 |
501 |
502 |
503 |
504 | Caius Storm
505 |
506 |
507 |
508 |
509 |
510 | Mira Celeste
511 |
512 |
513 |
514 |
515 |
516 | Liam Ashford
517 |
518 |
519 |
520 |
521 |
522 | Vega Dawn
523 |
524 |
525 |
526 |
527 |
528 | Orion Phoenix
529 |
530 |
531 |
532 |
533 |
534 | Rex Solara
535 |
536 |
537 |
538 |
539 |
540 | Elara Finch
541 |
542 |
543 |
544 |
545 |
546 | Zoe Star
547 |
548 |
549 |
550 |
551 |
552 | Extra One
553 |
554 |
555 |
556 |
557 |
558 | Extra Two
559 |
560 |
561 |
562 |
563 |
564 |
565 |
569 |
570 |
571 |
572 |
573 | Rylan Ash
574 |
575 |
576 |
577 |
578 |
579 | Lyra Wren
580 |
581 |
582 |
583 |
584 |
585 | Axel Orion
586 |
587 |
588 |
589 |
590 |
591 | Nova Sky
592 |
593 |
594 |
595 |
596 |
597 | Kael Dray
598 |
599 |
600 |
601 |
602 |
603 | Vesper Quill
604 |
605 |
606 |
607 |
608 |
609 | Lira Wilder
610 |
611 |
612 |
613 |
614 |
615 | Indigo Raye
616 |
617 |
618 |
619 |
620 |
621 | Juno Storm
622 |
623 |
624 |
625 |
626 |
627 | Ollie Lune
628 |
629 |
630 |
631 |
632 |
633 | Extra One
634 |
635 |
636 |
637 |
638 |
639 | Extra Two
640 |
641 |
642 |
643 |
644 |
645 |
646 |
650 |
651 |
652 |
653 |
654 | Corin Blaize
655 |
656 |
657 |
658 |
659 |
660 | Tess Kade
661 |
662 |
663 |
664 |
665 |
666 | Juno Hale
667 |
668 |
669 |
670 |
671 |
672 | Coral Vale
673 |
674 |
675 |
676 |
677 |
678 | Ari Lennox
679 |
680 |
681 |
682 |
683 |
684 | Ronan Aster
685 |
686 |
687 |
688 |
689 |
690 | Arius Quill
691 |
692 |
693 |
694 |
695 |
696 | Rex Ember
697 |
698 |
699 |
700 |
701 |
702 | Vega Ashford
703 |
704 |
705 |
706 |
707 |
708 | Finn Fenix
709 |
710 |
711 |
712 |
713 |
714 | Extra One
715 |
716 |
717 |
718 |
719 |
720 | Extra Two
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
--------------------------------------------------------------------------------
/js/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/3DCarousel/f354a11d19777b4e573d47b016354951b1ecace3/js/.DS_Store
--------------------------------------------------------------------------------
/js/ScrollSmoother.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ScrollSmoother 3.13.0
3 | * https://gsap.com
4 | *
5 | * @license Copyright 2025, GreenSock. All rights reserved.
6 | * Subject to the terms at https://gsap.com/standard-license.
7 | * @author: Jack Doyle, jack@greensock.com
8 | */
9 |
10 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).window=e.window||{})}(this,function(e){"use strict";function _defineProperties(e,t){for(var r=0;r
=v())&&(n=((r=v())-(t=e.ratio<0||1Math.abs(l)?a:l)/(1-t),f=-c*t;return 0t.end&&(s+=t.distance),n-=t.distance}o=d+s+y*((I.utils.clamp(e.start,e.end,r)-e.start-s)/(n-e.start)-c)}m.length&&!a&&m.forEach(function(e){return e(o-s)}),o=function _round(e){return Math.round(1e5*e)/1e5||0}(o+f),l?(l.resetTo("y",o,-F,!0),M&&l.progress(1)):(g.y=o+"px",g.renderTransform(1))}}})),I.core.getCache(s.trigger).stRevert=Ra,s.startY=d,s.pins=p,s.markers=m,s.ratio=i,s.autoSpeed=a,r.style.willChange="transform"),s}var n,S,e,i,b,s,a,l,c,f,r,u,h,d,g,p,m=t.smoothTouch,w=t.onUpdate,T=t.onStop,_=t.smooth,C=t.onFocusIn,P=t.normalizeScroll,x=t.wholePixels,R=this,E=t.effectsPrefix||"",k=Y.getScrollFunc(U),H=1===Y.isTouch?!0===m?.8:parseFloat(m)||0:0===_||!1===_?0:parseFloat(_)||.8,A=H&&+t.speed||1,N=0,F=0,M=1,z=J(0),B={y:0},L="undefined"!=typeof ResizeObserver&&!1!==t.autoResize&&new ResizeObserver(function(){if(!Y.isRefreshing){var e=v(S)*A;e<-N&&Ha(e),$.restart(!0)}});function refreshHeight(){return e=n.clientHeight,n.style.overflow="visible",K.style.height=U.innerHeight+(e-U.innerHeight)/A+"px",e-U.innerHeight}Pa(),Y.addEventListener("killAll",Pa),I.delayedCall(.5,function(){return M=0}),this.scrollTop=Ha,this.scrollTo=function(e,t,r){var n=I.utils.clamp(0,v(),isNaN(e)?o.offset(e,r,!!t&&!f):+e);t?f?I.to(o,{duration:H,scrollTop:n,overwrite:"auto",ease:W}):k(n):Ha(n)},this.offset=function(e,t,r){var n,o=(e=q(e)[0]).style.cssText,i=Y.create({trigger:e,start:t||"top top"});return b&&(M?Y.refresh():Na([i],!0)),n=i.start/(r?A:1),i.kill(!1),e.style.cssText=o,I.core.getCache(e).uncache=1,n},this.content=function(e){if(arguments.length){var t=q(e||"#smooth-content")[0]||console.warn("ScrollSmoother needs a valid content element.")||K.children[0];return t!==n&&(c=(n=t).getAttribute("style")||"",L&&L.observe(n),I.set(n,{overflow:"visible",width:"100%",boxSizing:"border-box",y:"+=0"}),H||I.set(n,{clearProps:"transform"})),this}return n},this.wrapper=function(e){return arguments.length?(S=q(e||"#smooth-wrapper")[0]||function _wrap(e){var t=j.querySelector(".ScrollSmoother-wrapper");return t||((t=j.createElement("div")).classList.add("ScrollSmoother-wrapper"),e.parentNode.insertBefore(t,e),t.appendChild(e)),t}(n),l=S.getAttribute("style")||"",refreshHeight(),I.set(S,H?{overflow:"hidden",position:"fixed",height:"100%",width:"100%",top:0,left:0,right:0,bottom:0}:{overflow:"visible",position:"relative",width:"100%",height:"auto",top:"auto",bottom:"auto",left:"auto",right:"auto"}),this):S},this.effects=function(e,t){if(b=b||[],!e)return b.slice(0);(e=q(e)).forEach(function(e){for(var t=b.length;t--;)b[t].trigger===e&&b[t].kill()});t=t||{};var r,n,o=t.speed,i=t.lag,s=t.effectsPadding,a=[];for(r=0;rr._dp._time,u=N,B.y=0,H&&(1===Y.isTouch&&(S.style.position="absolute"),S.scrollTop=0,1===Y.isTouch&&(S.style.position="fixed"))}},onRefresh:function onRefresh(e){e.animation.invalidate(),e.setPositions(e.start,refreshHeight()/A),h||Fa(e),B.y=-k()*A,Ga(B.y),M||(h&&(g=!1),e.animation.progress(I.utils.clamp(0,1,u/A/-e.end))),h&&(e.progress-=.001,e.update()),ScrollSmoother.isRefreshing=!1},id:"ScrollSmoother",scroller:U,invalidateOnRefresh:!0,start:0,refreshPriority:-9999,end:function end(){return refreshHeight()/A},onScrubComplete:function onScrubComplete(){z.reset(),T&&T(o)},scrub:H||!0}),this.smooth=function(e){return arguments.length&&(A=(H=e||0)&&+t.speed||1,i.scrubDuration(e)),i.getTween()?i.getTween().duration():0},i.getTween()&&(i.getTween().vars.ease=t.ease||W),this.scrollTrigger=i,t.effects&&this.effects(!0===t.effects?"[data-"+E+"speed], [data-"+E+"lag]":t.effects,{effectsPadding:t.effectsPadding,refresh:!1}),t.sections&&this.sections(!0===t.sections?"[data-section]":t.sections),O.forEach(function(e){e.vars.scroller=S,e.revert(!1,!0),e.init(e.vars,e.animation)}),this.paused=function(e,t){return arguments.length?(!!f!==e&&(e?(i.getTween()&&i.getTween().pause(),k(-N/A),z.reset(),(r=Y.normalizeScroll())&&r.disable(),(f=Y.observe({preventDefault:!0,type:"wheel,touch,scroll",debounce:!1,allowClicks:!0,onChangeY:function onChangeY(){return Ha(-N)}})).nested=X(G,"wheel,touch,scroll",!0,!1!==t)):(f.nested.kill(),f.kill(),f=0,r&&r.enable(),i.progress=(-N/A-i.start)/(i.end-i.start),Fa(i))),this):!!f},this.kill=this.revert=function(){o.paused(!1),Fa(i),i.kill();for(var e=(b||[]).concat(s||[]),t=e.length;t--;)e[t].kill();Y.scrollerProxy(S),Y.removeEventListener("killAll",Pa),Y.removeEventListener("refresh",Oa),S.style.cssText=l,n.style.cssText=c;var r=Y.defaults({});r&&r.scroller===S&&Y.defaults({scroller:U}),o.normalizer&&Y.normalizeScroll(!1),clearInterval(a),Q=null,L&&L.disconnect(),K.style.removeProperty("height"),U.removeEventListener("focusin",Ka)},this.refresh=function(e,t){return i.refresh(e,t)},P&&(this.normalizer=Y.normalizeScroll(!0===P?{debounce:!0,content:!H&&n}:P)),Y.config(t),"scrollBehavior"in U.getComputedStyle(K)&&I.set([K,G],{scrollBehavior:"auto"}),U.addEventListener("focusin",Ka),a=setInterval(Ba,250),"loading"===j.readyState||requestAnimationFrame(function(){return Y.refresh()})}r.version="3.13.0",r.create=function(e){return Q&&e&&Q.content()===q(e.content)[0]?Q:new r(e)},r.get=function(){return Q},t()&&I.registerPlugin(r),e.ScrollSmoother=r,e.default=r;if (typeof(window)==="undefined"||window!==e){Object.defineProperty(e,"__esModule",{value:!0})} else {delete e.default}});
11 |
12 |
--------------------------------------------------------------------------------
/js/ScrollToPlugin.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ScrollToPlugin 3.13.0
3 | * https://gsap.com
4 | *
5 | * @license Copyright 2025, GreenSock. All rights reserved.
6 | * Subject to the terms at https://gsap.com/standard-license.
7 | * @author: Jack Doyle, jack@greensock.com
8 | */
9 |
10 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).window=e.window||{})}(this,function(e){"use strict";function l(){return"undefined"!=typeof window}function m(){return f||l()&&(f=window.gsap)&&f.registerPlugin&&f}function n(e){return"string"==typeof e}function o(e){return"function"==typeof e}function p(e,t){var o="x"===t?"Width":"Height",n="scroll"+o,r="client"+o;return e===T||e===i||e===c?Math.max(i[n],c[n])-(T["inner"+o]||i[r]||c[r]):e[n]-e["offset"+o]}function q(e,t){var o="scroll"+("x"===t?"Left":"Top");return e===T&&(null!=e.pageXOffset?o="page"+t.toUpperCase()+"Offset":e=null!=i[o]?i:c),function(){return e[o]}}function s(e,t){if(!(e=y(e)[0])||!e.getBoundingClientRect)return console.warn("scrollTo target doesn't exist. Using 0")||{x:0,y:0};var o=e.getBoundingClientRect(),n=!t||t===T||t===c,r=n?{top:i.clientTop-(T.pageYOffset||i.scrollTop||c.scrollTop||0),left:i.clientLeft-(T.pageXOffset||i.scrollLeft||c.scrollLeft||0)}:t.getBoundingClientRect(),l={x:o.left-r.left,y:o.top-r.top};return!n&&t&&(l.x+=q(t,"x")(),l.y+=q(t,"y")()),l}function t(e,t,o,r,l){return isNaN(e)||"object"==typeof e?n(e)&&"="===e.charAt(1)?parseFloat(e.substr(2))*("-"===e.charAt(0)?-1:1)+r-l:"max"===e?p(t,o)-l:Math.min(p(t,o),s(e,t)[o]-l):parseFloat(e)-l}function u(){f=m(),l()&&f&&"undefined"!=typeof document&&document.body&&(T=window,c=document.body,i=document.documentElement,y=f.utils.toArray,f.config({autoKillThreshold:7}),h=f.config(),a=1)}var f,a,T,i,c,y,h,v,r={version:"3.13.0",name:"scrollTo",rawVars:1,register:function register(e){f=e,u()},init:function init(e,r,l,i,s){a||u();var p=this,c=f.getProperty(e,"scrollSnapType");p.isWin=e===T,p.target=e,p.tween=l,r=function _clean(e,t,r,l){if(o(e)&&(e=e(t,r,l)),"object"!=typeof e)return n(e)&&"max"!==e&&"="!==e.charAt(1)?{x:e,y:e}:{y:e};if(e.nodeType)return{y:e,x:e};var i,s={};for(i in e)s[i]="onAutoKill"!==i&&o(e[i])?e[i](t,r,l):e[i];return s}(r,i,e,s),p.vars=r,p.autoKill=!!("autoKill"in r?r:h).autoKill,p.getX=q(e,"x"),p.getY=q(e,"y"),p.x=p.xPrev=p.getX(),p.y=p.yPrev=p.getY(),v=v||f.core.globals().ScrollTrigger,"smooth"===f.getProperty(e,"scrollBehavior")&&f.set(e,{scrollBehavior:"auto"}),c&&"none"!==c&&(p.snap=1,p.snapInline=e.style.scrollSnapType,e.style.scrollSnapType="none"),null!=r.x?(p.add(p,"x",p.x,t(r.x,e,"x",p.x,r.offsetX||0),i,s),p._props.push("scrollTo_x")):p.skipX=1,null!=r.y?(p.add(p,"y",p.y,t(r.y,e,"y",p.y,r.offsetY||0),i,s),p._props.push("scrollTo_y")):p.skipY=1},render:function render(e,t){for(var o,n,r,l,i,s=t._pt,c=t.target,u=t.tween,f=t.autoKill,a=t.xPrev,y=t.yPrev,d=t.isWin,g=t.snap,x=t.snapInline;s;)s.r(e,s.d),s=s._next;o=d||!t.skipX?t.getX():a,r=(n=d||!t.skipY?t.getY():y)-y,l=o-a,i=h.autoKillThreshold,t.x<0&&(t.x=0),t.y<0&&(t.y=0),f&&(!t.skipX&&(i=Math.abs(r)?t:r}function P(){(Ae=Se.core.globals().ScrollTrigger)&&Ae.core&&function _integrate(){var e=Ae.core,r=e.bridge||{},t=e._scrollers,n=e._proxies;t.push.apply(t,ze),n.push.apply(n,Ye),ze=t,Ye=n,o=function _bridge(e,t){return r[e](t)}}()}function Q(e){return Se=e||r(),!ke&&Se&&"undefined"!=typeof document&&document.body&&(Te=window,Me=(Ce=document).documentElement,Ee=Ce.body,t=[Te,Ce,Me,Ee],Se.utils.clamp,Be=Se.core.context||function(){},Oe="onpointerenter"in Ee?"pointer":"mouse",Pe=E.isTouch=Te.matchMedia&&Te.matchMedia("(hover: none), (pointer: coarse)").matches?1:"ontouchstart"in Te||0=i,n=Math.abs(t)>=i;k&&(r||n)&&k(se,e,t,me,ye),r&&(m&&0Math.abs(t)?"x":"y",oe=!0),"y"!==ae&&(me[2]+=e,se._vx.update(e,!0)),"x"!==ae&&(ye[2]+=t,se._vy.update(t,!0)),n?ee=ee||requestAnimationFrame(kf):kf()}function nf(e){if(!hf(e,1)){var t=(e=N(e,s)).clientX,r=e.clientY,n=t-se.x,i=r-se.y,o=se.isDragging;se.x=t,se.y=r,(o||(n||i)&&(Math.abs(se.startX-t)>=a||Math.abs(se.startY-r)>=a))&&(re=o?2:1,o||(se.isDragging=!0),mf(n,i))}}function qf(e){return e.touches&&1=e)return a[n];return a[n-1]}for(n=a.length,e+=r;n--;)if(a[n]<=e)return a[n];return a[0]}:function(e,t,r){void 0===r&&(r=.001);var n=o(e);return!t||Math.abs(n-e)r&&(n*=t/100),e=e.substr(0,r-1)),e=n+(e in q?q[e]*t:~e.indexOf("%")?parseFloat(e)*t/100:parseFloat(e)||0)}return e}function Eb(e,t,r,n,i,o,a,s){var l=i.startColor,c=i.endColor,u=i.fontSize,f=i.indent,d=i.fontWeight,p=Fe.createElement("div"),g=Ma(r)||"fixed"===z(r,"pinType"),h=-1!==e.indexOf("scroller"),v=g?We:r,b=-1!==e.indexOf("start"),m=b?l:c,y="border-color:"+m+";font-size:"+u+";color:"+m+";font-weight:"+d+";pointer-events:none;white-space:nowrap;font-family:sans-serif,Arial;z-index:1000;padding:4px 8px;border-width:0;border-style:solid;";return y+="position:"+((h||s)&&g?"fixed;":"absolute;"),!h&&!s&&g||(y+=(n===qe?I:Y)+":"+(o+parseFloat(f))+"px;"),a&&(y+="box-sizing:border-box;text-align:left;width:"+a.offsetWidth+"px;"),p._isStart=b,p.setAttribute("class","gsap-marker-"+e+(t?" marker-"+t:"")),p.style.cssText=y,p.innerText=t||0===t?e+"-"+t:e,v.children[0]?v.insertBefore(p,v.children[0]):v.appendChild(p),p._offset=p["offset"+n.op.d2],X(p,0,n,b),p}function Jb(){return 34We.clientWidth)||(ze.cache++,v?T=T||requestAnimationFrame(Z):Z(),st||V("scrollStart"),st=at())}function Lb(){y=Xe.innerWidth,m=Xe.innerHeight}function Mb(e){ze.cache++,!0!==e&&(Ke||h||Fe.fullscreenElement||Fe.webkitFullscreenElement||b&&y===Xe.innerWidth&&!(Math.abs(Xe.innerHeight-m)>.25*Xe.innerHeight))||c.restart(!0)}function Pb(){return yb(ne,"scrollEnd",Pb)||Mt(!0)}function Sb(e){for(var t=0;tt,n=e._startClamp&&e.start>=t;(r||n)&&e.setPositions(n?t-1:e.start,r?Math.max(n?t:e.start+1,t):e.end,!0)}),$b(!1),et=0,r.forEach(function(e){return e&&e.render&&e.render(-1)}),ze.forEach(function(e){Ua(e)&&(e.smooth&&requestAnimationFrame(function(){return e.target.style.scrollBehavior="smooth"}),e.rec&&e(e.rec))}),Ub(_,1),c.pause(),Ct++,Z(rt=2),kt.forEach(function(e){return Ua(e.vars.onRefresh)&&e.vars.onRefresh(e)}),rt=ne.isRefreshing=!1,V("refresh")}else xb(ne,"scrollEnd",Pb)},j=0,Et=1,Z=function _updateAll(e){if(2===e||!rt&&!k){ne.isUpdating=!0,it&&it.update(0);var t=kt.length,r=at(),n=50<=r-D,i=t&&kt[0].scroll();if(Et=i=Ra(be,he)){if(oe&&Ae()&&!de)for(o=oe.parentNode;o&&o!==We;)o._pinOffset&&(B-=o._pinOffset,I-=o._pinOffset),o=o.parentNode}else i=nb(ae),s=he===qe,a=Ae(),Q=parseFloat(K(he.a))+w,!y&&1=I})},ke.update=function(e,t,r){if(!de||r||e){var n,i,o,a,s,l,c,u=!0===rt?re:ke.scroll(),f=e?0:(u-B)/F,d=f<0?0:1u+(u-D)/(at()-Ve)*E&&(d=.9999)),d!==p&&ke.enabled){if(a=(s=(n=ke.isActive=!!d&&d<1)!=(!!p&&p<1))||!!d!=!!p,ke.direction=p=Ra(be,he),fe)if(e||!n&&!l)pc(ae,V);else{var g=_t(ae,!0),h=u-B;pc(ae,We,g.top+(he===qe?h:0)+xt,g.left+(he===qe?0:h)+xt)}Pt(n||l?W:G),$&&d<1&&n||b(Q+(1!==d||l?0:j))}}else b(Ja(Q+j*d));!ue||A.tween||Ke||ot||te.restart(!0),T&&(s||ce&&d&&(d<1||!tt))&&Je(T.targets).forEach(function(e){return e.classList[n||ce?"add":"remove"](T.className)}),!k||ve||e||k(ke),a&&!Ke?(ve&&(c&&("complete"===o?O.pause().totalProgress(1):"reset"===o?O.restart(!0).pause():"restart"===o?O.restart(!0):O[o]()),k&&k(ke)),!s&&tt||(C&&s&&Ya(ke,C),xe[i]&&Ya(ke,xe[i]),ce&&(1===d?ke.kill(!1,1):xe[i]=0),s||xe[i=1===d?1:3]&&Ya(ke,xe[i])),pe&&!n&&Math.abs(ke.getVelocity())>(Va(pe)?pe:2500)&&(Xa(ke.callbackAnimation),ee?ee.progress(1):Xa(O,"reverse"===o?1:!d,1))):ve&&k&&!Ke&&k(ke)}if(x){var v=de?u/de.duration()*(de._caScrollDist||0):u;y(v+(q._isFlipped?1:0)),x(v)}S&&S(-u/de.duration()*(de._caScrollDist||0))}},ke.enable=function(e,t){ke.enabled||(ke.enabled=!0,xb(be,"resize",Mb),me||xb(be,"scroll",Kb),Te&&xb(ScrollTrigger,"refreshInit",Te),!1!==e&&(ke.progress=Oe=0,R=D=Ee=Ae()),!1!==t&&ke.refresh())},ke.getTween=function(e){return e&&A?A.tween:ee},ke.setPositions=function(e,t,r,n){if(de){var i=de.scrollTrigger,o=de.duration(),a=i.end-i.start;e=i.start+a*e/o,t=i.start+a*t/o}ke.refresh(!1,!1,{start:Ea(e,r&&!!ke._startClamp),end:Ea(t,r&&!!ke._endClamp)},n),ke.update()},ke.adjustPinSpacing=function(e){if(Z&&e){var t=Z.indexOf(he.d)+1;Z[t]=parseFloat(Z[t])+e+xt,Z[1]=parseFloat(Z[1])+e+xt,Pt(Z)}},ke.disable=function(e,t){if(ke.enabled&&(!1!==e&&ke.revert(!0,!0),ke.enabled=ke.isActive=!1,t||ee&&ee.pause(),re=0,n&&(n.uncache=1),Te&&yb(ScrollTrigger,"refreshInit",Te),te&&(te.pause(),A.tween&&A.tween.kill()&&(A.tween=0)),!me)){for(var r=kt.length;r--;)if(kt[r].scroller===be&&kt[r]!==ke)return;yb(be,"resize",Mb),me||yb(be,"scroll",Kb)}},ke.kill=function(e,t){ke.disable(e,t),ee&&!t&&ee.kill(),a&&delete Tt[a];var r=kt.indexOf(ke);0<=r&&kt.splice(r,1),r===je&&0o&&(b()>o?a.progress(1)&&b(o):a.resetTo("scrollY",o))}Wa(e)||(e={}),e.preventDefault=e.isNormalizer=e.allowClicks=!0,e.type||(e.type="wheel,touch"),e.debounce=!!e.debounce,e.id=e.id||"normalizer";var n,o,l,i,a,c,u,s,f=e.normalizeScrollX,t=e.momentum,r=e.allowNestedScroll,d=e.onRelease,p=J(e.target)||Ue,g=Ne.core.globals().ScrollSmoother,h=g&&g.get(),v=R&&(e.content&&J(e.content)||h&&!1!==e.content&&!h.smooth()&&h.content()),b=L(p,qe),m=L(p,He),y=1,x=(E.isTouch&&Xe.visualViewport?Xe.visualViewport.scale*Xe.visualViewport.width:Xe.outerWidth)/Xe.innerWidth,_=0,w=Ua(t)?function(){return t(n)}:function(){return t||2.8},S=yc(p,e.type,!0,r),k=Ia,T=Ia;return v&&Ne.set(v,{y:"+=0"}),e.ignoreCheck=function(e){return R&&"touchmove"===e.type&&function ignoreDrag(){if(i){requestAnimationFrame(Gq);var e=Ja(n.deltaY/2),t=T(b.v-e);if(v&&t!==b.v+b.offset){b.offset=t-b.v;var r=Ja((parseFloat(v&&v._gsap.y)||0)-b.offset);v.style.transform="matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, "+r+", 0, 1)",v._gsap.y=r+"px",b.cacheID=ze.cache,Z()}return!0}b.offset&&Kq(),i=!0}()||1.05=o||o-1<=r)&&Ne.to({},{onUpdate:Qq,duration:i})}else s.restart(!0);d&&d(e)},e.onWheel=function(){a._ts&&a.pause(),1e3G||U.register(window.gsap),V=typeof Intl!="undefined"?new Intl.Segmenter:0,P=e=>typeof e=="string"?P(document.querySelectorAll(e)):"length"in e?Array.from(e):[e],X=e=>P(e).filter(t=>t instanceof HTMLElement),J=[],K=function(){},oe=/\s+/g,Y=new RegExp("\\p{RI}\\p{RI}|\\p{Emoji}(\\p{EMod}|\\u{FE0F}\\u{20E3}?|[\\u{E0020}-\\u{E007E}]+\\u{E007F})?(\\u{200D}\\p{Emoji}(\\p{EMod}|\\u{FE0F}\\u{20E3}?|[\\u{E0020}-\\u{E007E}]+\\u{E007F})?)*|.","gu"),Z={left:0,top:0,width:0,height:0},ee=(e,t)=>{if(t){let s=new Set(e.join("").match(t)||J),i=e.length,o,c,n,a;if(s.size)for(;--i>-1;){c=e[i];for(n of s)if(n.startsWith(c)&&n.length>c.length){for(o=0,a=c;n.startsWith(a+=e[i+ ++o])&&a.lengthwindow.getComputedStyle(e).display==="inline"&&(e.style.display="inline-block"),z=(e,t,s)=>t.insertBefore(typeof e=="string"?document.createTextNode(e):e,s),Q=(e,t,s)=>{let i=t[e+"sClass"]||"",{tag:o="div",aria:c="auto",propIndex:n=!1}=t,a=e==="line"?"block":"inline-block",h=i.indexOf("++")>-1,b=x=>{let g=document.createElement(o),C=s.length+1;return i&&(g.className=i+(h?" "+i+C:"")),n&&g.style.setProperty("--"+e,C+""),c!=="none"&&g.setAttribute("aria-hidden","true"),o!=="span"&&(g.style.position="relative",g.style.display=a),g.textContent=x,s.push(g),g};return h&&(i=i.replace("++","")),b.collection=s,b},ae=(e,t,s,i)=>{let o=Q("line",s,i),c=window.getComputedStyle(e).textAlign||"left";return(n,a)=>{let h=o("");for(h.style.textAlign=c,e.insertBefore(h,t[n]);n{var x;let g=Array.from(e.childNodes),C=0,{wordDelimiter:R,reduceWhiteSpace:L=!0,prepareText:$}=t,q=e.getBoundingClientRect(),j=q,D=!L&&window.getComputedStyle(e).whiteSpace.substring(0,3)==="pre",E=0,v=s.collection,r,f,H,l,m,y,I,d,u,W,S,O,T,F,w,p,k,N;for(typeof R=="object"?(H=R.delimiter||R,f=R.replaceWith||""):f=R===""?"":R||" ",r=f!==" ";C-1?(y=v[v.length-1],y.appendChild(document.createTextNode(i?"":p))):(y=s(i?"":p),z(y,e,l),E&&u===1&&!I&&y.insertBefore(E,y.firstChild)),i)for(S=V?ee([...V.segment(p)].map(M=>M.segment),h):p.match(a)||J,N=0;Nj.top&&W.left<=j.left){for(O=e.cloneNode(),T=e.childNodes[0];T&&T!==y;)F=T,T=T.nextSibling,O.appendChild(F);e.parentNode.insertBefore(O,e),o&&te(O)}j=W}(u=m.length?" ":r&&p.slice(-1)===" "?" "+f:f,e,l)}e.removeChild(l),E=0}else l.nodeType===1&&(n&&n.indexOf(l)>-1?(v.indexOf(l.previousSibling)>-1&&v[v.length-1].appendChild(l),E=l):(ie(l,t,s,i,o,c,n,a,h,!0),E=0),o&&te(l))};const ne=class se{constructor(t,s){this.isSplit=!1,re(),this.elements=X(t),this.chars=[],this.words=[],this.lines=[],this.masks=[],this.vars=s,this._split=()=>this.isSplit&&this.split(this.vars);let i=[],o,c=()=>{let n=i.length,a;for(;n--;){a=i[n];let h=a.element.offsetWidth;if(h!==a.width){a.width=h,this._split();return}}};this._data={orig:i,obs:typeof ResizeObserver!="undefined"&&new ResizeObserver(()=>{clearTimeout(o),o=setTimeout(c,200)})},K(this),this.split(s)}split(t){this.isSplit&&this.revert(),this.vars=t=t||this.vars||{};let{type:s="chars,words,lines",aria:i="auto",deepSlice:o=!0,smartWrap:c,onSplit:n,autoSplit:a=!1,specialChars:h,mask:b}=this.vars,x=s.indexOf("lines")>-1,g=s.indexOf("chars")>-1,C=s.indexOf("words")>-1,R=g&&!C&&!x,L=h&&("push"in h?new RegExp("(?:"+h.join("|")+")","gu"):h),$=L?new RegExp(L.source+"|"+Y.source,"gu"):Y,q=!!t.ignore&&X(t.ignore),{orig:j,animTime:D,obs:E}=this._data,v;return(g||C||x)&&(this.elements.forEach((r,f)=>{j[f]={element:r,html:r.innerHTML,ariaL:r.getAttribute("aria-label"),ariaH:r.getAttribute("aria-hidden")},i==="auto"?r.setAttribute("aria-label",(r.textContent||"").trim()):i==="hidden"&&r.setAttribute("aria-hidden","true");let H=[],l=[],m=[],y=g?Q("char",t,H):null,I=Q("word",t,l),d,u,W,S;if(ie(r,t,I,y,R,o&&(x||R),q,$,L,!1),x){let O=P(r.childNodes),T=ae(r,O,t,m),F,w=[],p=0,k=O.map(M=>M.nodeType===1?M.getBoundingClientRect():Z),N=Z;for(d=0;dN.top&&k[d].left<=N.left&&(T(p,d),p=d),N=k[d]));p{var le;return(le=M.parentNode)==null?void 0:le.removeChild(M)})}if(!C){for(d=0;d{let f=r.cloneNode();return r.replaceWith(f),f.appendChild(r),r.className&&(f.className=r.className.replace(/(\b\w+\b)/g,"$1-mask")),f.style.overflow="clip",f}))),this.isSplit=!0,_&&(a?_.addEventListener("loadingdone",this._split):_.status==="loading"&&console.warn("SplitText called before fonts loaded")),(v=n&&n(this))&&v.totalTime&&(this._data.anim=D?v.totalTime(D):v),x&&a&&this.elements.forEach((r,f)=>{j[f].width=r.offsetWidth,E&&E.observe(r)}),this}revert(){var t,s;let{orig:i,anim:o,obs:c}=this._data;return c&&c.disconnect(),i.forEach(({element:n,html:a,ariaL:h,ariaH:b})=>{n.innerHTML=a,h?n.setAttribute("aria-label",h):n.removeAttribute("aria-label"),b?n.setAttribute("aria-hidden",b):n.removeAttribute("aria-hidden")}),this.chars.length=this.words.length=this.lines.length=i.length=this.masks.length=0,this.isSplit=!1,_==null||_.removeEventListener("loadingdone",this._split),o&&(this._data.animTime=o.totalTime(),o.revert()),(s=(t=this.vars).onRevert)==null||s.call(t,this),this}static create(t,s){return new se(t,s)}static register(t){A=A||t||window.gsap,A&&(P=A.utils.toArray,K=A.core.context||K),!G&&window.innerWidth>0&&(_=document.fonts,G=!0)}};ne.version="3.13.0";let U=ne;B.SplitText=U,B.default=U,Object.defineProperty(B,"__esModule",{value:!0})});
12 |
--------------------------------------------------------------------------------
/js/gsap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * GSAP 3.13.0
3 | * https://gsap.com
4 | *
5 | * @license Copyright 2025, GreenSock. All rights reserved.
6 | * Subject to the terms at https://gsap.com/standard-license.
7 | * @author: Jack Doyle, jack@greensock.com
8 | */
9 |
10 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function r(t){return"string"==typeof t}function s(t){return"function"==typeof t}function t(t){return"number"==typeof t}function u(t){return void 0===t}function v(t){return"object"==typeof t}function w(t){return!1!==t}function x(){return"undefined"!=typeof window}function y(t){return s(t)||r(t)}function P(t){return(i=yt(t,ot))&&ze}function Q(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function R(t,e){return!e&&console.warn(t)}function S(t,e){return t&&(ot[t]=e)&&i&&(i[t]=e)||ot}function T(){return 0}function ea(t){var e,r,i=t[0];if(v(i)||s(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=gt.length;r--&&!gt[r].targetTest(i););e=gt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Zt(t[r],e)))||t.splice(r,1);return t}function fa(t){return t._gsap||ea(Ot(t))[0]._gsap}function ga(t,e,r){return(r=t[e])&&s(r)?t[e]():u(r)&&t.getAttribute&&t.getAttribute(e)||r}function ha(t,e){return(t=t.split(",")).forEach(e)||t}function ia(t){return Math.round(1e5*t)/1e5||0}function ja(t){return Math.round(1e7*t)/1e7||0}function ka(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function la(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++ia;)s=s._prev;return s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[i]=e,e._prev=s,e.parent=e._dp=t,e}function za(t,e,r,i){void 0===r&&(r="_first"),void 0===i&&(i="_last");var n=e._prev,a=e._next;n?n._next=a:t[r]===e&&(t[r]=a),a?a._prev=n:t[i]===e&&(t[i]=n),e._next=e._prev=e.parent=null}function Aa(t,e){t.parent&&(!e||t.parent.autoRemoveChildren)&&t.parent.remove&&t.parent.remove(t),t._act=0}function Ba(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var r=t;r;)r._dirty=1,r=r.parent;return t}function Da(t,e,r,i){return t._startAt&&(L?t._startAt.revert(ht):t.vars.immediateRender&&!t.vars.autoRevert||t._startAt.render(e,!0,i))}function Fa(t){return t._repeat?Tt(t._tTime,t=t.duration()+t._rDelay)*t:0}function Ha(t,e){return(t-e._start)*e._ts+(0<=e._ts?0:e._dirty?e.totalDuration():e._tDur)}function Ia(t){return t._end=ja(t._start+(t._tDur/Math.abs(t._ts||t._rts||U)||0))}function Ja(t,e){var r=t._dp;return r&&r.smoothChildTiming&&t._ts&&(t._start=ja(r._time-(0U)&&e.render(r,!0)),Ba(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur(n=Math.abs(n))&&(a=i,o=n);return a}function ub(t){return Aa(t),t.scrollTrigger&&t.scrollTrigger.kill(!!L),t.progress()<1&&Pt(t,"onInterrupt"),t}function xb(t){if(t)if(t=!t.name&&t.default||t,x()||t.headless){var e=t.name,r=s(t),i=e&&!r&&t.init?function(){this._props=[]}:t,n={init:T,render:ue,add:Vt,kill:de,modifier:he,rawVars:0},a={targetTest:0,get:0,getSetter:ie,aliases:{},register:0};if(Ft(),t!==i){if(pt[e])return;ra(i,ra(va(t,n),a)),yt(i.prototype,yt(n,va(t,a))),pt[i.prop=e]=i,t.targetTest&&(gt.push(i),ft[e]=1),e=("css"===e?"CSS":e.charAt(0).toUpperCase()+e.substr(1))+"Plugin"}S(e,i),t.register&&t.register(ze,i,ge)}else Ct.push(t)}function Ab(t,e,r){return(6*(t+=t<0?1:1>16,e>>8&St,e&St]:0:Dt.black;if(!p){if(","===e.substr(-1)&&(e=e.substr(0,e.length-1)),Dt[e])p=Dt[e];else if("#"===e.charAt(0)){if(e.length<6&&(e="#"+(n=e.charAt(1))+n+(a=e.charAt(2))+a+(s=e.charAt(3))+s+(5===e.length?e.charAt(4)+e.charAt(4):"")),9===e.length)return[(p=parseInt(e.substr(1,6),16))>>16,p>>8&St,p&St,parseInt(e.substr(7),16)/255];p=[(e=parseInt(e.substr(1),16))>>16,e>>8&St,e&St]}else if("hsl"===e.substr(0,3))if(p=c=e.match(tt),r){if(~e.indexOf("="))return p=e.match(et),i&&p.length<4&&(p[3]=1),p}else o=+p[0]%360/360,u=p[1]/100,n=2*(h=p[2]/100)-(a=h<=.5?h*(u+1):h+u-h*u),3=N?u.endTime(!1):t._dur;return r(e)&&(isNaN(e)||e in o)?(a=e.charAt(0),s="%"===e.substr(-1),n=e.indexOf("="),"<"===a||">"===a?(0<=n&&(e=e.replace(/=/,"")),("<"===a?u._start:u.endTime(0<=u._repeat))+(parseFloat(e.substr(1))||0)*(s?(n<0?u:i).totalDuration()/100:1)):n<0?(e in o||(o[e]=h),o[e]):(a=parseFloat(e.charAt(n-1)+e.substr(n+1)),s&&i&&(a=a/100*($(i)?i[0]:i).totalDuration()),1=r&&te)return i;i=i._next}else for(i=t._last;i&&i._start>=r;){if("isPause"===i.data&&i._start=n._start)&&n._ts&&h!==n){if(n.parent!==this)return this.render(t,e,r);if(n.render(0=this.totalDuration()||!v&&_)&&(f!==this._start&&Math.abs(l)===Math.abs(this._ts)||this._lock||(!t&&g||!(v===m&&0=i&&(a instanceof Jt?e&&n.push(a):(r&&n.push(a),t&&n.push.apply(n,a.getChildren(!0,e,r)))),a=a._next;return n},e.getById=function getById(t){for(var e=this.getChildren(1,1,1),r=e.length;r--;)if(e[r].vars.id===t)return e[r]},e.remove=function remove(t){return r(t)?this.removeLabel(t):s(t)?this.killTweensOf(t):(t.parent===this&&za(this,t),t===this._recent&&(this._recent=this._last),Ba(this))},e.totalTime=function totalTime(t,e){return arguments.length?(this._forcing=1,!this._dp&&this._ts&&(this._start=ja(Rt.time-(0r:!r||s.isActive())&&n.push(s):(i=s.getTweensOf(a,r)).length&&n.push.apply(n,i),s=s._next;return n},e.tweenTo=function tweenTo(t,e){e=e||{};var r,i=this,n=xt(i,t),a=e.startAt,s=e.onStart,o=e.onStartParams,u=e.immediateRender,h=Jt.to(i,ra({ease:e.ease||"none",lazy:!1,immediateRender:!1,time:n,overwrite:"auto",duration:e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale())||U,onStart:function onStart(){if(i.pause(),!r){var t=e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale());h._dur!==t&&Sa(h,t,0,1).render(h._time,!0,!0),r=1}s&&s.apply(h,o||[])}},e));return u?h.render(0):h},e.tweenFromTo=function tweenFromTo(t,e,r){return this.tweenTo(e,ra({startAt:{time:xt(this,t)}},r))},e.recent=function recent(){return this._recent},e.nextLabel=function nextLabel(t){return void 0===t&&(t=this._time),sb(this,xt(this,t))},e.previousLabel=function previousLabel(t){return void 0===t&&(t=this._time),sb(this,xt(this,t),1)},e.currentLabel=function currentLabel(t){return arguments.length?this.seek(t,!0):this.previousLabel(this._time+U)},e.shiftChildren=function shiftChildren(t,e,r){void 0===r&&(r=0);for(var i,n=this._first,a=this.labels;n;)n._start>=r&&(n._start+=t,n._end+=t),n=n._next;if(e)for(i in a)a[i]>=r&&(a[i]+=t);return Ba(this)},e.invalidate=function invalidate(t){var e=this._first;for(this._lock=0;e;)e.invalidate(t),e=e._next;return i.prototype.invalidate.call(this,t)},e.clear=function clear(t){void 0===t&&(t=!0);for(var e,r=this._first;r;)e=r._next,this.remove(r),r=e;return this._dp&&(this._time=this._tTime=this._pTime=0),t&&(this.labels={}),Ba(this)},e.totalDuration=function totalDuration(t){var e,r,i,n=0,a=this,s=a._last,o=N;if(arguments.length)return a.timeScale((a._repeat<0?a.duration():a.totalDuration())/(a.reversed()?-t:t));if(a._dirty){for(i=a.parent;s;)e=s._prev,s._dirty&&s.totalDuration(),o<(r=s._start)&&a._sort&&s._ts&&!a._lock?(a._lock=1,La(a,s,r-s._delay,1)._lock=0):o=r,r<0&&s._ts&&(n-=r,(!i&&!a._dp||i&&i.smoothChildTiming)&&(a._start+=r/a._ts,a._time-=r,a._tTime-=r),a.shiftChildren(-r,!1,-Infinity),o=0),s._end>n&&s._ts&&(n=s._end),s=e;Sa(a,a===I&&a._time>n?a._time:n,1,1),a._dirty=0}return a._tDur},Timeline.updateRoot=function updateRoot(t){if(I._ts&&(oa(I,Ha(t,I)),f=Rt.frame),Rt.frame>=mt){mt+=X.autoSleep||120;var e=I._first;if((!e||!e._ts)&&X.autoSleep&&Rt._listeners.length<2){for(;e&&!e._ts;)e=e._next;e||Rt.sleep()}}},Timeline}(Nt);ra(Qt.prototype,{_lock:0,_hasPause:0,_forcing:0});function bc(t,e,i,n,a,o){var u,h,l,f;if(pt[t]&&!1!==(u=new pt[t]).init(a,u.rawVars?e[t]:function _processVars(t,e,i,n,a){if(s(t)&&(t=Gt(t,a,e,i,n)),!v(t)||t.style&&t.nodeType||$(t)||J(t))return r(t)?Gt(t,a,e,i,n):t;var o,u={};for(o in t)u[o]=Gt(t[o],a,e,i,n);return u}(e[t],n,a,o,i),i,n,o)&&(i._pt=h=new ge(i._pt,a,t,0,1,u.render,u,0,u.priority),i!==d))for(l=i._ptLookup[i._targets.indexOf(a)],f=u._props.length;f--;)l[u._props[f]]=h;return u}function hc(t,r,e,i){var n,a,s=r.ease||i||"power1.inOut";if($(r))a=e[t]||(e[t]=[]),r.forEach(function(t,e){return a.push({t:e/(r.length-1)*100,v:t,e:s})});else for(n in r)a=e[n]||(e[n]=[]),"ease"===n||a.push({t:parseFloat(t),v:r[n],e:s})}var Ut,qt,Vt=function _addPropTween(t,e,i,n,a,o,u,h,l,f){s(n)&&(n=n(a||0,t,o));var d,c=t[e],p="get"!==i?i:s(c)?l?t[e.indexOf("set")||!s(t["get"+e.substr(3)])?e:"get"+e.substr(3)](l):t[e]():c,_=s(c)?l?re:te:$t;if(r(n)&&(~n.indexOf("random(")&&(n=pb(n)),"="===n.charAt(1)&&(!(d=ka(p,n)+(Za(p)||0))&&0!==d||(n=d))),!f||p!==n||qt)return isNaN(p*n)||""===n?(c||e in t||Q(e,n),function _addComplexStringPropTween(t,e,r,i,n,a,s){var o,u,h,l,f,d,c,p,_=new ge(this._pt,t,e,0,1,oe,null,n),m=0,g=0;for(_.b=r,_.e=i,r+="",(c=~(i+="").indexOf("random("))&&(i=pb(i)),a&&(a(p=[r,i],t,e),r=p[0],i=p[1]),u=r.match(it)||[];o=it.exec(i);)l=o[0],f=i.substring(m,o.index),h?h=(h+1)%5:"rgba("===f.substr(-5)&&(h=1),l!==u[g++]&&(d=parseFloat(u[g-1])||0,_._pt={_next:_._pt,p:f||1===g?f:",",s:d,c:"="===l.charAt(1)?ka(d,l)-d:parseFloat(l)-d,m:h&&h<4?Math.round:0},m=it.lastIndex);return _.c=m")}),s.duration();else{for(l in u={},x)"ease"===l||"easeEach"===l||hc(l,x[l],u,x.easeEach);for(l in u)for(C=u[l].sort(function(t,e){return t.t-e.t}),o=z=0;o=t._tDur||e<0)&&t.ratio===u&&(u&&Aa(t,1),r||L||(Pt(t,u?"onComplete":"onReverseComplete",!0),t._prom&&t._prom()))}else t._zTime||(t._zTime=e)}(this,t,e,r);return this},e.targets=function targets(){return this._targets},e.invalidate=function invalidate(t){return t&&this.vars.runBackwards||(this._startAt=0),this._pt=this._op=this._onUpdate=this._lazy=this.ratio=0,this._ptLookup=[],this.timeline&&this.timeline.invalidate(t),E.prototype.invalidate.call(this,t)},e.resetTo=function resetTo(t,e,r,i,n){c||Rt.wake(),this._ts||this.play();var a,s=Math.min(this._dur,(this._dp._time-this._start)*this._ts);return this._initted||Wt(this,s),a=this._ease(s/this._dur),function _updatePropTweens(t,e,r,i,n,a,s,o){var u,h,l,f,d=(t._pt&&t._ptCache||(t._ptCache={}))[e];if(!d)for(d=t._ptCache[e]=[],l=t._ptLookup,f=t._targets.length;f--;){if((u=l[f][e])&&u.d&&u.d._pt)for(u=u.d._pt;u&&u.p!==e&&u.fp!==e;)u=u._next;if(!u)return qt=1,t.vars[e]="+=0",Wt(t,s),qt=0,o?R(e+" not eligible for reset"):1;d.push(u)}for(f=d.length;f--;)(u=(h=d[f])._pt||h).s=!i&&0!==i||n?u.s+(i||0)+a*u.c:i,u.c=r-u.s,h.e&&(h.e=ia(r)+Za(h.e)),h.b&&(h.b=u.s+Za(h.b))}(this,t,e,r,i,a,s,n)?this.resetTo(t,e,r,i,1):(Ja(this,0),this.parent||ya(this._dp,this,"_first","_last",this._dp._sort?"_start":0),this.render(0))},e.kill=function kill(t,e){if(void 0===e&&(e="all"),!(t||e&&"all"!==e))return this._lazy=this._pt=0,this.parent?ub(this):this.scrollTrigger&&this.scrollTrigger.kill(!!L),this;if(this.timeline){var i=this.timeline.totalDuration();return this.timeline.killTweensOf(t,e,Ut&&!0!==Ut.vars.overwrite)._first||ub(this),this.parent&&i!==this.timeline.totalDuration()&&Sa(this,this._dur*this.timeline._tDur/i,0,1),this}var n,a,s,o,u,h,l,f=this._targets,d=t?Ot(t):f,c=this._ptLookup,p=this._pt;if((!e||"all"===e)&&function _arraysMatch(t,e){for(var r=t.length,i=r===e.length;i&&r--&&t[r]===e[r];);return r<0}(f,d))return"all"===e&&(this._pt=0),ub(this);for(n=this._op=this._op||[],"all"!==e&&(r(e)&&(u={},ha(e,function(t){return u[t]=1}),e=u),e=function _addAliasesToVars(t,e){var r,i,n,a,s=t[0]?fa(t[0]).harness:0,o=s&&s.aliases;if(!o)return e;for(i in r=yt({},e),o)if(i in r)for(n=(a=o[i].split(",")).length;n--;)r[a[n]]=r[i];return r}(f,e)),l=f.length;l--;)if(~d.indexOf(f[l]))for(u in a=c[l],"all"===e?(n[l]=e,o=a,s={}):(s=n[l]=n[l]||{},o=e),o)(h=a&&a[u])&&("kill"in h.d&&!0!==h.d.kill(u)||za(this,h,"_pt"),delete a[u]),"all"!==s&&(s[u]=1);return this._initted&&!this._pt&&p&&ub(this),this},Tween.to=function to(t,e,r){return new Tween(t,e,r)},Tween.from=function from(t,e){return Wa(1,arguments)},Tween.delayedCall=function delayedCall(t,e,r,i){return new Tween(e,0,{immediateRender:!1,lazy:!1,overwrite:!1,delay:t,onComplete:e,onReverseComplete:e,onCompleteParams:r,onReverseCompleteParams:r,callbackScope:i})},Tween.fromTo=function fromTo(t,e,r){return Wa(2,arguments)},Tween.set=function set(t,e){return e.duration=0,e.repeatDelay||(e.repeat=0),new Tween(t,e)},Tween.killTweensOf=function killTweensOf(t,e,r){return I.killTweensOf(t,e,r)},Tween}(Nt);ra(Jt.prototype,{_targets:[],_lazy:0,_startAt:0,_op:0,_onInit:0}),ha("staggerTo,staggerFrom,staggerFromTo",function(r){Jt[r]=function(){var t=new Qt,e=kt.call(arguments,0);return e.splice("staggerFromTo"===r?5:4,0,0),t[r].apply(t,e)}});function pc(t,e,r){return t.setAttribute(e,r)}function xc(t,e,r,i){i.mSet(t,e,i.m.call(i.tween,r,i.mt),i)}var $t=function _setterPlain(t,e,r){return t[e]=r},te=function _setterFunc(t,e,r){return t[e](r)},re=function _setterFuncWithParam(t,e,r,i){return t[e](i.fp,r)},ie=function _getSetter(t,e){return s(t[e])?te:u(t[e])&&t.setAttribute?pc:$t},ne=function _renderPlain(t,e){return e.set(e.t,e.p,Math.round(1e6*(e.s+e.c*t))/1e6,e)},se=function _renderBoolean(t,e){return e.set(e.t,e.p,!!(e.s+e.c*t),e)},oe=function _renderComplexString(t,e){var r=e._pt,i="";if(!t&&e.b)i=e.b;else if(1===t&&e.e)i=e.e;else{for(;r;)i=r.p+(r.m?r.m(r.s+r.c*t):Math.round(1e4*(r.s+r.c*t))/1e4)+i,r=r._next;i+=e.c}e.set(e.t,e.p,i,e)},ue=function _renderPropTweens(t,e){for(var r=e._pt;r;)r.r(t,r.d),r=r._next},he=function _addPluginModifier(t,e,r,i){for(var n,a=this._pt;a;)n=a._next,a.p===i&&a.modifier(t,e,r),a=n},de=function _killPropTweensOf(t){for(var e,r,i=this._pt;i;)r=i._next,i.p===t&&!i.op||i.op===t?za(this,i,"_pt"):i.dep||(e=1),i=r;return!e},_e=function _sortPropTweensByPriority(t){for(var e,r,i,n,a=t._pt;a;){for(e=a._next,r=i;r&&r.pr>a.pr;)r=r._next;(a._prev=r?r._prev:n)?a._prev._next=a:i=a,(a._next=r)?r._prev=a:n=a,a=e}t._pt=i},ge=(PropTween.prototype.modifier=function modifier(t,e,r){this.mSet=this.mSet||this.set,this.set=xc,this.m=t,this.mt=r,this.tween=e},PropTween);function PropTween(t,e,r,i,n,a,s,o,u){this.t=e,this.s=i,this.c=n,this.p=r,this.r=a||ne,this.d=s||this,this.set=o||$t,this.pr=u||0,(this._next=t)&&(t._prev=this)}ha(vt+"parent,duration,ease,delay,overwrite,runBackwards,startAt,yoyo,immediateRender,repeat,repeatDelay,data,paused,reversed,lazy,callbackScope,stringFilter,id,yoyoEase,stagger,inherit,repeatRefresh,keyframes,autoRevert,scrollTrigger",function(t){return ft[t]=1}),ot.TweenMax=ot.TweenLite=Jt,ot.TimelineLite=ot.TimelineMax=Qt,I=new Qt({sortChildren:!1,defaults:Z,autoRemoveChildren:!0,id:"root",smoothChildTiming:!0}),X.stringFilter=Gb;function Fc(t){return(be[t]||Me).map(function(t){return t()})}function Gc(){var t=Date.now(),o=[];2{setTimeout((()=>{this.progress(t,e,i)}))};this.images.forEach((function(e){e.once("progress",t),e.check()}))},n.prototype.progress=function(t,e,i){this.progressedCount++,this.hasAnyBroken=this.hasAnyBroken||!t.isLoaded,this.emitEvent("progress",[this,t,e]),this.jqDeferred&&this.jqDeferred.notify&&this.jqDeferred.notify(this,t),this.progressedCount===this.images.length&&this.complete(),this.options.debug&&s&&s.log(`progress: ${i}`,t,e)},n.prototype.complete=function(){let t=this.hasAnyBroken?"fail":"done";if(this.isComplete=!0,this.emitEvent(t,[this]),this.emitEvent("always",[this]),this.jqDeferred){let t=this.hasAnyBroken?"reject":"resolve";this.jqDeferred[t](this)}},h.prototype=Object.create(e.prototype),h.prototype.check=function(){this.getIsImageComplete()?this.confirm(0!==this.img.naturalWidth,"naturalWidth"):(this.proxyImage=new Image,this.img.crossOrigin&&(this.proxyImage.crossOrigin=this.img.crossOrigin),this.proxyImage.addEventListener("load",this),this.proxyImage.addEventListener("error",this),this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.proxyImage.src=this.img.currentSrc||this.img.src)},h.prototype.getIsImageComplete=function(){return this.img.complete&&this.img.naturalWidth},h.prototype.confirm=function(t,e){this.isLoaded=t;let{parentNode:i}=this.img,s="PICTURE"===i.nodeName?i:this.img;this.emitEvent("progress",[this,s,e])},h.prototype.handleEvent=function(t){let e="on"+t.type;this[e]&&this[e](t)},h.prototype.onload=function(){this.confirm(!0,"onload"),this.unbindEvents()},h.prototype.onerror=function(){this.confirm(!1,"onerror"),this.unbindEvents()},h.prototype.unbindEvents=function(){this.proxyImage.removeEventListener("load",this),this.proxyImage.removeEventListener("error",this),this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype=Object.create(h.prototype),d.prototype.check=function(){this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.img.src=this.url,this.getIsImageComplete()&&(this.confirm(0!==this.img.naturalWidth,"naturalWidth"),this.unbindEvents())},d.prototype.unbindEvents=function(){this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype.confirm=function(t,e){this.isLoaded=t,this.emitEvent("progress",[this,this.element,e])},n.makeJQueryPlugin=function(e){(e=e||t.jQuery)&&(i=e,i.fn.imagesLoaded=function(t,e){return new n(this,t,e).jqDeferred.promise(i(this))})},n.makeJQueryPlugin(),n}));
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | // Import utility function for preloading images
2 | import { preloadImages } from './utils.js';
3 |
4 | // Register the GSAP plugins
5 | gsap.registerPlugin(ScrollTrigger, ScrollSmoother, ScrollToPlugin, SplitText);
6 |
7 | // Initialize GSAP's ScrollSmoother for smooth scrolling and scroll-based effects
8 | const smoother = ScrollSmoother.create({
9 | smooth: 1, // How long (in seconds) it takes to "catch up"
10 | effects: true, // Enable data-speed and data-lag-based scroll effects
11 | normalizeScroll: true, // Normalizes scroll behavior across browsers
12 | });
13 |
14 | // Reference to the container that wraps all the 3D scene elements
15 | const sceneWrapper = document.querySelector('.scene-wrapper');
16 |
17 | // Global flag to prevent multiple animations from overlapping or triggering at once
18 | let isAnimating = false;
19 |
20 | // A Map to store SplitText instances keyed by DOM elements (used for animating text characters)
21 | const splitMap = new Map();
22 |
23 | /**
24 | * Returns an array of transform strings to evenly space carousel cells in 3D
25 | *
26 | * @param {number} count - Number of carousel cells
27 | * @param {number} radius - Radius of the circular layout
28 | * @returns {string[]} Array of transform strings for each cell
29 | */
30 | const getCarouselCellTransforms = (count, radius) => {
31 | const angleStep = 360 / count; // Divide 360° by number of cells to get angle step
32 | return Array.from({ length: count }, (_, i) => {
33 | const angle = i * angleStep;
34 | return `rotateY(${angle}deg) translateZ(${radius}px)`; // 3D rotation + translation
35 | });
36 | };
37 |
38 | /**
39 | * Applies 3D transforms to each cell in a given carousel
40 | *
41 | * @param {Element} carousel - DOM element representing the carousel
42 | * @returns {void}
43 | */
44 | const setupCarouselCells = (carousel) => {
45 | const wrapper = carousel.closest('.scene');
46 | const radius = parseFloat(wrapper.dataset.radius) || 500; // Read radius from data attribute or default to 500
47 | const cells = carousel.querySelectorAll('.carousel__cell');
48 |
49 | const transforms = getCarouselCellTransforms(cells.length, radius); // Get transform strings
50 | cells.forEach((cell, i) => {
51 | cell.style.transform = transforms[i]; // Apply transform to each cell
52 | });
53 | };
54 |
55 | /**
56 | * Creates a scroll-linked GSAP timeline for a given carousel scene
57 | *
58 | * @param {Element} carousel - DOM element of the carousel
59 | * @returns {GSAPTimeline} Scroll-driven animation timeline
60 | */
61 | const createScrollAnimation = (carousel) => {
62 | const wrapper = carousel.closest('.scene');
63 | const cards = carousel.querySelectorAll('.card');
64 | const titleSpan = wrapper.querySelector('.scene__title span');
65 | const split = splitMap.get(titleSpan);
66 | const chars = split?.chars || [];
67 |
68 | // Create scroll-driven timeline
69 | carousel._timeline = gsap.timeline({
70 | defaults: { ease: 'sine.inOut' },
71 | scrollTrigger: {
72 | trigger: wrapper,
73 | start: 'top bottom', // Start when top of wrapper hits bottom of viewport
74 | end: 'bottom top', // End when bottom of wrapper hits top of viewport
75 | scrub: true, // Smooth animation based on scroll position
76 | },
77 | });
78 |
79 | carousel._timeline
80 | .fromTo(carousel, { rotationY: 0 }, { rotationY: -180 }, 0) // Rotate carousel horizontally
81 | .fromTo(
82 | carousel,
83 | { rotationZ: 3, rotationX: 3 },
84 | { rotationZ: -3, rotationX: -3 },
85 | 0
86 | ) // Subtle 3D tilt
87 | .fromTo(
88 | cards,
89 | { filter: 'brightness(250%)' },
90 | { filter: 'brightness(80%)', ease: 'power3' },
91 | 0
92 | ) // Brightness dimming
93 | .fromTo(cards, { rotationZ: 10 }, { rotationZ: -10, ease: 'none' }, 0); // Rotate cards around Z
94 |
95 | // Animate title characters in on scroll
96 | if (chars.length > 0) {
97 | animateChars(chars, 'in', {
98 | scrollTrigger: {
99 | trigger: wrapper,
100 | start: 'top center',
101 | toggleActions: 'play none none reverse',
102 | },
103 | });
104 | }
105 |
106 | return carousel._timeline;
107 | };
108 |
109 | /**
110 | * Initializes SplitText instances on key animated elements
111 | *
112 | * @returns {void}
113 | */
114 | const initTextsSplit = () => {
115 | document
116 | .querySelectorAll(
117 | '.scene__title span, .preview__title span, .preview__close'
118 | )
119 | .forEach((span) => {
120 | const split = SplitText.create(span, {
121 | type: 'chars', // Split by characters
122 | charsClass: 'char', // Assign class to each character
123 | autoSplit: true, // Revert and re-split whenever the fonts finish loading
124 | });
125 | splitMap.set(span, split); // Store split instance for reuse
126 | });
127 | };
128 |
129 | /**
130 | * Returns interpolated rotation values based on scroll progress
131 | *
132 | * @param {number} progress - Scroll progress (0 to 1)
133 | * @returns {Object} Object with interpolated rotationX, rotationY, rotationZ values
134 | */
135 | const getInterpolatedRotation = (progress) => ({
136 | rotationY: gsap.utils.interpolate(0, -180, progress), // Horizontal spin from 0° to -180°
137 | rotationX: gsap.utils.interpolate(3, -3, progress), // Tilt forward/backward
138 | rotationZ: gsap.utils.interpolate(3, -3, progress), // Z-axis twist
139 | });
140 |
141 | /**
142 | * Animates a single grid item into view with position, scale, and 3D depth
143 | *
144 | * @param {Element} el - DOM element to animate
145 | * @param {number} dx - Horizontal distance from center
146 | * @param {number} dy - Vertical distance from center
147 | * @param {number} rotationY - Y-axis rotation direction
148 | * @param {number} delay - Delay before animation starts
149 | * @returns {void}
150 | */
151 | const animateGridItemIn = (el, dx, dy, rotationY, delay) => {
152 | // Animate 2D transform and opacity
153 | gsap.fromTo(
154 | el,
155 | {
156 | transformOrigin: `% 50% ${dx > 0 ? -dx * 0.8 : dx * 0.8}px`,
157 | //x: dx, // Offset based on distance from center
158 | autoAlpha: 0,
159 | y: dy * 0.5, // Slight vertical offset
160 | scale: 0.5, // Scaled down
161 | rotationY: dx < 0 ? rotationY : rotationY, // Rotate in from left/right
162 | },
163 | {
164 | //x: 0,
165 | y: 0,
166 | scale: 1,
167 | rotationY: 0,
168 | autoAlpha: 1,
169 | duration: 0.4,
170 | ease: 'sine',
171 | delay: delay + 0.1,
172 | }
173 | );
174 |
175 | // Animate z-position separately for 3D pop
176 | gsap.fromTo(
177 | el,
178 | { z: -3500 },
179 | {
180 | z: 0,
181 | duration: 0.3,
182 | ease: 'expo',
183 | delay,
184 | }
185 | );
186 | };
187 |
188 | /**
189 | * Animates a single grid item out of view with depth and fade
190 | *
191 | * @param {Element} el - DOM element to animate
192 | * @param {number} dx - Horizontal distance from center
193 | * @param {number} dy - Vertical distance from center
194 | * @param {number} rotationY - Y-axis rotation direction
195 | * @param {number} delay - Delay before animation starts
196 | * @param {boolean} isLast - Whether this is the last item (for onComplete)
197 | * @param {Function} [onComplete] - Callback when animation finishes
198 | * @returns {void}
199 | */
200 | const animateGridItemOut = (
201 | el,
202 | dx,
203 | dy,
204 | rotationY,
205 | delay,
206 | isLast,
207 | onComplete
208 | ) => {
209 | // Animate 2D transform and opacity
210 | gsap.to(el, {
211 | startAt: {
212 | transformOrigin: `50% 50% ${dx > 0 ? -dx * 0.8 : dx * 0.8}px`,
213 | },
214 | //x: dx,
215 | y: dy * 0.4,
216 | rotationY: dx < 0 ? rotationY : rotationY,
217 | scale: 0.4,
218 | autoAlpha: 0,
219 | duration: 0.4,
220 | ease: 'sine.in',
221 | delay,
222 | });
223 | gsap.to(el, {
224 | z: -3500,
225 | duration: 0.4,
226 | ease: 'expo.in',
227 | delay: delay + 0.9,
228 | onComplete: isLast ? onComplete : undefined, // Call onComplete only for the last item
229 | });
230 | };
231 |
232 | /**
233 | * Animates all grid items in or out with a distance-based stagger and easing
234 | *
235 | * @param {Object} options
236 | * @param {NodeList} options.items - Collection of grid item DOM elements
237 | * @param {number} options.centerX - X-coordinate of the center
238 | * @param {number} options.centerY - Y-coordinate of the center
239 | * @param {'in' | 'out'} [options.direction='in'] - Animation direction
240 | * @param {Function} [options.onComplete] - Callback after all animations complete
241 | * @returns {void}
242 | */
243 | const animateGridItems = ({
244 | items,
245 | centerX,
246 | centerY,
247 | direction = 'in',
248 | onComplete,
249 | }) => {
250 | // Measure position of each item and calculate distance from center
251 | const itemData = Array.from(items).map((el) => {
252 | const rect = el.getBoundingClientRect();
253 | const elCenterX = rect.left + rect.width / 2;
254 | const elCenterY = rect.top + rect.height / 2;
255 | const dx = centerX - elCenterX;
256 | const dy = centerY - elCenterY;
257 | const dist = Math.hypot(dx, dy); // Euclidean distance from center
258 | const isLeft = elCenterX < centerX;
259 | return { el, dx, dy, dist, isLeft };
260 | });
261 |
262 | const maxDist = Math.max(...itemData.map((d) => d.dist)); // Farthest distance
263 | const totalStagger = 0.025 * (itemData.length - 1); // Total stagger duration
264 |
265 | let latest = { delay: -1, el: null }; // Track latest delay item
266 |
267 | itemData.forEach(({ el, dx, dy, dist, isLeft }) => {
268 | const norm = maxDist ? dist / maxDist : 0; // Normalize distance
269 | const exponential = Math.pow(direction === 'in' ? 1 - norm : norm, 1); // Easing
270 | const delay = exponential * totalStagger;
271 | const rotationY = isLeft ? 100 : -100; // Directional rotation
272 |
273 | if (direction === 'in') {
274 | animateGridItemIn(el, dx, dy, rotationY, delay);
275 | } else {
276 | if (delay > latest.delay) {
277 | latest = { delay, el };
278 | }
279 | animateGridItemOut(el, dx, dy, rotationY, delay, false, onComplete);
280 | }
281 | });
282 |
283 | // Ensure onComplete runs only after the last item finishes
284 | if (direction === 'out' && latest.el) {
285 | const { el, dx, dy, isLeft } = itemData.find((d) => d.el === latest.el);
286 | const rotationY = isLeft ? 100 : -100;
287 | animateGridItemOut(el, dx, dy, rotationY, latest.delay, true, onComplete);
288 | }
289 | };
290 |
291 | /**
292 | * Animates all grid items in the preview into view
293 | *
294 | * @param {Element} preview - Preview DOM element containing grid items
295 | * @returns {void}
296 | */
297 | const animatePreviewGridIn = (preview) => {
298 | const items = preview.querySelectorAll('.grid__item');
299 | // Clear any inline styles from previous animations
300 | gsap.set(items, { clearProps: 'all' });
301 | // Trigger grid item entrance animation from center of screen
302 | animateGridItems({
303 | items,
304 | centerX: window.innerWidth / 2,
305 | centerY: window.innerHeight / 2,
306 | direction: 'in',
307 | });
308 | };
309 |
310 | /**
311 | * Animates all grid items in the preview out of view
312 | * @param {HTMLElement} preview - The preview container
313 | */
314 | const animatePreviewGridOut = (preview) => {
315 | const items = preview.querySelectorAll('.grid__item');
316 | // Trigger grid item exit animation toward edges
317 | const onComplete = () =>
318 | gsap.set(preview, { pointerEvents: 'none', autoAlpha: 0 });
319 | animateGridItems({
320 | items,
321 | centerX: window.innerWidth / 2,
322 | centerY: window.innerHeight / 2,
323 | direction: 'out',
324 | onComplete,
325 | });
326 | };
327 |
328 | /**
329 | * Retrieves relevant DOM elements and text splits from a scene title
330 | * @param {HTMLElement} titleEl - The `.scene__title` element
331 | * @returns {Object} wrapper, carousel, cards, span, chars
332 | */
333 | const getSceneElementsFromTitle = (titleEl) => {
334 | const wrapper = titleEl.closest('.scene'); // Scene container
335 | const carousel = wrapper?.querySelector('.carousel'); // Carousel in the scene
336 | const cards = carousel?.querySelectorAll('.card'); // All card elements
337 | const span = titleEl.querySelector('span'); // Title span
338 | const chars = splitMap.get(span)?.chars || []; // SplitText chars
339 | return { wrapper, carousel, cards, span, chars };
340 | };
341 |
342 | /**
343 | * Retrieves scene-related elements from a preview element
344 | * @param {HTMLElement} previewEl - The `.preview` element
345 | * @returns {Object} All scene elements and corresponding titleEl
346 | */
347 | const getSceneElementsFromPreview = (previewEl) => {
348 | const previewId = `#${previewEl.id}`;
349 | const titleLink = document.querySelector(
350 | `.scene__title a[href="${previewId}"]`
351 | );
352 | const titleEl = titleLink?.closest('.scene__title'); // Corresponding title element
353 | return { ...getSceneElementsFromTitle(titleEl), titleEl };
354 | };
355 |
356 | /**
357 | * Animates SplitText character elements in or out
358 | *
359 | * @param {HTMLElement[]} chars - Array of character elements to animate
360 | * @param {'in' | 'out'} direction - Direction of the animation ('in' for fade in, 'out' for fade out)
361 | * @param {Object} [opts={}] - Optional GSAP config overrides (e.g. scrollTrigger)
362 | */
363 | const animateChars = (chars, direction = 'in', opts = {}) => {
364 | const base = {
365 | autoAlpha: direction === 'in' ? 1 : 0,
366 | duration: 0.02,
367 | ease: 'none',
368 | stagger: { each: 0.04, from: direction === 'in' ? 'start' : 'end' },
369 | ...opts,
370 | };
371 |
372 | gsap.fromTo(chars, { autoAlpha: direction === 'in' ? 0 : 1 }, base);
373 | };
374 |
375 | /**
376 | * Animates title and close button characters in a preview
377 | *
378 | * @param {HTMLElement} preview - The preview container
379 | * @param {'in' | 'out'} direction - Animation direction
380 | * @param {string} [selector='.preview__title span, .preview__close'] - Selector for elements to animate
381 | */
382 | const animatePreviewTexts = (
383 | preview,
384 | direction = 'in',
385 | selector = '.preview__title span, .preview__close'
386 | ) => {
387 | preview.querySelectorAll(selector).forEach((el) => {
388 | const chars = splitMap.get(el)?.chars || [];
389 | animateChars(chars, direction);
390 | });
391 | };
392 |
393 | /**
394 | * Handles transition from carousel view to preview grid
395 | *
396 | * @param {Event} e - Click event triggered from `.scene__title`
397 | */
398 | const activatePreviewFromCarousel = (e) => {
399 | e.preventDefault();
400 | if (isAnimating) return;
401 | isAnimating = true;
402 |
403 | const titleEl = e.currentTarget;
404 | const { wrapper, carousel, cards, chars } =
405 | getSceneElementsFromTitle(titleEl);
406 |
407 | // Calculate scroll position to center the scene
408 | const offsetTop = wrapper.getBoundingClientRect().top + window.scrollY;
409 | const targetY = offsetTop - window.innerHeight / 2 + wrapper.offsetHeight / 2;
410 |
411 | // Temporarily disable scroll-based animations
412 | ScrollTrigger.getAll().forEach((t) => t.disable(false));
413 |
414 | gsap
415 | .timeline({
416 | defaults: { duration: 1.5, ease: 'power2.inOut' },
417 | onComplete: () => {
418 | isAnimating = false;
419 | ScrollTrigger.getAll().forEach((t) => t.enable());
420 | carousel._timeline.scrollTrigger.scroll(targetY);
421 | },
422 | })
423 | .to(window, {
424 | onStart: () => {
425 | lockUserScroll();
426 | },
427 | onComplete: () => {
428 | unlockUserScroll();
429 | smoother.paused(true);
430 | },
431 | scrollTo: { y: targetY, autoKill: true },
432 | })
433 | .to(
434 | chars,
435 | {
436 | autoAlpha: 0,
437 | duration: 0.02,
438 | ease: 'none',
439 | stagger: { each: 0.04, from: 'end' },
440 | },
441 | 0
442 | )
443 | .to(carousel, { rotationX: 90, rotationY: -360, z: -2000 }, 0)
444 | .to(
445 | carousel,
446 | {
447 | duration: 2.5,
448 | ease: 'power3.inOut',
449 | z: 1500,
450 | rotationZ: 270,
451 | onComplete: () => gsap.set(sceneWrapper, { autoAlpha: 0 }),
452 | },
453 | 0.7
454 | )
455 | .to(cards, { rotationZ: 0 }, 0)
456 | .add(() => {
457 | const previewSelector = titleEl.querySelector('a')?.getAttribute('href');
458 | const preview = document.querySelector(previewSelector);
459 | gsap.set(preview, { pointerEvents: 'auto', autoAlpha: 1 });
460 | animatePreviewGridIn(preview);
461 | animatePreviewTexts(preview, 'in');
462 | }, '<+=1.9');
463 | };
464 |
465 | /**
466 | * Handles transition from preview grid back to carousel view
467 | *
468 | * @param {Event} e - Click event triggered from `.preview__close`
469 | */
470 | const deactivatePreviewToCarousel = (e) => {
471 | if (isAnimating) return;
472 | isAnimating = true;
473 |
474 | const preview = e.currentTarget.closest('.preview');
475 | if (!preview) return;
476 |
477 | const { carousel, cards, chars } = getSceneElementsFromPreview(preview);
478 |
479 | animatePreviewTexts(preview, 'out');
480 | animatePreviewGridOut(preview);
481 |
482 | gsap.set(sceneWrapper, { autoAlpha: 1 });
483 |
484 | const progress = 0.5; // halfway
485 | /*
486 | BUG: progress should always be 0.5 but for some reason it's 0 sometimes
487 | const timeline = carousel._timeline;
488 | const scrollTrigger = timeline?.scrollTrigger;
489 | const progress = scrollTrigger?.progress ?? 0;
490 | */
491 |
492 | const { rotationX, rotationY, rotationZ } = getInterpolatedRotation(progress);
493 |
494 | gsap
495 | .timeline({
496 | delay: 0.7,
497 | defaults: { duration: 1.3, ease: 'expo' },
498 | onComplete: () => {
499 | smoother.paused(false);
500 | isAnimating = false;
501 | },
502 | })
503 | .fromTo(
504 | chars,
505 | { autoAlpha: 0 },
506 | {
507 | autoAlpha: 1,
508 | duration: 0.02,
509 | ease: 'none',
510 | stagger: { each: 0.04, from: 'start' },
511 | }
512 | )
513 | .fromTo(
514 | carousel,
515 | {
516 | z: -550,
517 | rotationX,
518 | rotationY: -720,
519 | rotationZ,
520 | yPercent: 300,
521 | },
522 | {
523 | rotationY,
524 | yPercent: 0,
525 | },
526 | 0
527 | )
528 | .fromTo(cards, { autoAlpha: 0 }, { autoAlpha: 1 }, 0.3);
529 | };
530 |
531 | /**
532 | * Adds click event listeners to scene titles and preview close buttons
533 | *
534 | * @returns {void}
535 | */
536 | const initEventListeners = () => {
537 | // When a scene title is clicked, activate the preview
538 | document.querySelectorAll('.scene__title').forEach((title) => {
539 | title.addEventListener('click', activatePreviewFromCarousel);
540 | });
541 |
542 | // When a preview close button is clicked, deactivate the preview
543 | document.querySelectorAll('.preview__close').forEach((btn) => {
544 | btn.addEventListener('click', deactivatePreviewToCarousel);
545 | });
546 | };
547 |
548 | /**
549 | * Initializes all carousels on the page
550 | *
551 | * @returns {void}
552 | */
553 | const initCarousels = () => {
554 | document.querySelectorAll('.carousel').forEach((carousel) => {
555 | setupCarouselCells(carousel); // Position carousel cells in 3D
556 | carousel._timeline = createScrollAnimation(carousel); // Attach scroll animation timeline
557 | });
558 | };
559 |
560 | function preventScroll(e) {
561 | e.preventDefault();
562 | }
563 |
564 | function lockUserScroll() {
565 | window.addEventListener('wheel', preventScroll, { passive: false });
566 | window.addEventListener('touchmove', preventScroll, { passive: false });
567 | window.addEventListener('keydown', preventArrowScroll, false);
568 | }
569 |
570 | function unlockUserScroll() {
571 | window.removeEventListener('wheel', preventScroll);
572 | window.removeEventListener('touchmove', preventScroll);
573 | window.removeEventListener('keydown', preventArrowScroll);
574 | }
575 |
576 | function preventArrowScroll(e) {
577 | const keys = [
578 | 'ArrowUp',
579 | 'ArrowDown',
580 | 'PageUp',
581 | 'PageDown',
582 | 'Home',
583 | 'End',
584 | ' ',
585 | ];
586 | if (keys.includes(e.key)) e.preventDefault();
587 | }
588 |
589 | /**
590 | * Initializes text splitting, carousels, and event listeners
591 | *
592 | * @returns {void}
593 | */
594 | const init = () => {
595 | initTextsSplit(); // Prepare character-level splits for animations
596 | initCarousels(); // Set up carousels with transforms and scroll triggers
597 | initEventListeners(); // Bind all interactive handlers
598 | window.addEventListener('resize', ScrollTrigger.refresh); // Refresh triggers on resize
599 | };
600 |
601 | // Start app once images are preloaded
602 | preloadImages('.grid__item-image').then(() => {
603 | document.body.classList.remove('loading'); // Remove loading state from body
604 | init(); // Begin initialization
605 | });
606 |
--------------------------------------------------------------------------------
/js/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Preloads images specified by the CSS selector.
3 | * @function
4 | * @param {string} [selector='img'] - CSS selector for target images.
5 | * @returns {Promise} - Resolves when all specified images are loaded.
6 | */
7 | const preloadImages = (selector = 'img') => {
8 | return new Promise((resolve) => {
9 | // The imagesLoaded library is used to ensure all images (including backgrounds) are fully loaded.
10 | imagesLoaded(document.querySelectorAll(selector), {background: true}, resolve);
11 | });
12 | };
13 |
14 | // Exporting utility functions for use in other modules.
15 | export {
16 | preloadImages
17 | };
--------------------------------------------------------------------------------