@hortonstudio/main 1.2.35 → 1.4.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.
@@ -1,389 +1,604 @@
1
- const API_NAME = 'hsmain';
1
+ const API_NAME = "hsmain";
2
+
3
+ // Module-scoped variables for resize handling
4
+ let resizeTimeout;
5
+ let resizeHandler;
2
6
 
3
7
  // Check for reduced motion preference
4
8
  const prefersReducedMotion = () => {
5
- return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
9
+ return (
10
+ window.matchMedia &&
11
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches
12
+ );
6
13
  };
7
14
 
8
15
  const config = {
9
- global: {
10
- animationDelay: 0
11
- },
12
- wordSplit: {
13
- duration: 1.5,
14
- stagger: 0.05,
15
- yPercent: 110,
16
- ease: "power4.out",
17
- start: "top 97%"
18
- },
19
- lineSplit: {
20
- duration: 1.5,
21
- stagger: 0.1,
22
- yPercent: 110,
23
- ease: "power4.out",
24
- start: "top 97%"
25
- },
26
- charSplit: {
27
- duration: 1.2,
28
- stagger: 0.015,
29
- yPercent: 110,
30
- ease: "power4.out",
31
- start: "top 97%"
32
- },
33
- appear: {
34
- y: 50,
35
- duration: 1.5,
36
- ease: "power3.out",
37
- start: "top 97%"
38
- }
16
+ global: {
17
+ animationDelay: 0,
18
+ },
19
+ wordSplit: {
20
+ duration: 1.5,
21
+ stagger: 0.05,
22
+ yPercent: 110,
23
+ ease: "power4.out",
24
+ start: "top 97%",
25
+ },
26
+ lineSplit: {
27
+ duration: 1.5,
28
+ stagger: 0.1,
29
+ yPercent: 110,
30
+ ease: "power4.out",
31
+ start: "top 97%",
32
+ },
33
+ charSplit: {
34
+ duration: 1.2,
35
+ stagger: 0.015,
36
+ yPercent: 110,
37
+ ease: "power4.out",
38
+ start: "top 97%",
39
+ },
40
+ appear: {
41
+ y: 50,
42
+ duration: 1.5,
43
+ ease: "power3.out",
44
+ start: "top 97%",
45
+ },
46
+ reveal: {
47
+ y: 50,
48
+ duration: 1.5,
49
+ ease: "power3.out",
50
+ start: "top 97%",
51
+ },
39
52
  };
40
53
 
41
54
  function updateConfig(newConfig) {
42
- function deepMerge(target, source) {
43
- for (const key in source) {
44
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
45
- target[key] = target[key] || {};
46
- deepMerge(target[key], source[key]);
47
- } else {
48
- target[key] = source[key];
49
- }
50
- }
51
- return target;
55
+ function deepMerge(target, source) {
56
+ for (const key in source) {
57
+ if (
58
+ source[key] &&
59
+ typeof source[key] === "object" &&
60
+ !Array.isArray(source[key])
61
+ ) {
62
+ target[key] = target[key] || {};
63
+ deepMerge(target[key], source[key]);
64
+ } else {
65
+ target[key] = source[key];
66
+ }
52
67
  }
53
-
54
- deepMerge(config, newConfig);
68
+ return target;
69
+ }
70
+
71
+ deepMerge(config, newConfig);
55
72
  }
56
73
 
57
74
  function killTextAnimations() {
58
- activeAnimations.forEach(({ timeline, element }) => {
59
- if (timeline) {
60
- timeline.kill();
61
- }
62
- if (element?.splitTextInstance) {
63
- element.splitTextInstance.revert();
64
- }
65
- });
66
- activeAnimations.length = 0;
75
+ activeAnimations.forEach(({ timeline, element }) => {
76
+ if (timeline) {
77
+ timeline.kill();
78
+ }
79
+ if (element?.splitTextInstance) {
80
+ element.splitTextInstance.revert();
81
+ }
82
+ });
83
+ activeAnimations.length = 0;
67
84
  }
68
85
 
69
86
  function startTextAnimations() {
70
- if (prefersReducedMotion()) {
71
- // For reduced motion, just show elements without animation
72
- showElementsWithoutAnimation();
73
- return;
74
- }
75
-
76
- setInitialStates().then(() => {
77
- initAnimations();
78
- });
87
+ if (prefersReducedMotion()) {
88
+ // For reduced motion, just show elements without animation
89
+ showElementsWithoutAnimation();
90
+ return;
91
+ }
92
+
93
+ setInitialStates().then(() => {
94
+ initAnimations();
95
+ });
79
96
  }
80
97
 
81
98
  let activeAnimations = [];
82
99
 
83
- function waitForFonts() {
84
- return document.fonts.ready;
100
+ async function waitForFonts() {
101
+ try {
102
+ return await document.fonts.ready;
103
+ } catch (error) {
104
+ console.warn("Font loading error:", error);
105
+ return Promise.resolve();
106
+ }
85
107
  }
86
108
 
87
109
  function showElementsWithoutAnimation() {
88
- // Simply show all text elements without any animation or split text
89
- const allTextElements = [
90
- ...document.querySelectorAll(".a-char-split > *:first-child"),
91
- ...document.querySelectorAll(".a-word-split > *:first-child"),
92
- ...document.querySelectorAll(".a-line-split > *:first-child"),
93
- ...document.querySelectorAll('.a-appear')
94
- ];
95
-
96
- allTextElements.forEach(element => {
97
- gsap.set(element, {
98
- autoAlpha: 1,
99
- y: 0,
100
- yPercent: 0,
101
- opacity: 1
102
- });
103
- });
110
+ // Simply show all text elements without any animation or split text
111
+ const allTextElements = [
112
+ ...document.querySelectorAll(".a-char-split > *:first-child"),
113
+ ...document.querySelectorAll(".a-word-split > *:first-child"),
114
+ ...document.querySelectorAll(".a-line-split > *:first-child"),
115
+ ...document.querySelectorAll(".a-appear"),
116
+ ...document.querySelectorAll(".a-reveal"),
117
+ ];
118
+
119
+ allTextElements.forEach((element) => {
120
+ try {
121
+ gsap.set(element, {
122
+ autoAlpha: 1,
123
+ y: 0,
124
+ yPercent: 0,
125
+ opacity: 1,
126
+ });
127
+ } catch (error) {
128
+ console.warn("Error setting element visibility:", error);
129
+ }
130
+ });
104
131
  }
105
132
 
106
133
  const CharSplitAnimations = {
107
- async initial() {
108
- await waitForFonts();
109
-
110
- if (prefersReducedMotion()) {
111
- return;
112
- }
113
-
114
- const elements = document.querySelectorAll(".a-char-split > *:first-child");
115
-
116
- elements.forEach((textElement) => {
117
- const split = SplitText.create(textElement, {
118
- type: "words,chars",
119
- mask: "chars",
120
- charsClass: "char",
121
- });
122
- textElement.splitTextInstance = split;
123
-
124
- gsap.set(split.chars, {
125
- yPercent: config.charSplit.yPercent
126
- });
127
- gsap.set(textElement, { autoAlpha: 1 });
128
- });
129
- },
134
+ async initial() {
135
+ await waitForFonts();
136
+
137
+ if (prefersReducedMotion()) {
138
+ return;
139
+ }
130
140
 
131
- async animate() {
132
- await waitForFonts();
141
+ const elements = document.querySelectorAll(".a-char-split > *:first-child");
133
142
 
134
- if (prefersReducedMotion()) {
135
- return;
136
- }
143
+ elements.forEach((textElement) => {
144
+ try {
145
+ const split = SplitText.create(textElement, {
146
+ type: "words,chars",
147
+ mask: "chars",
148
+ charsClass: "char",
149
+ });
150
+ textElement.splitTextInstance = split;
137
151
 
138
- document.querySelectorAll(".a-char-split > *:first-child").forEach((textElement) => {
139
- const chars = textElement.querySelectorAll('.char');
140
- const tl = gsap.timeline({
141
- scrollTrigger: {
142
- trigger: textElement,
143
- start: config.charSplit.start,
144
- invalidateOnRefresh: false,
145
- },
146
- onComplete: () => {
147
- if (textElement.splitTextInstance) {
148
- textElement.splitTextInstance.revert();
149
- delete textElement.splitTextInstance;
150
- }
151
- }
152
- });
153
-
154
- tl.to(chars, {
155
- yPercent: 0,
156
- duration: config.charSplit.duration,
157
- stagger: config.charSplit.stagger,
158
- ease: config.charSplit.ease,
159
- });
160
-
161
- activeAnimations.push({ timeline: tl, element: textElement });
152
+ gsap.set(split.chars, {
153
+ yPercent: config.charSplit.yPercent,
162
154
  });
155
+ gsap.set(textElement, { autoAlpha: 1 });
156
+ } catch (error) {
157
+ console.warn("Error creating char split:", error);
158
+ gsap.set(textElement, { autoAlpha: 1 });
159
+ }
160
+ });
161
+ },
162
+
163
+ async animate() {
164
+ await waitForFonts();
165
+
166
+ if (prefersReducedMotion()) {
167
+ return;
163
168
  }
169
+
170
+ document
171
+ .querySelectorAll(".a-char-split > *:first-child")
172
+ .forEach((textElement) => {
173
+ try {
174
+ const chars = textElement.querySelectorAll(".char");
175
+ const tl = gsap.timeline({
176
+ scrollTrigger: {
177
+ trigger: textElement,
178
+ start: config.charSplit.start,
179
+ invalidateOnRefresh: true,
180
+ toggleActions: "play none none none",
181
+ once: true,
182
+ },
183
+ onComplete: () => {
184
+ if (textElement.splitTextInstance) {
185
+ textElement.splitTextInstance.revert();
186
+ delete textElement.splitTextInstance;
187
+ }
188
+ },
189
+ });
190
+
191
+ tl.to(chars, {
192
+ yPercent: 0,
193
+ duration: config.charSplit.duration,
194
+ stagger: config.charSplit.stagger,
195
+ ease: config.charSplit.ease,
196
+ });
197
+
198
+ activeAnimations.push({ timeline: tl, element: textElement });
199
+ } catch (error) {
200
+ console.warn("Error animating char split:", error);
201
+ }
202
+ });
203
+ },
164
204
  };
165
205
 
166
206
  const WordSplitAnimations = {
167
- async initial() {
168
- await waitForFonts();
169
-
170
- if (prefersReducedMotion()) {
171
- return;
172
- }
173
-
174
- const elements = document.querySelectorAll(".a-word-split > *:first-child");
175
-
176
- elements.forEach((textElement) => {
177
- const split = SplitText.create(textElement, {
178
- type: "words",
179
- mask: "words",
180
- wordsClass: "word",
181
- });
182
- textElement.splitTextInstance = split;
183
-
184
- gsap.set(split.words, {
185
- yPercent: config.wordSplit.yPercent
186
- });
187
- gsap.set(textElement, { autoAlpha: 1 });
188
- });
189
- },
207
+ async initial() {
208
+ await waitForFonts();
209
+
210
+ if (prefersReducedMotion()) {
211
+ return;
212
+ }
190
213
 
191
- async animate() {
192
- await waitForFonts();
214
+ const elements = document.querySelectorAll(".a-word-split > *:first-child");
193
215
 
194
- if (prefersReducedMotion()) {
195
- return;
196
- }
216
+ elements.forEach((textElement) => {
217
+ try {
218
+ const split = SplitText.create(textElement, {
219
+ type: "words",
220
+ mask: "words",
221
+ wordsClass: "word",
222
+ });
223
+ textElement.splitTextInstance = split;
197
224
 
198
- document.querySelectorAll(".a-word-split > *:first-child").forEach((textElement) => {
199
- const words = textElement.querySelectorAll('.word');
200
- const tl = gsap.timeline({
201
- scrollTrigger: {
202
- trigger: textElement,
203
- start: config.wordSplit.start,
204
- invalidateOnRefresh: false,
205
- },
206
- onComplete: () => {
207
- if (textElement.splitTextInstance) {
208
- textElement.splitTextInstance.revert();
209
- delete textElement.splitTextInstance;
210
- }
211
- }
212
- });
213
-
214
- tl.to(words, {
215
- yPercent: 0,
216
- duration: config.wordSplit.duration,
217
- stagger: config.wordSplit.stagger,
218
- ease: config.wordSplit.ease,
219
- });
220
-
221
- activeAnimations.push({ timeline: tl, element: textElement });
225
+ gsap.set(split.words, {
226
+ yPercent: config.wordSplit.yPercent,
222
227
  });
228
+ gsap.set(textElement, { autoAlpha: 1 });
229
+ } catch (error) {
230
+ console.warn("Error creating word split:", error);
231
+ gsap.set(textElement, { autoAlpha: 1 });
232
+ }
233
+ });
234
+ },
235
+
236
+ async animate() {
237
+ await waitForFonts();
238
+
239
+ if (prefersReducedMotion()) {
240
+ return;
223
241
  }
242
+
243
+ document
244
+ .querySelectorAll(".a-word-split > *:first-child")
245
+ .forEach((textElement) => {
246
+ try {
247
+ const words = textElement.querySelectorAll(".word");
248
+ const tl = gsap.timeline({
249
+ scrollTrigger: {
250
+ trigger: textElement,
251
+ start: config.wordSplit.start,
252
+ invalidateOnRefresh: true,
253
+ toggleActions: "play none none none",
254
+ once: true,
255
+ },
256
+ onComplete: () => {
257
+ if (textElement.splitTextInstance) {
258
+ textElement.splitTextInstance.revert();
259
+ delete textElement.splitTextInstance;
260
+ }
261
+ },
262
+ });
263
+
264
+ tl.to(words, {
265
+ yPercent: 0,
266
+ duration: config.wordSplit.duration,
267
+ stagger: config.wordSplit.stagger,
268
+ ease: config.wordSplit.ease,
269
+ });
270
+
271
+ activeAnimations.push({ timeline: tl, element: textElement });
272
+ } catch (error) {
273
+ console.warn("Error animating word split:", error);
274
+ }
275
+ });
276
+ },
224
277
  };
225
278
 
226
279
  const LineSplitAnimations = {
227
- async initial() {
228
- await waitForFonts();
229
-
230
- if (prefersReducedMotion()) {
231
- return;
232
- }
233
-
234
- const elements = document.querySelectorAll(".a-line-split > *:first-child");
235
-
236
- elements.forEach((textElement) => {
237
- const split = SplitText.create(textElement, {
238
- type: "lines",
239
- mask: "lines",
240
- linesClass: "line",
241
- });
242
- textElement.splitTextInstance = split;
243
-
244
- gsap.set(split.lines, {
245
- yPercent: config.lineSplit.yPercent
246
- });
247
- gsap.set(textElement, { autoAlpha: 1 });
248
- });
249
- },
280
+ async initial() {
281
+ await waitForFonts();
250
282
 
251
- async animate() {
252
- await waitForFonts();
283
+ if (prefersReducedMotion()) {
284
+ return;
285
+ }
253
286
 
254
- if (prefersReducedMotion()) {
255
- return;
256
- }
287
+ const elements = document.querySelectorAll(".a-line-split > *:first-child");
257
288
 
258
- document.querySelectorAll(".a-line-split > *:first-child").forEach((textElement) => {
259
- const lines = textElement.querySelectorAll('.line');
260
- const tl = gsap.timeline({
261
- scrollTrigger: {
262
- trigger: textElement,
263
- start: config.lineSplit.start,
264
- invalidateOnRefresh: false,
265
- },
266
- onComplete: () => {
267
- if (textElement.splitTextInstance) {
268
- textElement.splitTextInstance.revert();
269
- delete textElement.splitTextInstance;
270
- }
271
- }
272
- });
273
-
274
- tl.to(lines, {
275
- yPercent: 0,
276
- duration: config.lineSplit.duration,
277
- stagger: config.lineSplit.stagger,
278
- ease: config.lineSplit.ease,
279
- });
280
-
281
- activeAnimations.push({ timeline: tl, element: textElement });
289
+ elements.forEach((textElement) => {
290
+ try {
291
+ const split = SplitText.create(textElement, {
292
+ type: "lines",
293
+ mask: "lines",
294
+ linesClass: "line",
282
295
  });
296
+ textElement.splitTextInstance = split;
297
+
298
+ gsap.set(split.lines, {
299
+ yPercent: config.lineSplit.yPercent,
300
+ });
301
+ gsap.set(textElement, { autoAlpha: 1 });
302
+ } catch (error) {
303
+ console.warn("Error creating line split:", error);
304
+ gsap.set(textElement, { autoAlpha: 1 });
305
+ }
306
+ });
307
+ },
308
+
309
+ async animate() {
310
+ await waitForFonts();
311
+
312
+ if (prefersReducedMotion()) {
313
+ return;
283
314
  }
315
+
316
+ document
317
+ .querySelectorAll(".a-line-split > *:first-child")
318
+ .forEach((textElement) => {
319
+ try {
320
+ const lines = textElement.querySelectorAll(".line");
321
+ const tl = gsap.timeline({
322
+ scrollTrigger: {
323
+ trigger: textElement,
324
+ start: config.lineSplit.start,
325
+ invalidateOnRefresh: true,
326
+ toggleActions: "play none none none",
327
+ once: true,
328
+ },
329
+ onComplete: () => {
330
+ if (textElement.splitTextInstance) {
331
+ textElement.splitTextInstance.revert();
332
+ delete textElement.splitTextInstance;
333
+ }
334
+ },
335
+ });
336
+
337
+ tl.to(lines, {
338
+ yPercent: 0,
339
+ duration: config.lineSplit.duration,
340
+ stagger: config.lineSplit.stagger,
341
+ ease: config.lineSplit.ease,
342
+ });
343
+
344
+ activeAnimations.push({ timeline: tl, element: textElement });
345
+ } catch (error) {
346
+ console.warn("Error animating line split:", error);
347
+ }
348
+ });
349
+ },
284
350
  };
285
351
 
286
352
  const AppearAnimations = {
287
- async initial() {
288
- await waitForFonts();
353
+ async initial() {
354
+ await waitForFonts();
289
355
 
290
- if (prefersReducedMotion()) {
291
- return;
292
- }
356
+ if (prefersReducedMotion()) {
357
+ return;
358
+ }
293
359
 
294
- const elements = document.querySelectorAll('.a-appear');
295
- elements.forEach(element => {
296
- gsap.set(element, {
297
- y: config.appear.y,
298
- opacity: 0
299
- });
360
+ const elements = document.querySelectorAll(".a-appear");
361
+ elements.forEach((element) => {
362
+ try {
363
+ gsap.set(element, {
364
+ y: config.appear.y,
365
+ opacity: 0,
300
366
  });
301
- },
367
+ } catch (error) {
368
+ console.warn("Error setting appear initial state:", error);
369
+ }
370
+ });
371
+ },
302
372
 
303
- async animate() {
304
- await waitForFonts();
373
+ async animate() {
374
+ await waitForFonts();
305
375
 
306
- if (prefersReducedMotion()) {
307
- return;
308
- }
376
+ if (prefersReducedMotion()) {
377
+ return;
378
+ }
379
+
380
+ document.querySelectorAll(".a-appear").forEach((element) => {
381
+ try {
382
+ const tl = gsap.timeline({
383
+ scrollTrigger: {
384
+ trigger: element,
385
+ start: config.appear.start,
386
+ invalidateOnRefresh: true,
387
+ toggleActions: "play none none none",
388
+ once: true,
389
+ },
390
+ });
309
391
 
310
- document.querySelectorAll('.a-appear').forEach(element => {
311
- const tl = gsap.timeline({
312
- scrollTrigger: {
313
- trigger: element,
314
- start: config.appear.start,
315
- invalidateOnRefresh: false,
316
- }
317
- });
318
-
319
- tl.to(element, {
320
- y: 0,
321
- opacity: 1,
322
- duration: config.appear.duration,
323
- ease: config.appear.ease
324
- });
325
-
326
- activeAnimations.push({ timeline: tl, element: element });
392
+ tl.to(element, {
393
+ y: 0,
394
+ opacity: 1,
395
+ duration: config.appear.duration,
396
+ ease: config.appear.ease,
327
397
  });
328
398
 
399
+ activeAnimations.push({ timeline: tl, element: element });
400
+ } catch (error) {
401
+ console.warn("Error animating appear:", error);
402
+ }
403
+ });
404
+ },
405
+ };
406
+
407
+ const RevealAnimations = {
408
+ async initial() {
409
+ await waitForFonts();
410
+
411
+ if (prefersReducedMotion()) {
412
+ return;
329
413
  }
414
+
415
+ const elements = document.querySelectorAll(".a-reveal");
416
+ elements.forEach((element) => {
417
+ try {
418
+ gsap.set(element, {
419
+ y: config.reveal.y,
420
+ opacity: 0,
421
+ });
422
+ } catch (error) {
423
+ console.warn("Error setting reveal initial state:", error);
424
+ }
425
+ });
426
+ },
427
+
428
+ async animate() {
429
+ await waitForFonts();
430
+
431
+ if (prefersReducedMotion()) {
432
+ return;
433
+ }
434
+
435
+ document.querySelectorAll(".a-reveal").forEach((element) => {
436
+ try {
437
+ const tl = gsap.timeline({
438
+ scrollTrigger: {
439
+ trigger: element,
440
+ start: config.reveal.start,
441
+ invalidateOnRefresh: true,
442
+ toggleActions: "play none none none",
443
+ once: true,
444
+ },
445
+ });
446
+
447
+ tl.to(element, {
448
+ y: 0,
449
+ opacity: 1,
450
+ duration: config.reveal.duration,
451
+ ease: config.reveal.ease,
452
+ });
453
+
454
+ activeAnimations.push({ timeline: tl, element: element });
455
+ } catch (error) {
456
+ console.warn("Error animating reveal:", error);
457
+ }
458
+ });
459
+ },
330
460
  };
331
461
 
332
462
  async function setInitialStates() {
333
- await Promise.all([
334
- CharSplitAnimations.initial(),
335
- WordSplitAnimations.initial(),
336
- LineSplitAnimations.initial(),
337
- AppearAnimations.initial()
338
- ]);
463
+ await Promise.all([
464
+ CharSplitAnimations.initial(),
465
+ WordSplitAnimations.initial(),
466
+ LineSplitAnimations.initial(),
467
+ AppearAnimations.initial(),
468
+ RevealAnimations.initial(),
469
+ ]);
339
470
  }
340
471
 
341
472
  async function initAnimations() {
342
- if (config.global.animationDelay > 0) {
343
- await new Promise(resolve => setTimeout(resolve, config.global.animationDelay * 1000));
344
- }
345
-
346
- await Promise.all([
347
- CharSplitAnimations.animate(),
348
- WordSplitAnimations.animate(),
349
- LineSplitAnimations.animate(),
350
- AppearAnimations.animate()
351
- ]);
473
+ if (config.global.animationDelay > 0) {
474
+ await new Promise((resolve) =>
475
+ setTimeout(resolve, config.global.animationDelay * 1000),
476
+ );
477
+ }
478
+
479
+ await Promise.all([
480
+ CharSplitAnimations.animate(),
481
+ WordSplitAnimations.animate(),
482
+ LineSplitAnimations.animate(),
483
+ AppearAnimations.animate(),
484
+ RevealAnimations.animate(),
485
+ ]);
352
486
  }
353
487
 
354
488
  export async function init() {
355
- if (prefersReducedMotion()) {
356
- // For reduced motion, just show elements without animation
357
- showElementsWithoutAnimation();
358
- } else {
359
- await setInitialStates();
360
- initAnimations();
361
- }
362
-
363
- let resizeTimeout;
364
- let lastWidth = window.innerWidth;
365
- window.addEventListener('resize', () => {
366
- const currentWidth = window.innerWidth;
367
- if (currentWidth !== lastWidth) {
368
- lastWidth = currentWidth;
369
- clearTimeout(resizeTimeout);
370
- resizeTimeout = setTimeout(() => {
371
- ScrollTrigger.refresh();
372
- }, 300);
373
- }
489
+ // Prevent duplicate initialization
490
+ const elements = document.querySelectorAll(
491
+ "[data-hs-anim-text]:not([data-initialized])",
492
+ );
493
+ if (
494
+ elements.length === 0 &&
495
+ document.querySelectorAll(
496
+ ".a-char-split:not([data-initialized]), .a-word-split:not([data-initialized]), .a-line-split:not([data-initialized]), .a-appear:not([data-initialized]), .a-reveal:not([data-initialized])",
497
+ ).length === 0
498
+ ) {
499
+ return { result: "anim-text already initialized", destroy: () => {} };
500
+ }
501
+
502
+ // Mark elements as initialized
503
+ elements.forEach((el) => el.setAttribute("data-initialized", "true"));
504
+ document
505
+ .querySelectorAll(
506
+ ".a-char-split, .a-word-split, .a-line-split, .a-appear, .a-reveal",
507
+ )
508
+ .forEach((el) => {
509
+ el.setAttribute("data-initialized", "true");
374
510
  });
375
511
 
376
- const api = window[API_NAME] || {};
377
- api.textAnimations = {
378
- config: config,
379
- updateConfig: updateConfig,
380
- start: startTextAnimations,
381
- kill: killTextAnimations,
382
- restart: () => {
383
- killTextAnimations();
384
- startTextAnimations();
512
+ if (prefersReducedMotion()) {
513
+ // For reduced motion, just show elements without animation
514
+ showElementsWithoutAnimation();
515
+ } else {
516
+ await setInitialStates();
517
+ initAnimations();
518
+ }
519
+
520
+ // Set up resize handler with cleanup
521
+ let lastWidth = window.innerWidth;
522
+ resizeHandler = () => {
523
+ const currentWidth = window.innerWidth;
524
+ if (currentWidth !== lastWidth) {
525
+ lastWidth = currentWidth;
526
+ clearTimeout(resizeTimeout);
527
+ resizeTimeout = setTimeout(() => {
528
+ try {
529
+ ScrollTrigger.refresh();
530
+ } catch (error) {
531
+ console.warn("Error refreshing ScrollTrigger:", error);
385
532
  }
386
- };
533
+ }, 300);
534
+ }
535
+ };
536
+ window.addEventListener("resize", resizeHandler);
537
+
538
+ // Add page load handler for proper ScrollTrigger refresh timing
539
+ const handlePageLoad = () => {
540
+ setTimeout(() => {
541
+ try {
542
+ ScrollTrigger.refresh();
543
+ } catch (error) {
544
+ console.warn("Error refreshing ScrollTrigger on page load:", error);
545
+ }
546
+ }, 100);
547
+ };
548
+
549
+ if (document.readyState === 'complete') {
550
+ handlePageLoad();
551
+ } else {
552
+ window.addEventListener('load', handlePageLoad);
553
+ }
554
+
555
+ // Initialize API with proper checks
556
+ if (!window[API_NAME]) {
557
+ window[API_NAME] = {};
558
+ }
559
+
560
+ const api = window[API_NAME];
561
+ api.textAnimations = {
562
+ config: config,
563
+ updateConfig: updateConfig,
564
+ start: startTextAnimations,
565
+ kill: killTextAnimations,
566
+ restart: () => {
567
+ killTextAnimations();
568
+ startTextAnimations();
569
+ },
570
+ cleanup: () => {
571
+ killTextAnimations();
572
+ window.removeEventListener("resize", resizeHandler);
573
+ clearTimeout(resizeTimeout);
574
+ },
575
+ };
576
+
577
+ // Return destroy function
578
+ const destroy = () => {
579
+ // Kill all animations
580
+ killTextAnimations();
387
581
 
388
- return { result: 'anim-text initialized' };
389
- }
582
+ // Remove resize handler
583
+ if (resizeHandler) {
584
+ window.removeEventListener("resize", resizeHandler);
585
+ }
586
+
587
+ // Clear timeout
588
+ if (resizeTimeout) {
589
+ clearTimeout(resizeTimeout);
590
+ }
591
+
592
+ // Remove initialized markers
593
+ document.querySelectorAll("[data-initialized]").forEach((el) => {
594
+ el.removeAttribute("data-initialized");
595
+ });
596
+
597
+ // Clean up API
598
+ if (api.textAnimations) {
599
+ delete api.textAnimations;
600
+ }
601
+ };
602
+
603
+ return { result: "anim-text initialized", destroy };
604
+ }