fancoolo-fx 1.7.0 → 1.8.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 (4) hide show
  1. package/README.md +26 -1
  2. package/package.json +1 -1
  3. package/readme.txt +18 -0
  4. package/src/fx.js +95 -154
package/README.md CHANGED
@@ -169,11 +169,36 @@ Set `trigger: 'scroll'` to enable ScrollTrigger. Pass `scrollTrigger: { trigger:
169
169
 
170
170
  **Resize handling:** Text-based effects (`textReveal`, `typeWriter`, `splitWords`) automatically re-split when the browser width changes. After one-shot animations complete, the SplitText DOM is reverted so text reflows naturally.
171
171
 
172
+ ## Preventing Flash of Unstyled Content (FOUC)
173
+
174
+ FX uses GSAP's `autoAlpha` internally, so elements with `visibility: hidden` are revealed automatically when their animation starts. Add this CSS **before** any content renders to prevent the flash where elements appear briefly before JS loads:
175
+
176
+ ```css
177
+ .fx-text-reveal-pl,.fx-text-reveal-st,.fx-text-reveal,
178
+ .fx-reveal-pl,.fx-reveal-st,.fx-reveal,
179
+ .fx-spin-reveal-pl,.fx-spin-reveal-st,.fx-spin-reveal,
180
+ .fx-bg-reveal-pl,.fx-bg-reveal-st,.fx-bg-reveal,
181
+ .fx-scale-in-pl,.fx-scale-in-st,.fx-scale-in,
182
+ .fx-fade-in-pl,.fx-fade-in-st,.fx-fade-in,
183
+ .fx-blur-in-pl,.fx-blur-in-st,.fx-blur-in,
184
+ .fx-clip-up-pl,.fx-clip-up-st,.fx-clip-up,
185
+ .fx-clip-down-pl,.fx-clip-down-st,.fx-clip-down,
186
+ .fx-tilt-in-st,.fx-tilt-in,
187
+ .fx-type-writer-pl,.fx-type-writer-st,.fx-type-writer,
188
+ .fx-draw-svg-pl,.fx-draw-svg-st,.fx-draw-svg,.fx-draw-svg-scrub,
189
+ .fx-split-words-pl,.fx-split-words-st,.fx-split-words,
190
+ .fx-slide-left-pl,.fx-slide-left-st,.fx-slide-left,
191
+ .fx-slide-right-pl,.fx-slide-right-st,.fx-slide-right{visibility:hidden}
192
+ ```
193
+
194
+ **WordPress:** The plugin injects this CSS automatically in the `<head>` — no action needed.
195
+
172
196
  ## Using in a New Project
173
197
 
174
198
  1. Copy this repo (or `npm install`)
175
199
  2. Add the 4 script tags (gsap, ScrollTrigger, SplitText, fx.js)
176
- 3. Add `.fx-*` classes in your HTML
200
+ 3. Add the FOUC prevention CSS in your `<head>` (see above)
201
+ 4. Add `.fx-*` classes in your HTML
177
202
 
178
203
  For compound sequences, create a project-specific JS file loaded after fx.js:
179
204
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fancoolo-fx",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "A class-driven GSAP animation wrapper for WordPress and static sites.",
5
5
  "main": "src/fx.js",
6
6
  "homepage": "https://krstivoja.github.io/fancoolo-fx/",
package/readme.txt CHANGED
@@ -68,6 +68,24 @@ Yes. Use the `fx-start-[top center]` modifier class, or set `scrollStart` in the
68
68
 
69
69
  == Changelog ==
70
70
 
71
+ = 1.8.1 =
72
+ * Fix: Removed split.revert() calls from textReveal, typeWriter, and splitWords — revert destroys JS state (event listeners, injected DOM) added after SplitText ran
73
+ * Fix: textReveal resize re-splitting fully handled by autoSplit, no manual revert needed
74
+
75
+ = 1.8.0 =
76
+ * Refactor: textReveal uses native SplitText `autoSplit`, `mask: "lines"`, and `onSplit` — removes manual overflow wrappers and resize handler
77
+ * Refactor: Responsive and reduced-motion handling via `gsap.matchMedia()` — animations auto-revert when conditions change
78
+ * Refactor: Idempotent `init()` using persistent WeakSet — safe to call multiple times without double-animating
79
+ * Refactor: Scrub effects (tiltIn, parallax, drawSVG scrub) routed through `buildScrollTrigger()` — now support debug markers and `fx-start-[...]` overrides
80
+ * Removed: Manual resize listener, `_splitRegistry`, `document.fonts.ready` blocking — all handled natively by GSAP
81
+ * Enhancement: `FX.refresh()` simplified to `ScrollTrigger.refresh()`
82
+
83
+ = 1.7.1 =
84
+ * Fix: FOUC prevention — all effects now use autoAlpha instead of opacity, elements start with visibility:hidden and are revealed by GSAP
85
+ * New: WordPress plugin injects visibility:hidden CSS automatically in the head
86
+ * New: Text-based effects set parent visibility before animating children to prevent flash
87
+ * Enhancement: clipUp/clipDown now include autoAlpha for consistent FOUC handling
88
+
71
89
  = 1.7.0 =
72
90
  * Fix: Text-based effects (textReveal, typeWriter, splitWords) now re-split on browser resize — line breaks stay correct at every viewport width
73
91
  * New: SplitText is reverted after one-shot animations complete — text reflows naturally without extra DOM wrappers
package/src/fx.js CHANGED
@@ -109,6 +109,11 @@
109
109
  slideIn: { duration: 1, ease: 'power3.out' },
110
110
  };
111
111
 
112
+ // ── State ───────────────────────────────────
113
+
114
+ var _animated = new WeakSet();
115
+ var _mm = gsap.matchMedia();
116
+
112
117
  // ── Helpers ──────────────────────────────────
113
118
 
114
119
  function getClassModifier(el, name, fallback) {
@@ -157,89 +162,36 @@
157
162
  return st;
158
163
  }
159
164
 
160
- // ── SplitText resize handling ───────────────
161
-
162
- var _splitRegistry = [];
163
- var _lastWidth = window.innerWidth;
164
- var _resizeTimer;
165
-
166
- function registerSplit(entry) {
167
- _splitRegistry.push(entry);
168
- }
169
-
170
- function unregisterSplit(entry) {
171
- var idx = _splitRegistry.indexOf(entry);
172
- if (idx > -1) _splitRegistry.splice(idx, 1);
173
- }
174
-
175
- function refreshSplits() {
176
- if (_splitRegistry.length === 0) return;
177
-
178
- var pending = [];
179
-
180
- for (var i = _splitRegistry.length - 1; i >= 0; i--) {
181
- var entry = _splitRegistry[i];
182
- if (entry.tween) entry.tween.kill();
183
- if (entry.split) entry.split.revert();
184
- pending.push(entry);
185
- }
186
-
187
- _splitRegistry.length = 0;
188
-
189
- pending.forEach(function (entry) {
190
- entry.effectFn(entry.el, entry.opts);
191
- });
192
-
193
- ScrollTrigger.refresh();
194
- }
195
-
196
- window.addEventListener('resize', function () {
197
- if (window.innerWidth === _lastWidth) return;
198
- _lastWidth = window.innerWidth;
199
- clearTimeout(_resizeTimer);
200
- _resizeTimer = setTimeout(refreshSplits, 200);
201
- });
202
-
203
165
  // ── Effects ──────────────────────────────────
204
166
 
205
167
  function textReveal(el, opts) {
206
168
  opts = opts || {};
207
169
  var o = resolveOptions(el, 'textReveal', opts);
170
+ var isScroll = opts.trigger === 'scroll' || opts.scrollTrigger;
171
+
172
+ gsap.set(el, { visibility: 'inherit' });
173
+
174
+ SplitText.create(el, {
175
+ type: 'lines',
176
+ mask: 'lines',
177
+ autoSplit: true,
178
+ onSplit: function (self) {
179
+ var tweenVars = {
180
+ y: '100%',
181
+ autoAlpha: 0,
182
+ duration: o.duration,
183
+ ease: o.ease,
184
+ stagger: o.stagger,
185
+ delay: o.delay,
186
+ };
208
187
 
209
- var split = new SplitText(el, { type: 'lines', linesClass: 'line-wrapper' });
188
+ if (isScroll) {
189
+ tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
190
+ }
210
191
 
211
- split.lines.forEach(function (line) {
212
- var wrapper = document.createElement('div');
213
- wrapper.style.overflow = 'hidden';
214
- line.parentNode.insertBefore(wrapper, line);
215
- wrapper.appendChild(line);
192
+ return gsap.from(self.lines, tweenVars);
193
+ },
216
194
  });
217
-
218
- var isOneShot = !(opts.trigger === 'scroll' || opts.scrollTrigger) || config.scrollOnce;
219
- var entry = { el: el, split: split, tween: null, effectFn: textReveal, opts: opts };
220
-
221
- var tweenVars = {
222
- y: '100%',
223
- opacity: 0,
224
- duration: o.duration,
225
- ease: o.ease,
226
- stagger: o.stagger,
227
- delay: o.delay,
228
- };
229
-
230
- if (isOneShot) {
231
- tweenVars.onComplete = function () {
232
- split.revert();
233
- unregisterSplit(entry);
234
- };
235
- }
236
-
237
- if (opts.trigger === 'scroll' || opts.scrollTrigger) {
238
- tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
239
- }
240
-
241
- entry.tween = gsap.from(split.lines, tweenVars);
242
- registerSplit(entry);
243
195
  }
244
196
 
245
197
  function reveal(el, opts) {
@@ -248,7 +200,7 @@
248
200
 
249
201
  var tweenVars = {
250
202
  y: opts.y != null ? opts.y : 80,
251
- opacity: 0,
203
+ autoAlpha: 0,
252
204
  duration: o.duration,
253
205
  ease: o.ease,
254
206
  delay: o.delay,
@@ -268,7 +220,7 @@
268
220
  var tweenVars = {
269
221
  rotation: opts.rotation != null ? opts.rotation : -30,
270
222
  scale: opts.scale != null ? opts.scale : 0.9,
271
- opacity: 0,
223
+ autoAlpha: 0,
272
224
  duration: o.duration,
273
225
  ease: o.ease,
274
226
  delay: o.delay,
@@ -287,7 +239,7 @@
287
239
 
288
240
  var tweenVars = {
289
241
  y: '100%',
290
- opacity: 0,
242
+ autoAlpha: 0,
291
243
  duration: o.duration,
292
244
  ease: o.ease,
293
245
  delay: o.delay,
@@ -306,7 +258,7 @@
306
258
 
307
259
  var tweenVars = {
308
260
  scale: opts.scale != null ? opts.scale : 0.92,
309
- opacity: 0,
261
+ autoAlpha: 0,
310
262
  duration: o.duration,
311
263
  ease: o.ease,
312
264
  delay: o.delay,
@@ -324,7 +276,7 @@
324
276
  var o = resolveOptions(el, 'fadeIn', opts);
325
277
 
326
278
  var tweenVars = {
327
- opacity: 0,
279
+ autoAlpha: 0,
328
280
  scale: opts.scale != null ? opts.scale : 0.95,
329
281
  duration: o.duration,
330
282
  ease: o.ease,
@@ -344,7 +296,7 @@
344
296
 
345
297
  var tweenVars = {
346
298
  filter: 'blur(' + (opts.blur != null ? opts.blur : 12) + 'px)',
347
- opacity: 0,
299
+ autoAlpha: 0,
348
300
  duration: o.duration,
349
301
  ease: o.ease,
350
302
  delay: o.delay,
@@ -363,6 +315,7 @@
363
315
 
364
316
  var tweenVars = {
365
317
  clipPath: 'inset(100% 0 0 0)',
318
+ autoAlpha: 0,
366
319
  duration: o.duration,
367
320
  ease: o.ease,
368
321
  delay: o.delay,
@@ -381,6 +334,7 @@
381
334
 
382
335
  var tweenVars = {
383
336
  clipPath: 'inset(0 0 100% 0)',
337
+ autoAlpha: 0,
384
338
  duration: o.duration,
385
339
  ease: o.ease,
386
340
  delay: o.delay,
@@ -397,24 +351,24 @@
397
351
  opts = opts || {};
398
352
  var o = resolveOptions(el, 'tiltIn', opts);
399
353
 
354
+ var st = buildScrollTrigger(el, opts.scrollTrigger || {});
355
+ st.end = opts.end || 'top 20%';
356
+ st.scrub = opts.scrub != null ? opts.scrub : 0.6;
357
+ delete st.once;
358
+
400
359
  gsap.fromTo(el, {
401
360
  rotationX: opts.rotationX != null ? opts.rotationX : 45,
402
361
  scale: opts.scale != null ? opts.scale : 0.8,
403
- opacity: opts.opacity != null ? opts.opacity : 0,
362
+ autoAlpha: opts.opacity != null ? opts.opacity : 0,
404
363
  transformPerspective: opts.perspective != null ? opts.perspective : 1000,
405
364
  transformOrigin: opts.transformOrigin || 'center bottom',
406
365
  }, {
407
366
  rotationX: 0,
408
367
  scale: 1,
409
- opacity: 1,
368
+ autoAlpha: 1,
410
369
  transformPerspective: 1000,
411
370
  ease: o.ease,
412
- scrollTrigger: {
413
- trigger: (opts.scrollTrigger && opts.scrollTrigger.trigger) || el,
414
- start: config.scrollStart || 'top 85%',
415
- end: opts.end || 'top 20%',
416
- scrub: opts.scrub != null ? opts.scrub : 0.6,
417
- },
371
+ scrollTrigger: st,
418
372
  });
419
373
  }
420
374
 
@@ -422,38 +376,29 @@
422
376
  opts = opts || {};
423
377
  var o = resolveOptions(el, 'typeWriter', opts);
424
378
 
379
+ gsap.set(el, { visibility: 'inherit' });
425
380
  var split = new SplitText(el, { type: 'chars' });
426
- gsap.set(split.chars, { opacity: 0 });
427
-
428
- var isOneShot = !(opts.trigger === 'scroll' || opts.scrollTrigger) || config.scrollOnce;
429
- var entry = { el: el, split: split, tween: null, effectFn: typeWriter, opts: opts };
381
+ gsap.set(split.chars, { autoAlpha: 0 });
430
382
 
431
383
  var tweenVars = {
432
- opacity: 1,
384
+ autoAlpha: 1,
433
385
  duration: o.duration,
434
386
  ease: o.ease,
435
387
  stagger: o.stagger,
436
388
  delay: o.delay,
437
389
  };
438
390
 
439
- if (isOneShot) {
440
- tweenVars.onComplete = function () {
441
- split.revert();
442
- unregisterSplit(entry);
443
- };
444
- }
445
-
446
391
  if (opts.trigger === 'scroll' || opts.scrollTrigger) {
447
392
  tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
448
393
  }
449
394
 
450
- entry.tween = gsap.to(split.chars, tweenVars);
451
- registerSplit(entry);
395
+ gsap.to(split.chars, tweenVars);
452
396
  }
453
397
 
454
398
  function drawSVG(el, opts) {
455
399
  opts = opts || {};
456
400
  var o = resolveOptions(el, 'drawSVG', opts);
401
+ gsap.set(el, { visibility: 'inherit' });
457
402
 
458
403
  var paths = el.tagName === 'path' || el.tagName === 'line' || el.tagName === 'circle' || el.tagName === 'polyline'
459
404
  ? [el]
@@ -471,15 +416,15 @@
471
416
  // Scrub mode: SVG draws as user scrolls (class fx-scrub-[0.6] or opts.scrub)
472
417
  var scrubVal = getClassModifier(el, 'scrub', opts.scrub != null ? opts.scrub : null);
473
418
  if (scrubVal !== null) {
419
+ var st = buildScrollTrigger(el, opts.scrollTrigger || {});
420
+ st.end = opts.end || 'top 20%';
421
+ st.scrub = scrubVal === true || scrubVal === 'true' ? true : scrubVal;
422
+ delete st.once;
423
+
474
424
  gsap.to(paths, {
475
425
  strokeDashoffset: 0,
476
426
  ease: o.ease,
477
- scrollTrigger: {
478
- trigger: (opts.scrollTrigger && opts.scrollTrigger.trigger) || el,
479
- start: config.scrollStart || 'top 85%',
480
- end: opts.end || 'top 20%',
481
- scrub: scrubVal === true || scrubVal === 'true' ? true : scrubVal,
482
- },
427
+ scrollTrigger: st,
483
428
  });
484
429
  return;
485
430
  }
@@ -503,17 +448,17 @@
503
448
  // Read y from modifier class fx-y-[80] or opts or default 50
504
449
  var yShift = getClassModifier(el, 'y', opts.y != null ? opts.y : 50);
505
450
 
451
+ var st = buildScrollTrigger(el, opts.scrollTrigger || {});
452
+ st.end = opts.end || 'bottom top';
453
+ st.scrub = opts.scrub != null ? opts.scrub : true;
454
+ delete st.once;
455
+
506
456
  gsap.fromTo(el, {
507
457
  y: -yShift,
508
458
  }, {
509
459
  y: yShift,
510
460
  ease: 'none',
511
- scrollTrigger: {
512
- trigger: (opts.scrollTrigger && opts.scrollTrigger.trigger) || el,
513
- start: config.scrollStart || 'top 85%',
514
- end: opts.end || 'bottom top',
515
- scrub: opts.scrub != null ? opts.scrub : true,
516
- },
461
+ scrollTrigger: st,
517
462
  });
518
463
  }
519
464
 
@@ -521,33 +466,23 @@
521
466
  opts = opts || {};
522
467
  var o = resolveOptions(el, 'splitWords', opts);
523
468
 
469
+ gsap.set(el, { visibility: 'inherit' });
524
470
  var split = new SplitText(el, { type: 'words' });
525
471
 
526
- var isOneShot = !(opts.trigger === 'scroll' || opts.scrollTrigger) || config.scrollOnce;
527
- var entry = { el: el, split: split, tween: null, effectFn: splitWords, opts: opts };
528
-
529
472
  var tweenVars = {
530
473
  y: opts.y != null ? opts.y : 30,
531
- opacity: 0,
474
+ autoAlpha: 0,
532
475
  duration: o.duration,
533
476
  ease: o.ease,
534
477
  stagger: o.stagger,
535
478
  delay: o.delay,
536
479
  };
537
480
 
538
- if (isOneShot) {
539
- tweenVars.onComplete = function () {
540
- split.revert();
541
- unregisterSplit(entry);
542
- };
543
- }
544
-
545
481
  if (opts.trigger === 'scroll' || opts.scrollTrigger) {
546
482
  tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
547
483
  }
548
484
 
549
- entry.tween = gsap.from(split.words, tweenVars);
550
- registerSplit(entry);
485
+ gsap.from(split.words, tweenVars);
551
486
  }
552
487
 
553
488
  function slideIn(el, opts) {
@@ -558,7 +493,7 @@
558
493
 
559
494
  var tweenVars = {
560
495
  x: direction === 'left' ? -xVal : xVal,
561
- opacity: 0,
496
+ autoAlpha: 0,
562
497
  duration: o.duration,
563
498
  ease: o.ease,
564
499
  delay: o.delay,
@@ -644,7 +579,6 @@
644
579
  // ── Init ────────────────────────────────────
645
580
 
646
581
  function init() {
647
- var processed = new Set();
648
582
 
649
583
  Object.keys(effects).forEach(function (name) {
650
584
  var fn = effects[name];
@@ -654,8 +588,9 @@
654
588
  plGroups.forEach(function (group) {
655
589
  group = group.filter(function (el) { return !isExcluded(el); });
656
590
  group.forEach(function (el, i) {
591
+ if (_animated.has(el)) return;
657
592
  fn(el, { delay: i * 0.15 });
658
- processed.add(el);
593
+ _animated.add(el);
659
594
  });
660
595
  });
661
596
 
@@ -667,12 +602,13 @@
667
602
  stGroups.forEach(function (group) {
668
603
  group = group.filter(function (el) { return !isExcluded(el); });
669
604
  group.forEach(function (el, i) {
605
+ if (_animated.has(el)) return;
670
606
  fn(el, {
671
607
  trigger: 'scroll',
672
608
  delay: i * 0.15,
673
609
  scrollTrigger: { trigger: el },
674
610
  });
675
- processed.add(el);
611
+ _animated.add(el);
676
612
  });
677
613
  });
678
614
 
@@ -682,18 +618,19 @@
682
618
  if (config.sectionSelector) {
683
619
  document.querySelectorAll(config.sectionSelector).forEach(function (section) {
684
620
  var bareEls = Array.from(section.querySelectorAll('.' + name))
685
- .filter(function (el) { return !processed.has(el) && !isExcluded(el); });
621
+ .filter(function (el) { return !_animated.has(el) && !isExcluded(el); });
686
622
  if (bareEls.length === 0) return;
687
623
 
688
624
  var groups = groupByParent(bareEls);
689
625
  groups.forEach(function (group) {
690
626
  group.forEach(function (el, i) {
627
+ if (_animated.has(el)) return;
691
628
  fn(el, {
692
629
  trigger: 'scroll',
693
630
  delay: i * 0.15,
694
631
  scrollTrigger: { trigger: el },
695
632
  });
696
- processed.add(el);
633
+ _animated.add(el);
697
634
  });
698
635
  });
699
636
  });
@@ -702,21 +639,21 @@
702
639
 
703
640
  // 4. Scrub-based effects — always scroll-linked, processed before tagMap.
704
641
  document.querySelectorAll('.fx-tilt-in-st, .fx-tilt-in-pl, .fx-tilt-in').forEach(function (el) {
705
- if (!processed.has(el) && !isExcluded(el)) {
642
+ if (!_animated.has(el) && !isExcluded(el)) {
706
643
  tiltIn(el);
707
- processed.add(el);
644
+ _animated.add(el);
708
645
  }
709
646
  });
710
647
  document.querySelectorAll('.fx-parallax-st, .fx-parallax-pl, .fx-parallax').forEach(function (el) {
711
- if (!processed.has(el) && !isExcluded(el)) {
648
+ if (!_animated.has(el) && !isExcluded(el)) {
712
649
  parallax(el);
713
- processed.add(el);
650
+ _animated.add(el);
714
651
  }
715
652
  });
716
653
  document.querySelectorAll('.fx-draw-svg-scrub').forEach(function (el) {
717
- if (!processed.has(el) && !isExcluded(el)) {
654
+ if (!_animated.has(el) && !isExcluded(el)) {
718
655
  drawSVG(el, { scrub: getClassModifier(el, 'scrub', 0.6) });
719
- processed.add(el);
656
+ _animated.add(el);
720
657
  }
721
658
  });
722
659
 
@@ -729,18 +666,19 @@
729
666
  if (!fn) return;
730
667
 
731
668
  var els = Array.from(section.querySelectorAll(selector))
732
- .filter(function (el) { return !processed.has(el) && !isExcluded(el); });
669
+ .filter(function (el) { return !_animated.has(el) && !isExcluded(el); });
733
670
  if (els.length === 0) return;
734
671
 
735
672
  var groups = groupByParent(els);
736
673
  groups.forEach(function (group) {
737
674
  applyScrollGroup(fn, group, section);
738
- group.forEach(function (el) { processed.add(el); });
675
+ group.forEach(function (el) { _animated.add(el); });
739
676
  });
740
677
  });
741
678
  });
742
679
  }
743
- // 5. fx-stagger-all-[selector] — target children, effect from sibling class
680
+
681
+ // 6. fx-stagger-all-[selector] — target children, effect from sibling class
744
682
  // Requires an effect class on the same element (e.g. fx-reveal-st).
745
683
  document.querySelectorAll('[class*="fx-stagger-all-"]').forEach(function (container) {
746
684
  // Parse selector from fx-stagger-all-[img,p]
@@ -770,7 +708,7 @@
770
708
  var isScroll = container.classList.contains(effectName + '-st') ||
771
709
  container.classList.contains(effectName);
772
710
  var children = Array.from(container.querySelectorAll(childSelector))
773
- .filter(function (el) { return !processed.has(el); });
711
+ .filter(function (el) { return !_animated.has(el); });
774
712
  if (children.length === 0) return;
775
713
 
776
714
  children.forEach(function (child, i) {
@@ -780,7 +718,7 @@
780
718
  opts.scrollTrigger = { trigger: child };
781
719
  }
782
720
  effectFn(child, opts);
783
- processed.add(child);
721
+ _animated.add(child);
784
722
  });
785
723
  });
786
724
 
@@ -805,17 +743,20 @@
805
743
  function boot() {
806
744
  applyPreConfig();
807
745
 
808
- // Skip animations if OS reduced motion is enabled
809
- if (config.respectReducedMotion && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
810
- return;
746
+ // Build media query from config — animations auto-revert when conditions stop matching
747
+ var parts = [];
748
+ if (config.disableMobile) {
749
+ parts.push('(min-width: ' + (config.mobileBreakpoint + 1) + 'px)');
811
750
  }
812
-
813
- // Skip animations on mobile
814
- if (config.disableMobile && window.innerWidth <= config.mobileBreakpoint) {
815
- return;
751
+ if (config.respectReducedMotion) {
752
+ parts.push('(prefers-reduced-motion: no-preference)');
816
753
  }
754
+ var conditions = parts.length > 0 ? parts.join(' and ') : 'all';
817
755
 
818
- init();
756
+ _mm.add(conditions, function () {
757
+ _animated = new WeakSet();
758
+ init();
759
+ });
819
760
  }
820
761
 
821
762
  if (document.readyState === 'loading') {
@@ -844,6 +785,6 @@
844
785
  splitWords: splitWords,
845
786
  slideIn: slideIn,
846
787
  init: init,
847
- refresh: refreshSplits,
788
+ refresh: function () { ScrollTrigger.refresh(); },
848
789
  };
849
790
  })();