hyperframes 0.1.14 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +3968 -3297
  3. package/dist/skills/hyperframes-captions/SKILL.md +2 -2
  4. package/dist/skills/hyperframes-compose/SKILL.md +1 -0
  5. package/dist/studio/assets/index-Bkp9HQbo.css +1 -0
  6. package/dist/studio/assets/index-DfhSlTti.js +93 -0
  7. package/dist/studio/index.html +2 -2
  8. package/dist/templates/_shared/CLAUDE.md +1 -1
  9. package/package.json +3 -2
  10. package/dist/studio/assets/index-CLmYRLY-.css +0 -1
  11. package/dist/studio/assets/index-CRvFpc0E.js +0 -84
  12. package/dist/templates/play-mode/compositions/captions.html +0 -163
  13. package/dist/templates/play-mode/compositions/intro.html +0 -107
  14. package/dist/templates/play-mode/compositions/stats.html +0 -388
  15. package/dist/templates/play-mode/index.html +0 -239
  16. package/dist/templates/swiss-grid/assets/swiss-grid.svg +0 -116
  17. package/dist/templates/swiss-grid/compositions/captions.html +0 -148
  18. package/dist/templates/swiss-grid/compositions/graphics.html +0 -207
  19. package/dist/templates/swiss-grid/compositions/intro.html +0 -131
  20. package/dist/templates/swiss-grid/index.html +0 -234
  21. package/dist/templates/vignelli/compositions/captions.html +0 -181
  22. package/dist/templates/vignelli/compositions/overlays.html +0 -323
  23. package/dist/templates/vignelli/index.html +0 -190
  24. package/dist/templates/warm-grain/compositions/captions.html +0 -147
  25. package/dist/templates/warm-grain/compositions/graphics.html +0 -164
  26. package/dist/templates/warm-grain/compositions/intro.html +0 -86
  27. package/dist/templates/warm-grain/index.html +0 -294
@@ -1,163 +0,0 @@
1
- <template id="captions-template">
2
- <div
3
- data-composition-id="captions"
4
- data-width="1920"
5
- data-height="1080"
6
- data-duration="__VIDEO_DURATION__"
7
- >
8
- <div id="captions-container"></div>
9
-
10
- <style>
11
- [data-composition-id="captions"] {
12
- width: 1920px;
13
- height: 1080px;
14
- pointer-events: none;
15
- }
16
-
17
- [data-composition-id="captions"] #captions-container {
18
- position: absolute;
19
- bottom: 100px; /* Position in bottom 1/3 */
20
- left: 50%;
21
- transform: translateX(-50%);
22
- display: flex;
23
- justify-content: center;
24
- align-items: center;
25
- width: 100%;
26
- height: 150px; /* Fixed height for the caption box */
27
- }
28
-
29
- .caption-box {
30
- position: absolute; /* Stacked in the same place */
31
- background-color: #ff2d8a;
32
- color: white;
33
- padding: 20px 40px;
34
- border-radius: 16px;
35
- font-family: "Nunito", sans-serif;
36
- font-weight: 900; /* Nunito Black */
37
- font-size: 64px;
38
- text-align: center;
39
- display: inline-block;
40
- white-space: nowrap;
41
- opacity: 0;
42
- visibility: hidden;
43
- transform: scale(0);
44
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
45
- }
46
- </style>
47
-
48
- <script>
49
- (function () {
50
- const TRANSCRIPT = [
51
- { text: "We", start: 0.119, end: 0.259 },
52
- { text: "asked", start: 0.319, end: 0.479 },
53
- { text: "what", start: 0.519, end: 0.659 },
54
- { text: "you", start: 0.699, end: 0.819 },
55
- { text: "needed.", start: 0.859, end: 1.819 },
56
- { text: "Forty-seven", start: 1.839, end: 2.299 },
57
- { text: "percent", start: 2.399, end: 2.679 },
58
- { text: "of", start: 2.7, end: 2.799 },
59
- { text: "you", start: 2.839, end: 2.939 },
60
- { text: "said", start: 3.039, end: 3.179 },
61
- { text: "motion", start: 3.24, end: 3.559 },
62
- { text: "graphics.", start: 3.579, end: 4.639 },
63
- { text: "Sixty-two", start: 4.659, end: 5.179 },
64
- { text: "percent", start: 5.299, end: 5.759 },
65
- { text: "said", start: 5.859, end: 5.98 },
66
- { text: "static", start: 6.099, end: 6.399 },
67
- { text: "content", start: 6.46, end: 6.879 },
68
- { text: "was", start: 6.92, end: 7.079 },
69
- { text: "costing", start: 7.099, end: 7.48 },
70
- { text: "you", start: 7.5, end: 7.579 },
71
- { text: "attention,", start: 7.679, end: 8.659 },
72
- { text: "and", start: 8.699, end: 8.86 },
73
- { text: "three", start: 8.88, end: 9.06 },
74
- { text: "out", start: 9.079, end: 9.18 },
75
- { text: "of", start: 9.199, end: 9.34 },
76
- { text: "four", start: 9.38, end: 9.799 },
77
- { text: "said", start: 9.84, end: 10.0 },
78
- { text: "you", start: 10.019, end: 10.159 },
79
- { text: "know", start: 10.179, end: 10.36 },
80
- { text: "the", start: 10.38, end: 10.479 },
81
- { text: "look", start: 10.52, end: 10.699 },
82
- { text: "you", start: 10.739, end: 10.859 },
83
- { text: "want", start: 10.98, end: 11.34 },
84
- { text: "but", start: 11.359, end: 11.52 },
85
- { text: "don't", start: 11.56, end: 11.779 },
86
- { text: "have", start: 11.819, end: 11.94 },
87
- { text: "the", start: 11.96, end: 12.06 },
88
- { text: "editing", start: 12.079, end: 12.4 },
89
- { text: "skills", start: 12.52, end: 12.86 },
90
- { text: "to", start: 12.88, end: 13.0 },
91
- { text: "get", start: 13.019, end: 13.18 },
92
- { text: "there.", start: 13.22, end: 14.22 },
93
- { text: "So", start: 14.239, end: 14.399 },
94
- { text: "we", start: 14.42, end: 14.52 },
95
- { text: "built", start: 14.619, end: 14.88 },
96
- { text: "Hyperframes", start: 15.079, end: 15.42 },
97
- ];
98
- const container = document.getElementById("captions-container");
99
- const tl = gsap.timeline({ paused: true });
100
-
101
- // Group transcript into lines (max 5 words)
102
- const lines = [];
103
- for (let i = 0; i < TRANSCRIPT.length; i += 5) {
104
- lines.push(TRANSCRIPT.slice(i, i + 5));
105
- }
106
-
107
- lines.forEach((lineWords, index) => {
108
- const lineText = lineWords.map((w) => w.text).join(" ");
109
- const startTime = lineWords[0].start;
110
- const endTime = lineWords[lineWords.length - 1].end;
111
-
112
- // Create element
113
- const el = document.createElement("div");
114
- el.className = "caption-box";
115
- el.textContent = lineText;
116
- el.id = `caption-line-${index}`;
117
- container.appendChild(el);
118
-
119
- // Animation: Pop in with scale-up bounce (0% to 110% to 100%)
120
- tl.to(
121
- el,
122
- {
123
- autoAlpha: 1,
124
- scale: 1.1,
125
- duration: 0.2,
126
- ease: "power2.out",
127
- },
128
- startTime,
129
- );
130
-
131
- tl.to(
132
- el,
133
- {
134
- scale: 1,
135
- duration: 0.1,
136
- ease: "power2.inOut",
137
- },
138
- startTime + 0.2,
139
- );
140
-
141
- // Stay visible until next line or end of its duration
142
- // We hide it when the next line starts or at its own end time
143
- const hideTime =
144
- index < lines.length - 1 ? Math.min(endTime, lines[index + 1][0].start) : endTime;
145
-
146
- tl.to(
147
- el,
148
- {
149
- autoAlpha: 0,
150
- scale: 0,
151
- duration: 0.15,
152
- ease: "power2.in",
153
- },
154
- hideTime,
155
- );
156
- });
157
-
158
- window.__timelines = window.__timelines || {};
159
- window.__timelines["captions"] = tl;
160
- })();
161
- </script>
162
- </div>
163
- </template>
@@ -1,107 +0,0 @@
1
- <template id="intro-template">
2
- <div
3
- id="intro-comp"
4
- data-composition-id="intro"
5
- data-start="0"
6
- data-duration="__VIDEO_DURATION__"
7
- data-width="1920"
8
- data-height="1080"
9
- >
10
- <div class="intro-container">
11
- <h1 class="title">HYPERFRAMES</h1>
12
- </div>
13
-
14
- <style>
15
- @import url("https://fonts.googleapis.com/css2?family=Nunito:wght@900&display=swap");
16
-
17
- [data-composition-id="intro"] .intro-container {
18
- width: 100%;
19
- height: 100%;
20
- display: flex;
21
- justify-content: center;
22
- align-items: center;
23
- pointer-events: none;
24
- }
25
-
26
- [data-composition-id="intro"] .title {
27
- font-family: "Nunito", sans-serif;
28
- font-weight: 900;
29
- font-size: 180px;
30
- color: #ff2d8a; /* Hot Pink */
31
- text-transform: uppercase;
32
- transform: rotate(-5deg); /* Tilted 5 degrees */
33
- margin: 0;
34
- padding: 20px;
35
- /* Layered offset shadow for depth without suffocating letterforms */
36
- filter: drop-shadow(8px 8px 0 #ffffff) drop-shadow(15px 15px 0 rgba(0, 0, 0, 0.1));
37
- opacity: 0;
38
- scale: 0;
39
- }
40
- </style>
41
-
42
- <script>
43
- (function () {
44
- const tl = gsap.timeline({ paused: true });
45
-
46
- // --- INTRO SEQUENCE ---
47
- // Animation: Scale-up bounce (0% to 110% to 100%) with elastic overshoot
48
- // Starts at 0.5s, lasts until 3s
49
- tl.to('[data-composition-id="intro"] .title', {
50
- opacity: 1,
51
- scale: 1.1,
52
- duration: 0.6,
53
- ease: "back.out(1.7)",
54
- delay: 0.5,
55
- })
56
- .to('[data-composition-id="intro"] .title', {
57
- scale: 1,
58
- duration: 0.4,
59
- ease: "elastic.out(1, 0.3)",
60
- })
61
- // Ambient motion
62
- .to(
63
- '[data-composition-id="intro"] .title',
64
- {
65
- rotation: "-3deg",
66
- duration: 1,
67
- repeat: 1,
68
- yoyo: true,
69
- ease: "sine.inOut",
70
- },
71
- "-=0.5",
72
- )
73
- // Exit intro
74
- .to(
75
- '[data-composition-id="intro"] .title',
76
- {
77
- opacity: 0,
78
- scale: 0.5,
79
- duration: 0.3,
80
- ease: "power2.in",
81
- },
82
- 1.4,
83
- );
84
-
85
- // --- END CARD SEQUENCE ---
86
- // Appears at 14.619s (after Moment 3 finishes exiting at 14.239 + 0.3)
87
- tl.to(
88
- '[data-composition-id="intro"] .title',
89
- {
90
- opacity: 1,
91
- scale: 1.1,
92
- duration: 0.6,
93
- ease: "back.out(1.7)",
94
- },
95
- 14.619,
96
- ).to('[data-composition-id="intro"] .title', {
97
- scale: 1,
98
- duration: 0.4,
99
- ease: "elastic.out(1, 0.3)",
100
- });
101
-
102
- window.__timelines = window.__timelines || {};
103
- window.__timelines["intro"] = tl;
104
- })();
105
- </script>
106
- </div>
107
- </template>
@@ -1,388 +0,0 @@
1
- <template id="stats-template">
2
- <div
3
- data-composition-id="stats"
4
- data-width="1920"
5
- data-height="1080"
6
- data-duration="__VIDEO_DURATION__"
7
- >
8
- <div id="stats-container">
9
- <!-- Moment 1: 47% NEED MOTION GRAPHICS -->
10
- <div id="moment-1" class="moment" style="opacity: 0; transform: scale(0) rotate(8deg)">
11
- <div class="sticker-stack">
12
- <div class="sticker-bg blue-bg"></div>
13
- <div class="sticker-content white-bg blue-border">
14
- <div class="stat-number blue-text">47%</div>
15
- <div class="stat-label blue-text">NEED MOTION GRAPHICS</div>
16
- </div>
17
- </div>
18
- <!-- Decorative shapes -->
19
- <div
20
- class="shape circle pink-bg"
21
- style="top: -40px; left: -40px; width: 80px; height: 80px"
22
- ></div>
23
- <div
24
- class="shape pill lime-bg"
25
- style="bottom: -30px; right: -50px; width: 120px; height: 40px; transform: rotate(-15deg)"
26
- ></div>
27
- </div>
28
-
29
- <!-- Moment 2: 62% STRUGGLE WITH STATIC CONTENT -->
30
- <div id="moment-2" class="moment" style="opacity: 0; transform: scale(0) rotate(-5deg)">
31
- <div class="sticker-stack">
32
- <div class="sticker-bg white-bg"></div>
33
- <div class="sticker-content blue-bg white-border">
34
- <div class="stat-number white-text">62%</div>
35
- <div class="stat-label white-text">STRUGGLE WITH STATIC CONTENT</div>
36
- </div>
37
- </div>
38
- <!-- Decorative shapes -->
39
- <div
40
- class="shape blob yellow-bg"
41
- style="
42
- top: -50px;
43
- right: -30px;
44
- width: 100px;
45
- height: 100px;
46
- border-radius: 40% 60% 70% 30% / 40% 50% 60% 50%;
47
- "
48
- ></div>
49
- <div
50
- class="shape circle pink-bg"
51
- style="bottom: -20px; left: -40px; width: 60px; height: 60px"
52
- ></div>
53
- </div>
54
-
55
- <!-- Moment 3: 75% LACK EDITING SKILLS -->
56
- <div id="moment-3" class="moment" style="opacity: 0; transform: scale(0) rotate(3deg)">
57
- <div class="sticker-stack">
58
- <div class="sticker-bg pink-bg"></div>
59
- <div class="sticker-content white-bg pink-border">
60
- <div class="stat-number pink-text">75%</div>
61
- <div class="stat-label pink-text">LACK EDITING SKILLS</div>
62
- </div>
63
- </div>
64
- <!-- Decorative shapes -->
65
- <div
66
- class="shape pill blue-bg"
67
- style="
68
- top: -30px;
69
- left: 50%;
70
- transform: translateX(-50%) rotate(5deg);
71
- width: 150px;
72
- height: 45px;
73
- "
74
- ></div>
75
- <div
76
- class="shape circle yellow-bg"
77
- style="bottom: -40px; right: -20px; width: 90px; height: 90px"
78
- ></div>
79
- </div>
80
- </div>
81
-
82
- <style>
83
- [data-composition-id="stats"] {
84
- width: 1920px;
85
- height: 1080px;
86
- position: relative;
87
- font-family: "Nunito", sans-serif;
88
- overflow: hidden;
89
- }
90
-
91
- [data-composition-id="stats"] #stats-container {
92
- width: 100%;
93
- height: 100%;
94
- position: relative;
95
- }
96
-
97
- [data-composition-id="stats"] .moment {
98
- position: absolute;
99
- display: flex;
100
- flex-direction: column;
101
- align-items: center;
102
- justify-content: center;
103
- padding: 40px;
104
- z-index: 100;
105
- }
106
-
107
- /* Layout Positions */
108
- [data-composition-id="stats"] #moment-1 {
109
- top: 200px;
110
- right: 150px; /* Right side when A-roll is Left */
111
- width: 600px;
112
- }
113
-
114
- [data-composition-id="stats"] #moment-2 {
115
- top: 250px;
116
- left: 100px; /* Left side when A-roll is Right */
117
- width: 700px;
118
- }
119
-
120
- [data-composition-id="stats"] #moment-3 {
121
- bottom: 100px; /* Below A-roll when centered */
122
- left: 50%;
123
- transform: translateX(-50%);
124
- width: 800px;
125
- }
126
-
127
- /* Sticker Stack Effect */
128
- [data-composition-id="stats"] .sticker-stack {
129
- position: relative;
130
- width: 100%;
131
- padding: 40px;
132
- filter: drop-shadow(0 15px 0 rgba(0, 87, 255, 0.2));
133
- }
134
-
135
- [data-composition-id="stats"] .sticker-bg {
136
- position: absolute;
137
- top: 0;
138
- left: 0;
139
- width: 100%;
140
- height: 100%;
141
- border-radius: 40px;
142
- z-index: -1;
143
- transform: rotate(-2deg);
144
- }
145
-
146
- [data-composition-id="stats"] .sticker-content {
147
- position: relative;
148
- padding: 40px;
149
- border-radius: 35px;
150
- border: 8px solid currentColor;
151
- display: flex;
152
- flex-direction: column;
153
- align-items: center;
154
- text-align: center;
155
- }
156
-
157
- /* Colors */
158
- [data-composition-id="stats"] .blue-bg {
159
- background-color: #0057ff;
160
- }
161
- [data-composition-id="stats"] .pink-bg {
162
- background-color: #ff2d8a;
163
- }
164
- [data-composition-id="stats"] .lime-bg {
165
- background-color: #7fff00;
166
- }
167
- [data-composition-id="stats"] .yellow-bg {
168
- background-color: #ffe500;
169
- }
170
- [data-composition-id="stats"] .white-bg {
171
- background-color: #ffffff;
172
- }
173
-
174
- [data-composition-id="stats"] .blue-text {
175
- color: #0057ff;
176
- }
177
- [data-composition-id="stats"] .white-text {
178
- color: #ffffff;
179
- }
180
- [data-composition-id="stats"] .pink-text {
181
- color: #ff2d8a;
182
- }
183
-
184
- [data-composition-id="stats"] .blue-border {
185
- border-color: #0057ff;
186
- }
187
- [data-composition-id="stats"] .white-border {
188
- border-color: #ffffff;
189
- }
190
- [data-composition-id="stats"] .pink-border {
191
- border-color: #ff2d8a;
192
- }
193
-
194
- /* Typography */
195
- [data-composition-id="stats"] .stat-number {
196
- font-size: 180px;
197
- font-weight: 900;
198
- line-height: 1;
199
- margin-bottom: 10px;
200
- }
201
-
202
- [data-composition-id="stats"] .stat-label {
203
- font-size: 48px;
204
- font-weight: 900;
205
- line-height: 1.1;
206
- max-width: 500px;
207
- }
208
-
209
- /* Shapes */
210
- [data-composition-id="stats"] .shape {
211
- position: absolute;
212
- z-index: -2;
213
- border-radius: 50%;
214
- }
215
- [data-composition-id="stats"] .pill {
216
- border-radius: 100px;
217
- }
218
- </style>
219
-
220
- <script>
221
- (function () {
222
- const TRANSCRIPT = [
223
- { text: "We", start: 0.119, end: 0.259 },
224
- { text: "asked", start: 0.319, end: 0.479 },
225
- { text: "what", start: 0.519, end: 0.659 },
226
- { text: "you", start: 0.699, end: 0.819 },
227
- { text: "needed.", start: 0.859, end: 1.819 },
228
- { text: "Forty-seven", start: 1.839, end: 2.299 },
229
- { text: "percent", start: 2.399, end: 2.679 },
230
- { text: "of", start: 2.7, end: 2.799 },
231
- { text: "you", start: 2.839, end: 2.939 },
232
- { text: "said", start: 3.039, end: 3.179 },
233
- { text: "motion", start: 3.24, end: 3.559 },
234
- { text: "graphics.", start: 3.579, end: 4.639 },
235
- { text: "Sixty-two", start: 4.659, end: 5.179 },
236
- { text: "percent", start: 5.299, end: 5.759 },
237
- { text: "said", start: 5.859, end: 5.98 },
238
- { text: "static", start: 6.099, end: 6.399 },
239
- { text: "content", start: 6.46, end: 6.879 },
240
- { text: "was", start: 6.92, end: 7.079 },
241
- { text: "costing", start: 7.099, end: 7.48 },
242
- { text: "you", start: 7.5, end: 7.579 },
243
- { text: "attention,", start: 7.679, end: 8.659 },
244
- { text: "and", start: 8.699, end: 8.86 },
245
- { text: "three", start: 8.88, end: 9.06 },
246
- { text: "out", start: 9.079, end: 9.18 },
247
- { text: "of", start: 9.199, end: 9.34 },
248
- { text: "four", start: 9.38, end: 9.799 },
249
- { text: "said", start: 9.84, end: 10.0 },
250
- { text: "you", start: 10.019, end: 10.159 },
251
- { text: "know", start: 10.179, end: 10.36 },
252
- { text: "the", start: 10.38, end: 10.479 },
253
- { text: "look", start: 10.52, end: 10.699 },
254
- { text: "you", start: 10.739, end: 10.859 },
255
- { text: "want", start: 10.98, end: 11.34 },
256
- { text: "but", start: 11.359, end: 11.52 },
257
- { text: "don't", start: 11.56, end: 11.779 },
258
- { text: "have", start: 11.819, end: 11.94 },
259
- { text: "the", start: 11.96, end: 12.06 },
260
- { text: "editing", start: 12.079, end: 12.4 },
261
- { text: "skills", start: 12.52, end: 12.86 },
262
- { text: "to", start: 12.88, end: 13.0 },
263
- { text: "get", start: 13.019, end: 13.18 },
264
- { text: "there.", start: 13.22, end: 14.22 },
265
- { text: "So", start: 14.239, end: 14.399 },
266
- { text: "we", start: 14.42, end: 14.52 },
267
- { text: "built", start: 14.619, end: 14.88 },
268
- { text: "Hyperframes", start: 15.079, end: 15.42 },
269
- ];
270
- const tl = gsap.timeline({ paused: true });
271
-
272
- // Helper to find word timing
273
- function getWordTime(wordText) {
274
- const word = TRANSCRIPT.find((w) =>
275
- w.text
276
- .toLowerCase()
277
- .replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "")
278
- .startsWith(wordText.toLowerCase()),
279
- );
280
- return word ? word.start : null;
281
- }
282
-
283
- // Timings from transcript
284
- const t1 = getWordTime("Forty-seven") || 1.839;
285
- const t2 = getWordTime("Sixty-two") || 4.659;
286
- const t3 = getWordTime("three") || 8.88;
287
-
288
- // Moment 1 Animation (approx 5s)
289
- tl.to(
290
- "#moment-1",
291
- {
292
- opacity: 1,
293
- scale: 1,
294
- duration: 0.8,
295
- ease: "elastic.out(1, 0.6)",
296
- onStart: () => gsap.set("#moment-1", { transformOrigin: "center center" }),
297
- },
298
- t1,
299
- );
300
-
301
- // Moment 1 Exit (before Moment 2)
302
- tl.to(
303
- "#moment-1",
304
- {
305
- opacity: 0,
306
- scale: 0.5,
307
- duration: 0.3,
308
- ease: "back.in(1.7)",
309
- },
310
- t2 - 0.4,
311
- );
312
-
313
- // Moment 2 Animation (approx 9s)
314
- tl.to(
315
- "#moment-2",
316
- {
317
- opacity: 1,
318
- scale: 1,
319
- duration: 0.8,
320
- ease: "elastic.out(1, 0.6)",
321
- onStart: () => gsap.set("#moment-2", { transformOrigin: "center center" }),
322
- },
323
- t2,
324
- );
325
-
326
- // Moment 2 Exit (before Moment 3)
327
- tl.to(
328
- "#moment-2",
329
- {
330
- opacity: 0,
331
- scale: 0.5,
332
- duration: 0.3,
333
- ease: "back.in(1.7)",
334
- },
335
- t3 - 0.4,
336
- );
337
-
338
- // Moment 3 Animation (approx 13s)
339
- tl.to(
340
- "#moment-3",
341
- {
342
- opacity: 1,
343
- scale: 1,
344
- duration: 0.8,
345
- ease: "elastic.out(1, 0.6)",
346
- onStart: () => gsap.set("#moment-3", { transformOrigin: "center center" }),
347
- },
348
- t3,
349
- );
350
-
351
- // Moment 3 Exit: 3 words before 'Hyperframes'
352
- // 'Editor' is at 15.079. 3 words before is 'So' at 14.239.
353
- tl.to(
354
- "#moment-3",
355
- {
356
- opacity: 0,
357
- scale: 0.5,
358
- duration: 0.3,
359
- ease: "back.in(1.7)",
360
- },
361
- 14.239,
362
- );
363
-
364
- // Ambient Motion (Finite)
365
- const totalDuration = 16.04;
366
- gsap.utils.toArray(".shape").forEach((shape, i) => {
367
- const stepDuration = 2 + i * 0.2;
368
- const steps = Math.ceil(totalDuration / stepDuration);
369
- for (let s = 0; s < steps; s++) {
370
- tl.to(
371
- shape,
372
- {
373
- y: s % 2 === 0 ? 15 : -15,
374
- rotation: s % 2 === 0 ? 5 : -5,
375
- duration: stepDuration,
376
- ease: "sine.inOut",
377
- },
378
- s * stepDuration,
379
- );
380
- }
381
- });
382
-
383
- window.__timelines = window.__timelines || {};
384
- window.__timelines["stats"] = tl;
385
- })();
386
- </script>
387
- </div>
388
- </template>