@hortonstudio/main 1.2.35 → 1.4.0

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,577 @@
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: false,
180
+ },
181
+ onComplete: () => {
182
+ if (textElement.splitTextInstance) {
183
+ textElement.splitTextInstance.revert();
184
+ delete textElement.splitTextInstance;
185
+ }
186
+ },
187
+ });
188
+
189
+ tl.to(chars, {
190
+ yPercent: 0,
191
+ duration: config.charSplit.duration,
192
+ stagger: config.charSplit.stagger,
193
+ ease: config.charSplit.ease,
194
+ });
195
+
196
+ activeAnimations.push({ timeline: tl, element: textElement });
197
+ } catch (error) {
198
+ console.warn("Error animating char split:", error);
199
+ }
200
+ });
201
+ },
164
202
  };
165
203
 
166
204
  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
- },
205
+ async initial() {
206
+ await waitForFonts();
207
+
208
+ if (prefersReducedMotion()) {
209
+ return;
210
+ }
190
211
 
191
- async animate() {
192
- await waitForFonts();
212
+ const elements = document.querySelectorAll(".a-word-split > *:first-child");
193
213
 
194
- if (prefersReducedMotion()) {
195
- return;
196
- }
214
+ elements.forEach((textElement) => {
215
+ try {
216
+ const split = SplitText.create(textElement, {
217
+ type: "words",
218
+ mask: "words",
219
+ wordsClass: "word",
220
+ });
221
+ textElement.splitTextInstance = split;
197
222
 
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 });
223
+ gsap.set(split.words, {
224
+ yPercent: config.wordSplit.yPercent,
222
225
  });
226
+ gsap.set(textElement, { autoAlpha: 1 });
227
+ } catch (error) {
228
+ console.warn("Error creating word split:", error);
229
+ gsap.set(textElement, { autoAlpha: 1 });
230
+ }
231
+ });
232
+ },
233
+
234
+ async animate() {
235
+ await waitForFonts();
236
+
237
+ if (prefersReducedMotion()) {
238
+ return;
223
239
  }
240
+
241
+ document
242
+ .querySelectorAll(".a-word-split > *:first-child")
243
+ .forEach((textElement) => {
244
+ try {
245
+ const words = textElement.querySelectorAll(".word");
246
+ const tl = gsap.timeline({
247
+ scrollTrigger: {
248
+ trigger: textElement,
249
+ start: config.wordSplit.start,
250
+ invalidateOnRefresh: false,
251
+ },
252
+ onComplete: () => {
253
+ if (textElement.splitTextInstance) {
254
+ textElement.splitTextInstance.revert();
255
+ delete textElement.splitTextInstance;
256
+ }
257
+ },
258
+ });
259
+
260
+ tl.to(words, {
261
+ yPercent: 0,
262
+ duration: config.wordSplit.duration,
263
+ stagger: config.wordSplit.stagger,
264
+ ease: config.wordSplit.ease,
265
+ });
266
+
267
+ activeAnimations.push({ timeline: tl, element: textElement });
268
+ } catch (error) {
269
+ console.warn("Error animating word split:", error);
270
+ }
271
+ });
272
+ },
224
273
  };
225
274
 
226
275
  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
- },
276
+ async initial() {
277
+ await waitForFonts();
250
278
 
251
- async animate() {
252
- await waitForFonts();
279
+ if (prefersReducedMotion()) {
280
+ return;
281
+ }
253
282
 
254
- if (prefersReducedMotion()) {
255
- return;
256
- }
283
+ const elements = document.querySelectorAll(".a-line-split > *:first-child");
257
284
 
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 });
285
+ elements.forEach((textElement) => {
286
+ try {
287
+ const split = SplitText.create(textElement, {
288
+ type: "lines",
289
+ mask: "lines",
290
+ linesClass: "line",
282
291
  });
292
+ textElement.splitTextInstance = split;
293
+
294
+ gsap.set(split.lines, {
295
+ yPercent: config.lineSplit.yPercent,
296
+ });
297
+ gsap.set(textElement, { autoAlpha: 1 });
298
+ } catch (error) {
299
+ console.warn("Error creating line split:", error);
300
+ gsap.set(textElement, { autoAlpha: 1 });
301
+ }
302
+ });
303
+ },
304
+
305
+ async animate() {
306
+ await waitForFonts();
307
+
308
+ if (prefersReducedMotion()) {
309
+ return;
283
310
  }
311
+
312
+ document
313
+ .querySelectorAll(".a-line-split > *:first-child")
314
+ .forEach((textElement) => {
315
+ try {
316
+ const lines = textElement.querySelectorAll(".line");
317
+ const tl = gsap.timeline({
318
+ scrollTrigger: {
319
+ trigger: textElement,
320
+ start: config.lineSplit.start,
321
+ invalidateOnRefresh: false,
322
+ },
323
+ onComplete: () => {
324
+ if (textElement.splitTextInstance) {
325
+ textElement.splitTextInstance.revert();
326
+ delete textElement.splitTextInstance;
327
+ }
328
+ },
329
+ });
330
+
331
+ tl.to(lines, {
332
+ yPercent: 0,
333
+ duration: config.lineSplit.duration,
334
+ stagger: config.lineSplit.stagger,
335
+ ease: config.lineSplit.ease,
336
+ });
337
+
338
+ activeAnimations.push({ timeline: tl, element: textElement });
339
+ } catch (error) {
340
+ console.warn("Error animating line split:", error);
341
+ }
342
+ });
343
+ },
284
344
  };
285
345
 
286
346
  const AppearAnimations = {
287
- async initial() {
288
- await waitForFonts();
347
+ async initial() {
348
+ await waitForFonts();
289
349
 
290
- if (prefersReducedMotion()) {
291
- return;
292
- }
350
+ if (prefersReducedMotion()) {
351
+ return;
352
+ }
293
353
 
294
- const elements = document.querySelectorAll('.a-appear');
295
- elements.forEach(element => {
296
- gsap.set(element, {
297
- y: config.appear.y,
298
- opacity: 0
299
- });
354
+ const elements = document.querySelectorAll(".a-appear");
355
+ elements.forEach((element) => {
356
+ try {
357
+ gsap.set(element, {
358
+ y: config.appear.y,
359
+ opacity: 0,
300
360
  });
301
- },
361
+ } catch (error) {
362
+ console.warn("Error setting appear initial state:", error);
363
+ }
364
+ });
365
+ },
302
366
 
303
- async animate() {
304
- await waitForFonts();
367
+ async animate() {
368
+ await waitForFonts();
305
369
 
306
- if (prefersReducedMotion()) {
307
- return;
308
- }
370
+ if (prefersReducedMotion()) {
371
+ return;
372
+ }
373
+
374
+ document.querySelectorAll(".a-appear").forEach((element) => {
375
+ try {
376
+ const tl = gsap.timeline({
377
+ scrollTrigger: {
378
+ trigger: element,
379
+ start: config.appear.start,
380
+ invalidateOnRefresh: false,
381
+ },
382
+ });
309
383
 
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 });
384
+ tl.to(element, {
385
+ y: 0,
386
+ opacity: 1,
387
+ duration: config.appear.duration,
388
+ ease: config.appear.ease,
327
389
  });
328
390
 
391
+ activeAnimations.push({ timeline: tl, element: element });
392
+ } catch (error) {
393
+ console.warn("Error animating appear:", error);
394
+ }
395
+ });
396
+ },
397
+ };
398
+
399
+ const RevealAnimations = {
400
+ async initial() {
401
+ await waitForFonts();
402
+
403
+ if (prefersReducedMotion()) {
404
+ return;
329
405
  }
406
+
407
+ const elements = document.querySelectorAll(".a-reveal");
408
+ elements.forEach((element) => {
409
+ try {
410
+ gsap.set(element, {
411
+ y: config.reveal.y,
412
+ opacity: 0,
413
+ });
414
+ } catch (error) {
415
+ console.warn("Error setting reveal initial state:", error);
416
+ }
417
+ });
418
+ },
419
+
420
+ async animate() {
421
+ await waitForFonts();
422
+
423
+ if (prefersReducedMotion()) {
424
+ return;
425
+ }
426
+
427
+ document.querySelectorAll(".a-reveal").forEach((element) => {
428
+ try {
429
+ const tl = gsap.timeline({
430
+ scrollTrigger: {
431
+ trigger: element,
432
+ start: config.reveal.start,
433
+ invalidateOnRefresh: false,
434
+ },
435
+ });
436
+
437
+ tl.to(element, {
438
+ y: 0,
439
+ opacity: 1,
440
+ duration: config.reveal.duration,
441
+ ease: config.reveal.ease,
442
+ });
443
+
444
+ activeAnimations.push({ timeline: tl, element: element });
445
+ } catch (error) {
446
+ console.warn("Error animating reveal:", error);
447
+ }
448
+ });
449
+ },
330
450
  };
331
451
 
332
452
  async function setInitialStates() {
333
- await Promise.all([
334
- CharSplitAnimations.initial(),
335
- WordSplitAnimations.initial(),
336
- LineSplitAnimations.initial(),
337
- AppearAnimations.initial()
338
- ]);
453
+ await Promise.all([
454
+ CharSplitAnimations.initial(),
455
+ WordSplitAnimations.initial(),
456
+ LineSplitAnimations.initial(),
457
+ AppearAnimations.initial(),
458
+ RevealAnimations.initial(),
459
+ ]);
339
460
  }
340
461
 
341
462
  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
- ]);
463
+ if (config.global.animationDelay > 0) {
464
+ await new Promise((resolve) =>
465
+ setTimeout(resolve, config.global.animationDelay * 1000),
466
+ );
467
+ }
468
+
469
+ await Promise.all([
470
+ CharSplitAnimations.animate(),
471
+ WordSplitAnimations.animate(),
472
+ LineSplitAnimations.animate(),
473
+ AppearAnimations.animate(),
474
+ RevealAnimations.animate(),
475
+ ]);
352
476
  }
353
477
 
354
478
  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
- }
479
+ // Prevent duplicate initialization
480
+ const elements = document.querySelectorAll(
481
+ "[data-hs-anim-text]:not([data-initialized])",
482
+ );
483
+ if (
484
+ elements.length === 0 &&
485
+ document.querySelectorAll(
486
+ ".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])",
487
+ ).length === 0
488
+ ) {
489
+ return { result: "anim-text already initialized", destroy: () => {} };
490
+ }
491
+
492
+ // Mark elements as initialized
493
+ elements.forEach((el) => el.setAttribute("data-initialized", "true"));
494
+ document
495
+ .querySelectorAll(
496
+ ".a-char-split, .a-word-split, .a-line-split, .a-appear, .a-reveal",
497
+ )
498
+ .forEach((el) => {
499
+ el.setAttribute("data-initialized", "true");
374
500
  });
375
501
 
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();
502
+ if (prefersReducedMotion()) {
503
+ // For reduced motion, just show elements without animation
504
+ showElementsWithoutAnimation();
505
+ } else {
506
+ await setInitialStates();
507
+ initAnimations();
508
+ }
509
+
510
+ // Set up resize handler with cleanup
511
+ let lastWidth = window.innerWidth;
512
+ resizeHandler = () => {
513
+ const currentWidth = window.innerWidth;
514
+ if (currentWidth !== lastWidth) {
515
+ lastWidth = currentWidth;
516
+ clearTimeout(resizeTimeout);
517
+ resizeTimeout = setTimeout(() => {
518
+ try {
519
+ ScrollTrigger.refresh();
520
+ } catch (error) {
521
+ console.warn("Error refreshing ScrollTrigger:", error);
385
522
  }
386
- };
523
+ }, 300);
524
+ }
525
+ };
526
+ window.addEventListener("resize", resizeHandler);
527
+
528
+ // Initialize API with proper checks
529
+ if (!window[API_NAME]) {
530
+ window[API_NAME] = {};
531
+ }
532
+
533
+ const api = window[API_NAME];
534
+ api.textAnimations = {
535
+ config: config,
536
+ updateConfig: updateConfig,
537
+ start: startTextAnimations,
538
+ kill: killTextAnimations,
539
+ restart: () => {
540
+ killTextAnimations();
541
+ startTextAnimations();
542
+ },
543
+ cleanup: () => {
544
+ killTextAnimations();
545
+ window.removeEventListener("resize", resizeHandler);
546
+ clearTimeout(resizeTimeout);
547
+ },
548
+ };
549
+
550
+ // Return destroy function
551
+ const destroy = () => {
552
+ // Kill all animations
553
+ killTextAnimations();
387
554
 
388
- return { result: 'anim-text initialized' };
389
- }
555
+ // Remove resize handler
556
+ if (resizeHandler) {
557
+ window.removeEventListener("resize", resizeHandler);
558
+ }
559
+
560
+ // Clear timeout
561
+ if (resizeTimeout) {
562
+ clearTimeout(resizeTimeout);
563
+ }
564
+
565
+ // Remove initialized markers
566
+ document.querySelectorAll("[data-initialized]").forEach((el) => {
567
+ el.removeAttribute("data-initialized");
568
+ });
569
+
570
+ // Clean up API
571
+ if (api.textAnimations) {
572
+ delete api.textAnimations;
573
+ }
574
+ };
575
+
576
+ return { result: "anim-text initialized", destroy };
577
+ }