PageSourceSearch

https://anniegsdairy.com/wp-content/plugins/divi-essential/assets/js/dnxte-particles.js?ver=1780064763

js anniegsdairy.com collected 2026-10-05 08:40:40 UTC 96,652 bytes, 2,631 lines download raw bytes

1(function(global) {
2  'use strict';
3
4  // Register ScrollTrigger with GSAP if both are available
5  if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
6    gsap.registerPlugin(ScrollTrigger);
7  }
8
9  // ---------------------------------------------------------------------------
10  // Utilities
11  // ---------------------------------------------------------------------------
12  const randomFrom  = (arr) => arr[Math.floor(Math.random() * arr.length)];
13  const randomRange = (min, max) => Math.random() * (max - min) + min;
14  const clamp       = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
15  const lerp        = (a, b, t) => a + (b - a) * t;
16  const dist        = (x1, y1, x2, y2) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
17
18  const AUTUMN_COLORS = ['#c0392b', '#e67e22', '#f1c40f', '#d35400', '#e74c3c', '#f39c12'];
19  const EMOJIS = ['🎉', '🎊', '🥳', '🎈', '🎁', '✨', '🌟', '⭐', '🔥', '❤️'];
20  const MATRIX_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()=+<>?/{}[]|';
21  const GEOMETRIC_SHAPES = ['circle', 'square', 'triangle', 'hexagon'];
22
23  function hexToRgb(hex) {
24    hex = hex.replace('#', '');
25    if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
26    const n = parseInt(hex, 16);
27    return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
28  }
29
30  function lerpColor(c1, c2, t) {
31    const a = hexToRgb(c1), b = hexToRgb(c2);
32    const r = Math.round(lerp(a.r, b.r, t));
33    const g = Math.round(lerp(a.g, b.g, t));
34    const bl = Math.round(lerp(a.b, b.b, t));
35    return 'rgb(' + r + ',' + g + ',' + bl + ')';
36  }
37
38  function randomColor(c1, c2) {
39    return lerpColor(c1, c2, Math.random());
40  }
41
42  // Pick a random color from an array, lerping between two random picks
43  function randomColorFromArray(arr) {
44    if (!arr || arr.length === 0) return '#ffffff';
45    if (arr.length === 1) return arr[0];
46    var i = Math.floor(Math.random() * arr.length);
47    var j = Math.floor(Math.random() * arr.length);
48    return lerpColor(arr[i], arr[j], Math.random());
49  }
50
51  function createEl(tag, styles, parent) {
52    const el = document.createElement(tag);
53    Object.assign(el.style, styles);
54    if (parent) parent.appendChild(el);
55    return el;
56  }
57
58  function createSVG(parent, w, h) {
59    const ns = 'http://www.w3.org/2000/svg';
60    const svg = document.createElementNS(ns, 'svg');
61    svg.setAttribute('width', w);
62    svg.setAttribute('height', h);
63    svg.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;';
64    parent.appendChild(svg);
65    return svg;
66  }
67
68  function createSVGLine(svg) {
69    const ns = 'http://www.w3.org/2000/svg';
70    const line = document.createElementNS(ns, 'line');
71    svg.appendChild(line);
72    return line;
73  }
74
75  // ---------------------------------------------------------------------------
76  // DnxteParticleEngine
77  // ---------------------------------------------------------------------------
78  class DnxteParticleEngine {
79    constructor(element, options) {
80      if (!element) return;
81      this.element = element;
82      this._intervals = [];
83      this._rafs = [];
84      this._listeners = [];
85      this._timelines = [];
86      this._tweens = [];
87      this._particles = [];
88      this._destroyed = false;
89      this._mouseX = -9999;
90      this._mouseY = -9999;
91      this._mouseInside = false;
92      this._paused = false;
93
94      // Ensure parent is positioned
95      const pos = getComputedStyle(element).position;
96      if (pos === 'static') element.style.position = 'relative';
97
98      // Merge defaults < data-attributes < explicit options
99      var defaults = {
100        type: 'snowfall',
101        count: 40,
102        speed: 1,
103        color1: '#00d4aa',
104        color2: '#7c3aed',
105        color3: '',
106        color4: '',
107        size: 1,
108        opacity: 0.8,
109        trigger: 'always',
110        interactive: false,
111        effectWidth: 100,
112        glow: false,
113        glowIntensity: 1,
114        wind: 0,
115        windStrength: 1,
116        connectionDistance: 150,
117        burstInterval: 2000,
118        forceStrength: 1,
119        forceRadius: 150
120      };
121
122      var data = this._parseDataAttrs(element);
123      this.opts = Object.assign({}, defaults, data, options || {});
124
125      // Normalise
126      this.opts.count = parseInt(this.opts.count, 10) || 40;
127      this.opts.speed = parseFloat(this.opts.speed) || 1;
128      this.opts.size = parseFloat(this.opts.size) || 1;
129      this.opts.opacity = parseFloat(this.opts.opacity);
130      if (isNaN(this.opts.opacity)) this.opts.opacity = 0.8;
131      this.opts.wind = parseFloat(this.opts.wind) || 0;
132      this.opts.windStrength = parseFloat(this.opts.windStrength) || 1;
133      this.opts.connectionDistance = parseFloat(this.opts.connectionDistance) || 150;
134      this.opts.burstInterval = parseInt(this.opts.burstInterval, 10) || 2000;
135      this.opts.forceStrength = parseFloat(this.opts.forceStrength) || 1;
136      this.opts.forceRadius = parseFloat(this.opts.forceRadius) || 150;
137      this.opts.glowIntensity = parseFloat(this.opts.glowIntensity) || 1;
138      this.opts.effectWidth = parseInt(this.opts.effectWidth, 10) || 100;
139      if (typeof this.opts.interactive === 'string') this.opts.interactive = this.opts.interactive === 'true';
140      if (typeof this.opts.glow === 'string') this.opts.glow = this.opts.glow === 'true';
141
142      // Build colors array (filter empty)
143      this.colors = [this.opts.color1, this.opts.color2];
144      if (this.opts.color3) this.colors.push(this.opts.color3);
145      if (this.opts.color4) this.colors.push(this.opts.color4);
146
147      // Container — apply effectWidth as centered width
148      var ew = clamp(this.opts.effectWidth, 10, 100);
149      var containerStyles = {
150        position: 'absolute',
151        top: '0', bottom: '0',
152        overflow: 'hidden',
153        pointerEvents: 'none',
154        zIndex: '1'
155      };
156      if (ew < 100) {
157        containerStyles.left = ((100 - ew) / 2) + '%';
158        containerStyles.width = ew + '%';
159      } else {
160        containerStyles.left = '0';
161        containerStyles.right = '0';
162      }
163      this.container = createEl('div', containerStyles, element);
164      this.container.classList.add('dnxte-particle-container');
165
166      var isInteractiveType = this._isInteractiveEffect(this.opts.type);
167      if (isInteractiveType || this.opts.interactive) {
168        this.container.style.pointerEvents = 'auto';
169      }
170
171      // Mouse tracking
172      this._onMouseMove = this._onMouseMove.bind(this);
173      this._onMouseLeave = this._onMouseLeave.bind(this);
174      if (isInteractiveType || this.opts.interactive) {
175        var section = element.closest('section') || element;
176        section.addEventListener('mousemove', this._onMouseMove);
177        section.addEventListener('mouseleave', this._onMouseLeave);
178        this._listeners.push([section, 'mousemove', this._onMouseMove]);
179        this._listeners.push([section, 'mouseleave', this._onMouseLeave]);
180      }
181
182      // Trigger handling — for 'always' init immediately,
183      // for 'scroll'/'hover' defer init until first trigger
184      this._handleTrigger();
185    }
186
187    // -------------------------------------------------------------------------
188    // Data attribute parsing
189    // -------------------------------------------------------------------------
190    _parseDataAttrs(el) {
191      var map = {
192        'particle-type': 'type',
193        'particle-count': 'count',
194        'particle-speed': 'speed',
195        'particle-color1': 'color1',
196        'particle-color2': 'color2',
197        'particle-color3': 'color3',
198        'particle-color4': 'color4',
199        'particle-size': 'size',
200        'particle-opacity': 'opacity',
201        'particle-trigger': 'trigger',
202        'particle-interactive': 'interactive',
203        'particle-effect-width': 'effectWidth',
204        'particle-glow': 'glow',
205        'particle-glow-intensity': 'glowIntensity',
206        'particle-wind': 'wind',
207        'particle-wind-strength': 'windStrength',
208        'particle-connection-distance': 'connectionDistance',
209        'particle-burst-interval': 'burstInterval',
210        'particle-force-strength': 'forceStrength',
211        'particle-force-radius': 'forceRadius'
212      };
213      var out = {};
214      for (var key in map) {
215        var v = el.dataset[this._camelCase(key)];
216        if (v !== undefined) out[map[key]] = v;
217      }
218      return out;
219    }
220
221    _camelCase(str) {
222      return str.replace(/-([a-z])/g, function(m, c) { return c.toUpperCase(); });
223    }
224
225    _isInteractiveEffect(type) {
226      return [
227        'mouse-attract', 'mouse-repel', 'mouse-trail', 'hover-burst',
228        'swarm', 'gravity-well', 'connection-web', 'ripple',
229        'orbit-cursor', 'color-wave'
230      ].indexOf(type) !== -1;
231    }
232
233    // -------------------------------------------------------------------------
234    // Mouse tracking
235    // -------------------------------------------------------------------------
236    _onMouseMove(e) {
237      var rect = this.container.getBoundingClientRect();
238      this._mouseX = e.clientX - rect.left;
239      this._mouseY = e.clientY - rect.top;
240      this._mouseInside = true;
241    }
242
243    _onMouseLeave() {
244      this._mouseInside = false;
245    }
246
247    // -------------------------------------------------------------------------
248    // Dimensions
249    // -------------------------------------------------------------------------
250    _w() { return this.container.offsetWidth || this.element.offsetWidth || 800; }
251    _h() { return this.container.offsetHeight || this.element.offsetHeight || 600; }
252
253    // -------------------------------------------------------------------------
254    // Glow helper
255    // -------------------------------------------------------------------------
256    _applyGlow(el, color) {
257      if (this.opts.glow) {
258        var intensity = this.opts.glowIntensity;
259        var blur = 8 * intensity;
260        var spread = 4 * intensity;
261        el.style.boxShadow = '0 0 ' + blur + 'px ' + spread + 'px ' + color;
262      }
263    }
264
265    _applyTextGlow(el, color) {
266      if (this.opts.glow) {
267        var intensity = this.opts.glowIntensity;
268        var blur = 8 * intensity;
269        el.style.textShadow = '0 0 ' + blur + 'px ' + color;
270      }
271    }
272
273    // -------------------------------------------------------------------------
274    // Wind helper — returns x offset over given duration
275    // -------------------------------------------------------------------------
276    _windOffset(duration) {
277      return this.opts.wind * this.opts.windStrength * 30 * duration;
278    }
279
280    // -------------------------------------------------------------------------
281    // Trigger handling
282    // -------------------------------------------------------------------------
283    _handleTrigger() {
284      var self = this;
285      this._effectActive = false;
286
287      if (this.opts.trigger === 'always') {
288        // Init immediately
289        this._effectActive = true;
290        this._paused = false;
291        this._initEffect();
292        return;
293      }
294
295      if (this.opts.trigger === 'scroll') {
296        this._paused = true;
297        var ST = (typeof ScrollTrigger !== 'undefined') ? ScrollTrigger : (typeof gsap !== 'undefined' && gsap.ScrollTrigger) ? gsap.ScrollTrigger : null;
298
299        if (ST) {
300          this._scrollTrigger = ST.create({
301            trigger: this.element,
302            start: 'top 80%',
303            end: 'bottom 20%',
304            onEnter:     function() { self._startEffect(); },
305            onLeave:     function() { self._stopEffect(); },
306            onEnterBack: function() { self._startEffect(); },
307            onLeaveBack: function() { self._stopEffect(); }
308          });
309        } else if (typeof IntersectionObserver !== 'undefined') {
310          // Fallback: IntersectionObserver
311          this._observer = new IntersectionObserver(function(entries) {
312            entries.forEach(function(entry) {
313              if (entry.isIntersecting) {
314                self._startEffect();
315              } else {
316                self._stopEffect();
317              }
318            });
319          }, { threshold: 0.2 });
320          this._observer.observe(this.element);
321        }
322        return;
323      }
324
325      if (this.opts.trigger === 'hover') {
326        this._paused = true;
327        var onEnter = function() { self._startEffect(); };
328        var onLeave = function() { self._stopEffect(); };
329        this.element.addEventListener('mouseenter', onEnter);
330        this.element.addEventListener('mouseleave', onLeave);
331        this._listeners.push([this.element, 'mouseenter', onEnter]);
332        this._listeners.push([this.element, 'mouseleave', onLeave]);
333        return;
334      }
335
336      // Fallback: always
337      this._effectActive = true;
338      this._paused = false;
339      this._initEffect();
340    }
341
342    _startEffect() {
343      if (this._effectActive) return;
344      this._effectActive = true;
345      this._paused = false;
346      this._initEffect();
347    }
348
349    _stopEffect() {
350      if (!this._effectActive) return;
351      this._effectActive = false;
352      this._paused = true;
353
354      // Kill all intervals
355      for (var i = 0; i < this._intervals.length; i++) clearInterval(this._intervals[i]);
356      this._intervals = [];
357
358      // Cancel all rAFs
359      for (var j = 0; j < this._rafs.length; j++) cancelAnimationFrame(this._rafs[j]);
360      this._rafs = [];
361
362      // Kill all tweens
363      for (var k = 0; k < this._tweens.length; k++) {
364        if (this._tweens[k] && this._tweens[k].kill) this._tweens[k].kill();
365      }
366      this._tweens = [];
367
368      // Kill timelines
369      for (var l = 0; l < this._timelines.length; l++) {
370        if (this._timelines[l] && this._timelines[l].kill) this._timelines[l].kill();
371      }
372      this._timelines = [];
373
374      // Clear all particles from container
375      while (this.container.firstChild) {
376        this.container.removeChild(this.container.firstChild);
377      }
378      this._particles = [];
379    }
380
381    _pauseAll() {
382      this._tweens.forEach(function(t) { if (t.pause) t.pause(); });
383      this._timelines.forEach(function(t) { if (t.pause) t.pause(); });
384    }
385
386    _resumeAll() {
387      this._tweens.forEach(function(t) { if (t.resume) t.resume(); });
388      this._timelines.forEach(function(t) { if (t.resume) t.resume(); });
389    }
390
391    // -------------------------------------------------------------------------
392    // Store helpers
393    // -------------------------------------------------------------------------
394    _addInterval(id) { this._intervals.push(id); return id; }
395    _addRaf(id) { this._rafs.push(id); return id; }
396    _addTween(t) { this._tweens.push(t); return t; }
397    _addTimeline(t) { this._timelines.push(t); return t; }
398
399    // -------------------------------------------------------------------------
400    // Effect router
401    // -------------------------------------------------------------------------
402    _initEffect() {
403      var map = {
404        'confetti':         '_initConfetti',
405        'fireworks':        '_initFireworks',
406        'streamers':        '_initStreamers',
407        'balloons':         '_initBalloons',
408        'champagne':        '_initChampagne',
409        'glitter':          '_initGlitter',
410        'starburst':        '_initStarburst',
411        'emoji':            '_initEmoji',
412        'snowfall':         '_initSnowfall',
413        'fireflies':        '_initFireflies',
414        'cherry-blossoms':  '_initCherryBlossoms',
415        'rain':             '_initRain',
416        'leaves':           '_initLeaves',
417        'bubbles':          '_initBubbles',
418        'galaxy':           '_initGalaxy',
419        'starfield':        '_initStarfield',
420        'nebula':           '_initNebula',
421        'comets':           '_initComets',
422        'matrix':           '_initMatrix',
423        'network':          '_initNetwork',
424        'dna':              '_initDna',
425        'wave':             '_initWave',
426        'magnetic':         '_initMagnetic',
427        'aurora':           '_initAurora',
428        'geometric':        '_initGeometric',
429        'pulse-rings':      '_initPulseRings',
430        'vortex':           '_initVortex',
431        'sparks':           '_initSparks',
432        'dust':             '_initDust',
433        'blobs':            '_initBlobs',
434        'mouse-attract':    '_initMouseAttract',
435        'mouse-repel':      '_initMouseRepel',
436        'mouse-trail':      '_initMouseTrail',
437        'hover-burst':      '_initHoverBurst',
438        'swarm':            '_initSwarm',
439        'gravity-well':     '_initGravityWell',
440        'connection-web':   '_initConnectionWeb',
441        'ripple':           '_initRipple',
442        'orbit-cursor':     '_initOrbitCursor',
443        'color-wave':       '_initColorWave'
444      };
445      var fn = map[this.opts.type];
446      if (fn && typeof this[fn] === 'function') {
447        this[fn]();
448      } else {
449        this._initSnowfall();
450      }
451    }
452
453    // =========================================================================
454    //  CELEBRATION EFFECTS (1-8)
455    // =========================================================================
456
457    // 1. Confetti ----------------------------------------------------------
458    _initConfetti() {
459      var self = this;
460      var burst = function() {
461        var w = self._w(), h = self._h();
462        for (var i = 0;
462 i < self.opts.count; i++) {
463          var s = randomRange(6, 12) * self.opts.size;
464          var color = randomColorFromArray(self.colors);
465          var el = createEl('div', {
466            position: 'absolute',
467            width: s + 'px',
468            height: s * randomRange(0.4, 1) + 'px',
469            backgroundColor: color,
470            borderRadius: Math.random() > 0.5 ? '2px' : '50%',
471            left: w / 2 + 'px',
472            top: h / 2 + 'px',
473            opacity: '0',
474            zIndex: '2'
475          }, self.container);
476          self._applyGlow(el, color);
477          var angle = randomRange(0, Math.PI * 2);
478          var radius = randomRange(100, Math.max(w, h) * 0.6);
479          var dur = randomRange(1.5, 3) / self.opts.speed;
480          var tl = gsap.timeline();
481          self._addTimeline(tl);
482          tl.set(el, { opacity: self.opts.opacity, scale: 0 });
483          tl.to(el, {
484            x: Math.cos(angle) * radius + self._windOffset(dur),
485            y: Math.sin(angle) * radius + randomRange(50, 150),
486            rotation: randomRange(-720, 720),
487            scale: 1,
488            opacity: 0,
489            duration: dur,
490            ease: 'power2.out',
491            onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
492          });
493        }
494      };
495      burst();
496      this._addInterval(setInterval(burst, this.opts.burstInterval));
497    }
498
499    // 2. Fireworks ----------------------------------------------------------
500    _initFireworks() {
501      var self = this;
502      var launchOne = function() {
503        var w = self._w(), h = self._h();
504        var startX = randomRange(w * 0.2, w * 0.8);
505        var peakY = randomRange(h * 0.1, h * 0.4);
506        var rocket = createEl('div', {
507          position: 'absolute',
508          width: 4 * self.opts.size + 'px',
509          height: 12 * self.opts.size + 'px',
510          backgroundColor: self.opts.color1,
511          borderRadius: '2px',
512          left: startX + 'px',
513          top: h + 'px',
514          opacity: String(self.opts.opacity)
515        }, self.container);
516        self._applyGlow(rocket, self.opts.color1);
517        var dur = randomRange(0.6, 1) / self.opts.speed;
518        var tl = gsap.timeline();
519        self._addTimeline(tl);
520        tl.to(rocket, {
521          y: -(h - peakY),
522          duration: dur,
523          ease: 'power2.in',
524          onComplete: function() {
525            if (rocket.parentNode) rocket.parentNode.removeChild(rocket);
526            // Explode
527            var count = Math.floor(self.opts.count * 0.6);
528            for (var i = 0; i < count; i++) {
529              var sz = randomRange(3, 6) * self.opts.size;
530              var color = randomColorFromArray(self.colors);
531              var spark = createEl('div', {
532                position: 'absolute',
533                width: sz + 'px',
534                height: sz + 'px',
535                backgroundColor: color,
536                borderRadius: '50%',
537                left: startX + 'px',
538                top: peakY + 'px',
539                opacity: String(self.opts.opacity)
540              }, self.container);
541              self._applyGlow(spark, color);
542              var angle = randomRange(0, Math.PI * 2);
543              var r = randomRange(40, 150) * self.opts.size;
544              var d2 = randomRange(0.8, 1.5) / self.opts.speed;
545              self._addTween(gsap.to(spark, {
546                x: Math.cos(angle) * r,
547                y: Math.sin(angle) * r + 60,
548                opacity: 0,
549                scale: 0.2,
550                duration: d2,
551                ease: 'power3.out',
552                onComplete: function() { if (spark.parentNode) spark.parentNode.removeChild(spark); }
553              }));
554            }
555          }
556        });
557      };
558      launchOne();
559      this._addInterval(setInterval(launchOne, this.opts.burstInterval));
560    }
561
562    // 3. Streamers ----------------------------------------------------------
563    _initStreamers() {
564      var self = this;
565      var w = this._w(), h = this._h();
566      for (var i = 0; i < this.opts.count; i++) {
567        (function(idx) {
568          var color = randomColorFromArray(self.colors);
569          var sw = randomRange(4, 8) * self.opts.size;
570          var sh = randomRange(40, 80) * self.opts.size;
571          var el = createEl('div', {
572            position: 'absolute',
573            width: sw + 'px',
574            height: sh + 'px',
575            backgroundColor: color,
576            borderRadius: sw + 'px',
577            left: randomRange(0, w) + 'px',
578            top: -sh + 'px',
579            opacity: String(self.opts.opacity)
580          }, self.container);
581          self._applyGlow(el, color);
582          var dur = randomRange(3, 6) / self.opts.speed;
583          var delay = randomRange(0, 3);
584          var sway = randomRange(-80, 80);
585          var tl = gsap.timeline({ repeat: -1, delay: delay });
586          self._addTimeline(tl);
587          tl.to(el, {
588            y: h + sh + 40,
589            x: sway + self._windOffset(dur),
590            rotation: randomRange(-180, 180),
591            duration: dur,
592            ease: 'sine.inOut',
593            onComplete: function() {
594              gsap.set(el, { x: 0, y: 0, left: randomRange(0, w) + 'px', rotation: 0 });
595            }
596          });
597        })(i);
598      }
599    }
600
601    // 4. Balloons -----------------------------------------------------------
602    _initBalloons() {
603      var self = this;
604      var w = this._w(), h = this._h();
605      for (var i = 0; i < this.opts.count; i++) {
606        (function() {
607          var sz = randomRange(20, 35) * self.opts.size;
608          var color = randomColorFromArray(self.colors);
609          var el = createEl('div', {
610            position: 'absolute',
611            width: sz + 'px',
612            height: sz * 1.2 + 'px',
613            backgroundColor: color,
614            borderRadius: '50% 50% 50% 50% / 40% 40% 60% 60%',
615            left: randomRange(0, w) + 'px',
616            top: h + sz + 'px',
617            opacity: String(self.opts.opacity)
618          }, self.container);
619          self._applyGlow(el, color);
620          // String
621          var str = createEl('div', {
622            position: 'absolute',
623            width: '1px',
624            height: sz * 0.5 + 'px',
625            backgroundColor: color,
626            left: sz / 2 + 'px',
627            top: sz * 1.2 + 'px',
628            opacity: '0.5'
629          }, el);
630          var dur = randomRange(5, 10) / self.opts.speed;
631          var delay = randomRange(0, 5);
632          var tl = gsap.timeline({ repeat: -1, delay: delay });
633          self._addTimeline(tl);
634          tl.to(el, {
635            y: -(h + sz * 3),
636            x: '+=' + (randomRange(-100, 100) + self._windOffset(dur)),
637            rotation: randomRange(-20, 20),
638            duration: dur,
639            ease: 'none',
640            onComplete: function() {
641              gsap.set(el, { x: 0, y: 0, left: randomRange(0, w) + 'px' });
642            }
643          });
644          // Wobble
645          self._addTween(gsap.to(el, {
646            x: '+=20', yoyo: true, repeat: -1,
647            duration: randomRange(1, 2) / self.opts.speed,
648            ease: 'sine.inOut'
649          }));
650        })();
651      }
652    }
653
654    // 5. Champagne ----------------------------------------------------------
655    _initChampagne() {
656      var self = this;
657      var spawnBubble = function() {
658        var w = self._w(), h = self._h();
659        var sz = randomRange(3, 8) * self.opts.size;
660        var el = createEl('div', {
661          position: 'absolute',
662          width: sz + 'px',
663          height: sz + 'px',
664          backgroundColor: randomFrom(['#ffd700', '#ffe44d', '#fff8dc', '#ffc107']),
665          borderRadius: '50%',
666          left: w / 2 + randomRange(-80, 80) * self.opts.size + 'px',
667          top: h + 'px',
668          opacity: String(self.opts.opacity)
669        }, self.container);
670        if (self.opts.glow) {
671          el.style.boxShadow = '0 0 ' + (6 * self.opts.glowIntensity) + 'px ' + (3 * self.opts.glowIntensity) + 'px #ffd700';
672        }
673        var dur = randomRange(1.5, 3.5) / self.opts.speed;
674        self._addTween(gsap.to(el, {
675          y: -(h * randomRange(0.5, 1.2)),
676          x: randomRange(-150, 150) + self._windOffset(dur),
677          opacity: 0,
678          scale: randomRange(0.3, 0.8),
679          duration: dur,
680          ease: 'power1.out',
681          onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
682        }));
683      };
684      for (var i = 0; i < Math.floor(this.opts.count * 0.5); i++) spawnBubble();
685      this._addInterval(setInterval(function() {
686        if (!self._paused) {
687          for (var j = 0; j < 5; j++) spawnBubble();
688        }
689      }, 200 / this.opts.speed));
690    }
691
692    // 6. Glitter ------------------------------------------------------------
693    _initGlitter() {
694      var self = this;
695      var w = this._w(), h = this._h();
696      var spawnGlitter = function() {
697        var sz = randomRange(2, 5) * self.opts.size;
698        var color = randomFrom(['#ffd700', '#c0c0c0', '#ffe44d', '#e8e8e8', '#fff8dc']);
699        var el = createEl('div', {
700          position: 'absolute',
701          width: sz + 'px',
702          height: sz + 'px',
703          backgroundColor: color,
704          borderRadius: '50%',
705          left: randomRange(0, w) + 'px',
706          top: -sz + 'px',
707          opacity: '0'
708        }, self.container);
709        self._applyGlow(el, color);
710        var dur = randomRange(2, 5) / self.opts.speed;
711        var tl = gsap.timeline();
712        self._addTimeline(tl);
713        tl.to(el, {
714          y: h + sz,
715          x: randomRange(-50, 50) + self._windOffset(dur),
716          duration: dur,
717          ease: 'none'
718        });
719        tl.to(el, {
720          opacity: self.opts.opacity,
721          duration: 0.15,
722          yoyo: true,
723          repeat: Math.floor(dur / 0.3),
724          ease: 'power2.inOut'
725        }, 0);
726        tl.to(el, {
727          scale: randomRange(0.5, 1.5),
728          duration: 0.3,
729          yoyo: true,
730          repeat: -1,
731          ease: 'sine.inOut'
732        }, 0);
733        tl.eventCallback('onComplete', function() {
734          if (el.parentNode) el.parentNode.removeChild(el);
735        });
736      };
737      for (var i = 0; i < this.opts.count; i++) {
738        setTimeout(function() { spawnGlitter(); }, randomRange(0, 2000));
739      }
740      this._addInterval(setInterval(function() {
741        if (!self._paused) spawnGlitter();
742      }, 100 / this.opts.speed));
743    }
744
745    // 7. Starburst ----------------------------------------------------------
746    _initStarburst() {
747      var self = this;
748      var burst = function() {
749        var w = self._w(), h = self._h();
750        for (var i = 0;
750 i < self.opts.count; i++) {
751          var sz = randomRange(12, 24) * self.opts.size;
752          var color = randomColorFromArray(self.colors);
753          var el = createEl('div', {
754            position: 'absolute',
755            left: w / 2 + 'px',
756            top: h / 2 + 'px',
757            fontSize: sz + 'px',
758            lineHeight: '1',
759            color: color,
760            opacity: '0',
761            pointerEvents: 'none'
762          }, self.container);
763          el.textContent = '★';
764          self._applyTextGlow(el, color);
765          var angle = randomRange(0, Math.PI * 2);
766          var r = randomRange(80, Math.max(w, h) * 0.5);
767          var dur = randomRange(1, 2.5) / self.opts.speed;
768          var tl = gsap.timeline();
769          self._addTimeline(tl);
770          tl.fromTo(el, { opacity: 0, scale: 0 }, {
771            opacity: self.opts.opacity,
772            scale: 1,
773            x: Math.cos(angle) * r + self._windOffset(dur),
774            y: Math.sin(angle) * r,
775            rotation: randomRange(-360, 360),
776            duration: dur,
777            ease: 'power2.out'
778          });
779          tl.to(el, {
780            opacity: 0, scale: 0.3,
781            duration: dur * 0.4,
782            ease: 'power2.in',
783            onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
784          });
785        }
786      };
787      burst();
788      this._addInterval(setInterval(burst, this.opts.burstInterval));
789    }
790
791    // 8. Emoji --------------------------------------------------------------
792    _initEmoji() {
793      var self = this;
794      var w = this._w(), h = this._h();
795      var spawnEmoji = function() {
796        var sz = randomRange(16, 32) * self.opts.size;
797        var el = createEl('div', {
798          position: 'absolute',
799          fontSize: sz + 'px',
800          lineHeight: '1',
801          left: randomRange(0, w) + 'px',
802          top: -sz * 2 + 'px',
803          opacity: String(self.opts.opacity),
804          pointerEvents: 'none'
805        }, self.container);
806        el.textContent = randomFrom(EMOJIS);
807        var dur = randomRange(3, 6) / self.opts.speed;
808        self._addTween(gsap.to(el, {
809          y: h + sz * 2,
810          x: randomRange(-60, 60) + self._windOffset(dur),
811          rotation: randomRange(-180, 180),
812          duration: dur,
813          ease: 'none',
814          onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
815        }));
816      };
817      for (var i = 0; i < this.opts.count; i++) {
818        setTimeout(function() { spawnEmoji(); }, randomRange(0, 3000));
819      }
820      this._addInterval(setInterval(function() {
821        if (!self._paused) spawnEmoji();
822      }, 300 / this.opts.speed));
823    }
824
825    // =========================================================================
826    //  NATURE EFFECTS (9-14)
827    // =========================================================================
828
829    // 9. Snowfall -----------------------------------------------------------
830    _initSnowfall() {
831      var self = this;
832      var w = this._w(), h = this._h();
833      for (var i = 0; i < this.opts.count; i++) {
834        (function() {
835          var sz = randomRange(3, 8) * self.opts.size;
836          var el = createEl('div', {
837            position: 'absolute',
838            width: sz + 'px',
839            height: sz + 'px',
840            backgroundColor: '#ffffff',
841            borderRadius: '50%',
842            left: randomRange(0, w) + 'px',
843            top: randomRange(-sz, -sz * 5) + 'px',
844            opacity: String(randomRange(0.4, self.opts.opacity))
845          }, self.container);
846          self._applyGlow(el, '#ffffff');
847          var dur = randomRange(4, 8) / self.opts.speed;
848          var delay = randomRange(0, 5);
849          var swayX = randomRange(-60, 60);
850          var tl = gsap.timeline({ repeat: -1, delay: delay });
851          self._addTimeline(tl);
852          tl.to(el, {
853            y: h + sz * 2,
854            x: swayX + self._windOffset(dur),
855            duration: dur,
856            ease: 'none',
857            onComplete: function() {
858              gsap.set(el, {
859                x: 0, y: 0,
860                left: randomRange(0, w) + 'px',
861                top: randomRange(-sz, -sz * 3) + 'px'
862              });
863            }
864          });
865          // Horizontal sway
866          self._addTween(gsap.to(el, {
867            x: '+=' + randomRange(-30, 30),
868            duration: randomRange(1.5, 3) / self.opts.speed,
869            yoyo: true, repeat: -1, ease: 'sine.inOut'
870          }));
871        })();
872      }
873    }
874
875    // 10. Fireflies ---------------------------------------------------------
876    _initFireflies() {
877      var self = this;
878      var w = this._w(), h = this._h();
879      for (var i = 0; i < this.opts.count; i++) {
880        (function() {
881          var sz = randomRange(3, 6) * self.opts.size;
882          var color = randomFrom(['#ffff00', '#ffee58', '#fff176', '#ffe082']);
883          var el = createEl('div', {
884            position: 'absolute',
885            width: sz + 'px',
886            height: sz + 'px',
887            backgroundColor: color,
888            borderRadius: '50%',
889            left: randomRange(0, w) + 'px',
890            top: randomRange(0, h) + 'px',
891            opacity: '0'
892          }, self.container);
893          el.style.boxShadow = '0 0 ' + (10 * self.opts.glowIntensity) + 'px ' + (5 * 
893self.opts.glowIntensity) + 'px ' + color;
894          // Random float
895          var moveDur = randomRange(4, 8) / self.opts.speed;
896          self._addTween(gsap.to(el, {
897            x: '+=' + randomRange(-120, 120),
898            y: '+=' + randomRange(-120, 120),
899            duration: moveDur,
900            yoyo: true, repeat: -1,
901            ease: 'sine.inOut'
902          }));
903          // Pulse opacity
904          self._addTween(gsap.to(el, {
905            opacity: self.opts.opacity,
906            duration: randomRange(0.8, 2) / self.opts.speed,
907            yoyo: true, repeat: -1,
908            ease: 'sine.inOut',
909            delay: randomRange(0, 3)
910          }));
911        })();
912      }
913    }
914
915    // 11. Cherry Blossoms ---------------------------------------------------
916    _initCherryBlossoms() {
917      var self = this;
918      var w = this._w(), h = this._h();
919      for (var i = 0; i < this.opts.count; i++) {
920        (function() {
921          var sz = randomRange(8, 16) * self.opts.size;
922          var pink = randomFrom(['#ffb7c5', '#ff69b4', '#ffc0cb', '#f8bbd0', '#f48fb1']);
923          var el = createEl('div', {
924            position: 'absolute',
925            width: sz + 'px',
926            height: sz * 0.6 + 'px',
927            backgroundColor: pink,
928            borderRadius: '50% 0 50% 0',
929            left: randomRange(0, w) + 'px',
930            top: -sz * 2 + 'px',
931            opacity: String(self.opts.opacity),
932            transformOrigin: 'center center'
933          }, self.container);
934          self._applyGlow(el, pink);
935          var dur = randomRange(4, 8) / self.opts.speed;
936          var delay = randomRange(0, 6);
937          var tl = gsap.timeline({ repeat: -1, delay: delay });
938          self._addTimeline(tl);
939          tl.to(el, {
940            y: h + sz * 2,
941            x: randomRange(-100, 100) + self._windOffset(dur),
942            rotation: randomRange(-360, 360),
943            duration: dur,
944            ease: 'none',
945            onComplete: function() {
946              gsap.set(el, { x: 0, y: 0, left: randomRange(0, w) + 'px', rotation: 0 });
947            }
948          });
949          // Sway
950          self._addTween(gsap.to(el, {
951            x: '+=' + randomRange(-40, 40),
952            rotateY: 180,
953            duration: randomRange(1, 3) / self.opts.speed,
954            yoyo: true, repeat: -1, ease: 'sine.inOut'
955          }));
956        })();
957      }
958    }
959
960    // 12. Rain --------------------------------------------------------------
961    _initRain() {
962      var self = this;
963      var w = this._w(), h = this._h();
964      for (var i = 0; i < this.opts.count; i++) {
965        (function() {
966          var len = randomRange(15, 30) * self.opts.size;
967          var el = createEl('div', {
968            position: 'absolute',
969            width: '1px',
970            height: len + 'px',
971            backgroundColor: 'rgba(174,194,224,' + self.opts.opacity + ')',
972            left: randomRange(0, w) + 'px',
973            top: -len + 'px'
974          }, self.container);
975          var dur = randomRange(0.3, 0.8) / self.opts.speed;
976          var delay = randomRange(0, 2);
977          var windX = self.opts.wind * self.opts.windStrength * 10;
978          el.style.transform = 'rotate(' + (windX * 2) + 'deg)';
979          var tl = gsap.timeline({ repeat: -1, delay: delay });
980          self._addTimeline(tl);
981          tl.to(el, {
982            y: h + len,
983            x: windX * dur * 20,
984            duration: dur,
985            ease: 'none',
986            onComplete: function() {
987              gsap.set(el, { x: 0, y: 0, left: randomRange(0, w) + 'px' });
988            }
989          });
990        })();
991      }
992      // Optional lightning
993      if (this.opts.glow) {
994        var flash = createEl('div', {
995          position: 'absolute',
996          top: '0', left: '0', right: '0', bottom: '0',
997          backgroundColor: 'rgba(255,255,255,0)',
998          pointerEvents: 'none',
999          zIndex: '10'
1000        }, this.container);
1001        this._addInterval(setInterval(function() {
1002          if (self._paused) return;
1003          if (Math.random() > 0.7) {
1004            gsap.to(flash, { backgroundColor: 'rgba(255,255,255,0.3)', duration: 0.05,
1005              onComplete: function() {
1006                gsap.to(flash, { backgroundColor: 'rgba(255,255,255,0)', duration: 0.3 });
1007              }
1008            });
1009          }
1010        }, 3000));
1011      }
1012    }
1013
1014    // 13. Leaves ------------------------------------------------------------
1015    _initLeaves() {
1016      var self = this;
1017      var w = this._w(), h = this._h();
1018      for (var i = 0; i < this.opts.count; i++) {
1019        (function() {
1020          var sz = randomRange(10, 20) * self.opts.size;
1021          var color = randomFrom(AUTUMN_COLORS);
1022          var el = createEl('div', {
1023            position: 'absolute',
1024            width: sz + 'px',
1025            height: sz * 0.6 + 'px',
1026            backgroundColor: color,
1027            borderRadius: '0 50%',
1028            left: randomRange(0, w) + 'px',
1029            top: -sz * 2 + 'px',
1030            opacity: String(self.opts.opacity),
1031            transformOrigin: 'center center'
1032          }, self.container);
1033          self._applyGlow(el, color);
1034          var dur = randomRange(4, 9) / self.opts.speed;
1035          var delay = randomRange(0, 5);
1036          var tl = gsap.timeline({ repeat: -1, delay: delay });
1037          self._addTimeline(tl);
1038          tl.to(el, {
1039            y: h + sz * 2,
1040            x: randomRange(-120, 120) + self._windOffset(dur),
1041            rotation: randomRange(-540, 540),
1042            duration: dur,
1043            ease: 'none',
1044            onComplete: function() {
1045              gsap.set(el, { x: 0, y: 0, left: randomRange(0, w) + 'px', rotation: 0 });
1046            }
1047          });
1048          // Flutter
1049          self._addTween(gsap.to(el, {
1050            scaleX: -1,
1051            duration: randomRange(0.5, 1.5) / self.opts.speed,
1052            yoyo: true, repeat: -1, ease: 'sine.inOut'
1053          }));
1054        })();
1055      }
1056    }
1057
1058    // 14. Bubbles -----------------------------------------------------------
1059    _initBubbles() {
1060      var self = this;
1061      var w = this._w(), h = this._h();
1062      for (var i = 0; i < this.opts.count; i++) {
1063        (function() {
1064          var sz = randomRange(8, 25) * self.opts.size;
1065          var el = createEl('div', {
1066            position: 'absolute',
1067            width: sz + 'px',
1068            height: sz + 'px',
1069            border: '1px solid rgba(255,255,255,' + self.opts.opacity * 0.6 + ')',
1070            backgroundColor: 'rgba(255,255,255,' + self.opts.opacity * 0.1 + ')',
1071            borderRadius: '50%',
1072            left: randomRange(0, w) + 'px',
1073            top: h + sz + 'px'
1074          }, self.container);
1075          if (self.opts.glow) {
1076            el.style.boxShadow = '0 0 ' + (8 * self.opts.glowIntensity) + 'px rgba(255,255,255,0.3)';
1077          }
1078          var dur = randomRange(4, 8) / self.opts.speed;
1079          var delay = randomRange(0, 5);
1080          var tl = gsap.timeline({ repeat: -1, delay: delay });
1081          self._addTimeline(tl);
1082          tl.to(el, {
1083            y: -(h + sz * 3),
1084            x: randomRange(-60, 60) + self._windOffset(dur),
1085            duration: dur,
1086            ease: 'none',
1087            onComplete: function() {
1088              gsap.set(el, { x: 0, y: 0, left: randomRange(0, w) + 'px', top: h + sz + 'px' });
1089            }
1090          });
1091          // Wobble
1092          self._addTween(gsap.to(el, {
1093            x: '+=' + randomRange(-20, 20),
1094            duration: randomRange(1, 2.5) / self.opts.speed,
1095            yoyo: true, repeat: -1, ease: 'sine.inOut'
1096          }));
1097        })();
1098      }
1099    }
1100
1101    // =========================================================================
1102    //  COSMIC EFFECTS (15-18)
1103    // =========================================================================
1104
1105    // 15. Galaxy ------------------------------------------------------------
1106    _initGalaxy() {
1107      var self = this;
1108      var w = this._w(), h = this._h();
1109      var cx = w / 2, cy = h / 2;
1110      var arms = 3;
1111      var galaxyContainer = createEl('div', {
1112        position: 'absolute',
1113        width: '100%', height: '100%',
1114        top: '0', left: '0'
1115      }, this.container);
1116      for (var i = 0; i < this.opts.count; i++) {
1117        (function(idx) {
1118          var arm = idx % arms;
1119          var armAngle = (arm / arms) * Math.PI * 2;
1120          var t = (idx / self.opts.count);
1121          var maxR = Math.min(w, h) * 0.4;
1122          var r = t * maxR;
1123          var spiralAngle = armAngle + t * Math.PI * 3;
1124          var jitter = randomRange(-15, 15);
1125          var sz = randomRange(2, 5) * self.opts.size;
1126          var color = lerpColor(self.opts.color1, self.opts.color2, t);
1127          var x = cx + Math.cos(spiralAngle) * r + jitter;
1128          var y = cy + Math.sin(spiralAngle) * r + jitter;
1129          var el = createEl('div', {
1130            position: 'absolute',
1131            width: sz + 'px',
1132            height: sz + 'px',
1133            backgroundColor: color,
1134            borderRadius: '50%',
1135            left: x + 'px',
1136            top: y + 'px',
1137            opacity: String(self.opts.opacity * randomRange(0.5, 1))
1138          }, galaxyContainer);
1139          self._applyGlow(el, color);
1140        })(i);
1141      }
1142      // Rotate the whole galaxy
1143      var dur = 30 / self.opts.speed;
1144      self._addTween(gsap.to(galaxyContainer, {
1145        rotation: 360,
1146        duration: dur,
1147        repeat: -1,
1148        ease: 'none',
1149        transformOrigin: '50% 50%'
1150      }));
1151    }
1152
1153    // 16. Starfield ---------------------------------------------------------
1154    _initStarfield() {
1155      var self = this;
1156      var w = this._w(), h = this._h();
1157      // Static twinkling stars
1158      for (var i = 0; i < this.opts.count; i++) {
1159        (function() {
1160          var sz = randomRange(1, 4) * self.opts.size;
1161          var el = createEl('div', {
1162            position: 'absolute',
1163            width: sz + 'px',
1164            height: sz + 'px',
1165            backgroundColor: '#ffffff',
1166            borderRadius: '50%',
1167            left: randomRange(0, w) + 'px',
1168            top: randomRange(0, h) + 'px',
1169            opacity: String(randomRange(0.2, self.opts.opacity))
1170          }, self.container);
1171          self._applyGlow(el, '#ffffff');
1172          // Twinkle
1173          self._addTween(gsap.to(el, {
1174            opacity: randomRange(0.1, 0.3),
1175            duration: randomRange(1, 3) / self.opts.speed,
1176            yoyo: true, repeat: -1,
1177            ease: 'sine.inOut',
1178            delay: randomRange(0, 4)
1179          }));
1180        })();
1181      }
1182      // Shooting stars
1183      this._addInterval(setInterval(function() {
1184        if (self._paused) return;
1185        var startX = randomRange(0, w * 0.5);
1186        var startY = randomRange(0, h * 0.3);
1187        var trail = createEl('div', {
1188          position: 'absolute',
1189          width: randomRange(40, 80) * self.opts.size + 'px',
1190          height: '2px',
1191          background: 'linear-gradient(to right, rgba(255,255,255,0), #ffffff)',
1192          left: startX + 'px',
1193          top: startY + 'px',
1194          opacity: '0',
1195          transformOrigin: 'right center',
1196          transform: 'rotate(30deg)'
1197        }, self.container);
1198        var dur = randomRange(0.5, 1) / self.opts.speed;
1199        var tl = gsap.timeline();
1200        self._addTimeline(tl);
1201        tl.to(trail, { opacity: self.opts.opacity, duration: 0.1 });
1202        tl.to(trail, {
1203          x: w * 0.6, y: h * 0.6, opacity: 0,
1204          duration: dur, ease: 'power1.in',
1205          onComplete: function() { if (trail.parentNode) trail.parentNode.removeChild(trail); }
1206        });
1207      }, 4000 / this.opts.speed));
1208    }
1209
1210    // 17. Nebula ------------------------------------------------------------
1211    _initNebula() {
1212      var self = this;
1213      var w = this._w(), h = this._h();
1214      // Large blurred color blobs
1215      var blobCount = Math.min(6, Math.ceil(this.opts.count * 0.15));
1216      for (var i = 0; i < blobCount; i++) {
1217        (function() {
1218          var sz = randomRange(100, 250) * self.opts.size;
1219          var color = randomColorFromArray(self.colors);
1220          var el = createEl('div', {
1221            position: 'absolute',
1222            width: sz + 'px',
1223            height: sz + 'px',
1224            backgroundColor: color,
1225            borderRadius: '50%',
1226            filter: 'blur(' + randomRange(40, 80) + 'px)',
1227            left: randomRange(-sz * 0.3, w - sz * 0.5) + 'px',
1228            top: randomRange(-sz * 0.3, h - sz * 0.5) + 'px',
1229            opacity: String(self.opts.opacity * 0.3)
1230          }, self.container);
1231          var dur = randomRange(8, 15) / self.opts.speed;
1232          self._addTween(gsap.to(el, {
1233            x: randomRange(-50, 50),
1234            y: randomRange(-50, 50),
1235            scale: randomRange(0.8, 1.3),
1236            duration: dur,
1237            yoyo: true, repeat: -1,
1238            ease: 'sine.inOut'
1239          }));
1240        })();
1241      }
1242      // Small twinkling stars
1243      for (var j = 0; j < this.opts.count; j++) {
1244        (function() {
1245          var sz = randomRange(1, 3) * self.opts.size;
1246          var el = createEl('div', {
1247            position: 'absolute',
1248            width: sz + 'px',
1249            height: sz + 'px',
1250            backgroundColor: '#ffffff',
1251            borderRadius: '50%',
1252            left: randomRange(0, w) + 'px',
1253            top: randomRange(0, h) + 'px',
1254            opacity: String(randomRange(0.3, self.opts.opacity))
1255          }, self.container);
1256          self._addTween(gsap.to(el, {
1257            opacity: randomRange(0.1, 0.4),
1258            duration: randomRange(1, 4) / self.opts.speed,
1259            yoyo: true, repeat: -1, ease: 'sine.inOut',
1260            delay: randomRange(0, 3)
1261          }));
1262        })();
1263      }
1264    }
1265
1266    // 18. Comets ------------------------------------------------------------
1267    _initComets() {
1268      var self = this;
1269      var spawnComet = function() {
1270        var w = self._w(), h = self._h();
1271        var sz = randomRange(4, 8) * self.opts.size;
1272        var tailLen = randomRange(60, 120) * self.opts.size;
1273        var color = randomColorFromArray(self.colors);
1274        var startY = randomRange(h * 0.1, h * 0.7);
1275        var el = createEl('div', {
1276          position: 'absolute',
1277          width: tailLen + 'px',
1278          height: sz + 'px',
1279          background: 'linear-gradient(to right, transparent, ' + color + ')',
1280          borderRadius: '0 50% 50% 0',
1281          left: -tailLen + 'px',
1282          top: startY + 'px',
1283          opacity: String(self.opts.opacity)
1284        }, self.container);
1285        if (self.opts.glow) {
1286          el.style.boxShadow = '0 0 ' + (15 * self.opts.glowIntensity) + 'px ' + color;
1287        }
1288        var dur = randomRange(1, 2.5) / self.opts.speed;
1289        self._addTween(gsap.to(el, {
1290          x: w + tailLen * 2,
1291          y: randomRange(-30, 80),
1292          duration: dur,
1293          ease: 'power1.in',
1294          onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
1295        }));
1296      };
1297      spawnComet();
1298      this._addInterval(setInterval(function() {
1299        if (!self._paused) spawnComet();
1300      }, this.opts.burstInterval));
1301    }
1302
1303    // =========================================================================
1304    //  TECH EFFECTS (19-21)
1305    // =========================================================================
1306
1307    // 19. Matrix ------------------------------------------------------------
1308    _initMatrix() {
1309      var self = this;
1310      var w = this._w(), h = this._h();
1311      var colWidth = 20 * this.opts.size;
1312      var cols = Math.floor(w / colWidth);
1313      var colCount = Math.min(cols, this.opts.count);
1314      for (var i = 0; i < colCount; i++) {
1315        (function(colIdx) {
1316          var x = colIdx * colWidth + colWidth / 2;
1317          var charCount = Math.floor(h / (14 * self.opts.size)) + 5;
1318          var delay = randomRange(0, 3);
1319          var spawnColumn = function() {
1320            var yPos = 0;
1321            for (var c = 0; c < charCount; c++) {
1322              (function(charIdx) {
1323                var sz = randomRange(10, 14) * self.opts.size;
1324                var el = createEl('div', {
1325                  position: 'absolute',
1326                  left: x + 'px',
1327                  top: -sz + 'px',
1328                  fontSize: sz + 'px',
1329                  fontFamily: 'monospace',
1330                  color: charIdx === 0 ? '#ffffff' : '#00ff00',
1331                  opacity: '0',
1332                  pointerEvents: 'none'
1333                }, self.container);
1334                el.textContent = randomFrom(MATRIX_CHARS.split(''));
1335                var charDelay = delay + charIdx * 0.05;
1336                var dur = randomRange(0.05, 0.1) / self.opts.speed;
1337                var tl = gsap.timeline({ delay: charDelay });
1338                self._addTimeline(tl);
1339                tl.set(el, { y: charIdx * sz * 1.2 });
1340                tl.to(el, {
1341                  opacity: self.opts.opacity * (1 - charIdx / charCount * 0.8),
1342                  duration: dur
1343                });
1344                tl.to(el, {
1345                  opacity: 0,
1346                  duration: randomRange(0.5, 1.5) / self.opts.speed,
1347                  delay: randomRange(0.5, 2),
1348                  onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
1349                });
1350                // Random character change
1351                self._addInterval(setInterval(function() {
1352                  if (el.parentNode) el.textContent = randomFrom(MATRIX_CHARS.split(''));
1353                }, randomRange(100, 500)));
1354              })(c);
1355            }
1356          };
1357          spawnColumn();
1358          self._addInterval(setInterval(function() {
1359            if (!self._paused) spawnColumn();
1360          }, randomRange(3000, 6000) / self.opts.speed));
1361        })(i);
1362      }
1363    }
1364
1365    // 20. Network -----------------------------------------------------------
1366    _initNetwork() {
1367      var self = this;
1368      var w = this._w(), h = this._h();
1369      var svg = createSVG(this.container, w, h);
1370      var nodes = [];
1371      var lines = [];
1372
1373      for (var i = 0; i < this.opts.count; i++) {
1374        (function(idx) {
1375          var sz = randomRange(3, 6) * self.opts.size;
1376          var color = randomColorFromArray(self.colors);
1377          var el = createEl('div', {
1378            position: 'absolute',
1379            width: sz + 'px',
1380            height: sz + 'px',
1381            backgroundColor: color,
1382            borderRadius: '50%',
1383            left: randomRange(0, w) + 'px',
1384            top: randomRange(0, h) + 'px',
1385            opacity: String(self.opts.opacity)
1386          }, self.container);
1387          self._applyGlow(el, color);
1388          var node = { el: el, vx: randomRange(-0.5, 0.5), vy: randomRange(-0.5, 0.5), color: color };
1389          nodes.push(node);
1390          // Slow drift
1391          var dur = randomRange(6, 12) / self.opts.speed;
1392          self._addTween(gsap.to(el, {
1393            x: '+=' + randomRange(-100, 100),
1394            y: '+=' + randomRange(-100, 100),
1395            duration: dur,
1396            yoyo: true, repeat: -1,
1397            ease: 'sine.inOut'
1398          }));
1399        })(i);
1400      }
1401
1402      // Pre-create line pool
1403      var maxLines = Math.min(this.opts.count * 3, 200);
1404      for (var l = 0; l < maxLines; l++) {
1405        var line = createSVGLine(svg);
1406        line.setAttribute('stroke', self.opts.color1);
1407        line.setAttribute('stroke-width', '1');
1408        line.setAttribute('opacity', '0');
1409        lines.push(line);
1410      }
1411
1412      // Update connections via rAF
1413      var updateConnections = function() {
1414        if (self._destroyed) return;
1415        var lineIdx = 0;
1416        var cDist = self.opts.connectionDistance;
1417        for (var a = 0; a < nodes.length; a++) {
1418          var ra = nodes[a].el.getBoundingClientRect();
1419          var cr = self.container.getBoundingClientRect();
1420          var ax = ra.left - cr.left + ra.width / 2;
1421          var ay = ra.top - cr.top + ra.height / 2;
1422          for (var b = a + 1; b < nodes.length; b++) {
1423            var rb = nodes[b].el.getBoundingClientRect();
1424            var bx = rb.left - cr.left + rb.width / 2;
1425            var by = rb.top - cr.top + rb.height / 2;
1426            var d = dist(ax, ay, bx, by);
1427            if (d < cDist && lineIdx < lines.length) {
1428              var opacity = (1 - d / cDist) * self.opts.opacity;
1429              lines[lineIdx].setAttribute('x1', ax);
1430              lines[lineIdx].setAttribute('y1', ay);
1431              lines[lineIdx].setAttribute('x2', bx);
1432              lines[lineIdx].setAttribute('y2', by);
1433              lines[lineIdx].setAttribute('opacity', opacity);
1434              lineIdx++;
1435            }
1436          }
1437        }
1438        // Hide unused lines
1439        for (var k = lineIdx; k < lines.length; k++) {
1440          lines[k].setAttribute('opacity', '0');
1441        }
1442        self._addRaf(requestAnimationFrame(updateConnections));
1443      };
1444      this._addRaf(requestAnimationFrame(updateConnections));
1445    }
1446
1447    // 21. DNA ---------------------------------------------------------------
1448    _initDna() {
1449      var self = this;
1450      var w = this._w(), h = this._h();
1451      var cx = w / 2;
1452      var pairs = this.opts.count;
1453      var vertSpacing = h / (pairs + 1);
1454      var radiusX = Math.min(w * 0.15, 80) * this.opts.size;
1455
1456      var helixContainer = createEl('div', {
1457        position: 'absolute',
1458        width: '100%', height: '100%',
1459        top: '0', left: '0'
1460      }, this.container);
1461
1462      var svg = createSVG(helixContainer, w, h);
1463      var allNodes = [];
1464
1465      for (var i = 0; i < pairs; i++) {
1466        (function(idx) {
1467          var yPos = vertSpacing * (idx + 1);
1468          var phase = (idx / pairs) * Math.PI * 4;
1469          var sz = randomRange(5, 8) * self.opts.size;
1470
1471          // Left node
1472          var leftEl = createEl('div', {
1473            position: 'absolute',
1474            width: sz + 'px',
1475            height: sz + 'px',
1476            backgroundColor: self.opts.color1,
1477            borderRadius: '50%',
1478            left: cx + 'px',
1479            top: yPos + 'px',
1480            opacity: String(self.opts.opacity)
1481          }, helixContainer);
1482          self._applyGlow(leftEl, self.opts.color1);
1483
1484          // Right node
1485          var rightEl = createEl('div', {
1486            position: 'absolute',
1487            width: sz + 'px',
1488            height: sz + 'px',
1489            backgroundColor: self.opts.color2,
1490            borderRadius: '50%',
1491            left: cx + 'px',
1492            top: yPos + 'px',
1493            opacity: String(self.opts.opacity)
1494          }, helixContainer);
1495          self._applyGlow(rightEl, self.opts.color2);
1496
1497          // Connector line
1498          var line = createSVGLine(svg);
1499          line.setAttribute('stroke', 'rgba(255,255,255,0.3)');
1500          line.setAttribute('stroke-width', '1');
1501
1502          allNodes.push({ left: leftEl, right: rightEl, line: line, y: yPos, phase: phase });
1503        })(i);
1504      }
1505
1506      // Animate rotation
1507      var time = { value: 0 };
1508      self._addTween(gsap.to(time, {
1509        value: Math.PI * 2,
1510        duration: 4 / self.opts.speed,
1511        repeat: -1,
1512        ease: 'none',
1513        onUpdate: function() {
1514          for (var j = 0; j < allNodes.length; j++) {
1515            var n = allNodes[j];
1516            var angle = n.phase + time.value;
1517            var lx = cx + Math.cos(angle) * radiusX;
1518            var rx = cx + Math.cos(angle + Math.PI) * radiusX;
1519            var lz = Math.sin(angle);
1520            var rz = Math.sin(angle + Math.PI);
1521            gsap.set(n.left, { left: lx + 'px', scale: 0.6 + lz * 0.4, zIndex: lz > 0 ? 2 : 0 });
1522            gsap.set(n.right, { left: rx + 'px', scale: 0.6 + rz * 0.4, zIndex: rz > 0 ? 2 : 0 });
1523            n.line.setAttribute('x1', lx);
1524            n.line.setAttribute('y1', n.y);
1525            n.line.setAttribute('x2', rx);
1526            n.line.setAttribute('y2', n.y);
1527            n.line.setAttribute('opacity', 0.3 * self.opts.opacity);
1528          }
1529        }
1530      }));
1531    }
1532
1533    // =========================================================================
1534    //  ABSTRACT EFFECTS (22-30)
1535    // =========================================================================
1536
1537    // 22. Wave --------------------------------------------------------------
1538    _initWave() {
1539      var self = this;
1540      var w = this._w(), h = this._h();
1541      var cols = Math.ceil(Math.sqrt(this.opts.count * (w / h)));
1542      var rows = Math.ceil(this.opts.count / cols);
1543      var spacingX = w / (cols + 1);
1544      var spacingY = h / (rows + 1);
1545      var dots = [];
1546
1547      for (var r = 0; r < rows; r++) {
1548        for (var c = 0; c < cols; c++) {
1549          (function(row, col) {
1550            var sz = randomRange(3, 5) * self.opts.size;
1551            var x = spacingX * (col + 1);
1552            var y = spacingY * (row + 1);
1553            var color = randomColorFromArray(self.colors);
1554            var el = createEl('div', {
1555              position: 'absolute',
1556              width: sz + 'px',
1557              height: sz + 'px',
1558              backgroundColor: color,
1559              borderRadius: '50%',
1560              left: x + 'px',
1561              top: y + 'px',
1562              opacity: String(self.opts.opacity)
1563            }, self.container);
1564            self._applyGlow(el, color);
1565            dots.push({ el: el, baseX: x, baseY: y, col: col, row: row });
1566          })(r, c);
1567        }
1568      }
1569
1570      var time = { value: 0 };
1571      self._addTween(gsap.to(time, {
1572        value: Math.PI * 200,
1573        duration: 200 / self.opts.speed,
1574        repeat: -1,
1575        ease: 'none',
1576        onUpdate: function() {
1577          for (var i = 0; i < dots.length; i++) {
1578            var d = dots[i];
1579            var offsetY = Math.sin(time.value + d.col * 0.5 + d.row * 0.3) * 20 * self.opts.size;
1580            gsap.set(d.el, { y: offsetY });
1581          }
1582        }
1583      }));
1584    }
1585
1586    // 23. Magnetic ----------------------------------------------------------
1587    _initMagnetic() {
1588      var self = this;
1589      var w = this._w(), h = this._h();
1590      var cx = w / 2, cy = h / 2;
1591
1592      for (var i = 0; i < this.opts.count; i++) {
1593        (function(idx) {
1594          var sz = randomRange(3, 6) * self.opts.size;
1595          var color = randomColorFromArray(self.colors);
1596          var el = createEl('div', {
1597            position: 'absolute',
1598            width: sz + 'px',
1599            height: sz + 'px',
1600            backgroundColor: color,
1601            borderRadius: '50%',
1602            left: cx + 'px',
1603            top: cy + 'px',
1604            opacity: String(self.opts.opacity)
1605          }, self.container);
1606          self._applyGlow(el, color);
1607
1608          var rx = randomRange(30, Math.min(w, h) * 0.4) * self.opts.size;
1609          var ry = rx * randomRange(0.3, 0.9);
1610          var phase = randomRange(0, Math.PI * 2);
1611          var dur = randomRange(4, 10) / self.opts.speed;
1612
1613          var time = { value: phase };
1614          self._addTween(gsap.to(time, {
1615            value: phase + Math.PI * 2,
1616            duration: dur,
1617            repeat: -1,
1618            ease: 'none',
1619            onUpdate: function() {
1620              var x = Math.cos(time.value) * rx;
1621              var y = Math.sin(time.value) * ry;
1622              gsap.set(el, { x: x, y: y });
1623            }
1624          }));
1625        })(i);
1626      }
1627    }
1628
1629    // 24. Aurora -------------------------------------------------------------
1630    _initAurora() {
1631      var self = this;
1632      var w = this._w(), h = this._h();
1633
1634      // Horizontal blurred ribbons
1635      var ribbonCount = Math.min(5, Math.ceil(this.opts.count * 0.1));
1636      for (var i = 0; i < ribbonCount; i++) {
1637        (function() {
1638          var ribbonH = randomRange(40, 80) * self.opts.size;
1639          var color = randomColorFromArray(self.colors);
1640          var yPos = randomRange(h * 0.1, h * 0.6);
1641          var el = createEl('div', {
1642            position: 'absolute',
1643            width: w * 1.5 + 'px',
1644            height: ribbonH + 'px',
1645            background: 'linear-gradient(90deg, transparent 0%, ' + color + ' 30%, ' + self.opts.c
1645olor2 + ' 70%, transparent 100%)',
1646            filter: 'blur(' + randomRange(20, 40) + 'px)',
1647            left: -w * 0.25 + 'px',
1648            top: yPos + 'px',
1649            opacity: String(self.opts.opacity * 0.4)
1650          }, self.container);
1651          var dur = randomRange(6, 12) / self.opts.speed;
1652          self._addTween(gsap.to(el, {
1653            x: randomRange(-100, 100),
1654            scaleY: randomRange(0.5, 1.5),
1655            opacity: self.opts.opacity * randomRange(0.2, 0.5),
1656            duration: dur,
1657            yoyo: true, repeat: -1,
1658            ease: 'sine.inOut'
1659          }));
1660        })();
1661      }
1662
1663      // Small particles
1664      for (var j = 0; j < this.opts.count; j++) {
1665        (function() {
1666          var sz = randomRange(1, 3) * self.opts.size;
1667          var color = randomColorFromArray(self.colors);
1668          var el = createEl('div', {
1669            position: 'absolute',
1670            width: sz + 'px',
1671            height: sz + 'px',
1672            backgroundColor: color,
1673            borderRadius: '50%',
1674            left: randomRange(0, w) + 'px',
1675            top: randomRange(h * 0.05, h * 0.7) + 'px',
1676            opacity: String(randomRange(0.2, self.opts.opacity))
1677          }, self.container);
1678          self._applyGlow(el, color);
1679          self._addTween(gsap.to(el, {
1680            x: randomRange(-30, 30),
1681            y: randomRange(-20, 20),
1682            opacity: randomRange(0.1, self.opts.opacity * 0.6),
1683            duration: randomRange(2, 5) / self.opts.speed,
1684            yoyo: true, repeat: -1,
1685            ease: 'sine.inOut',
1686            delay: randomRange(0, 3)
1687          }));
1688        })();
1689      }
1690    }
1691
1692    // 25. Geometric ---------------------------------------------------------
1693    _initGeometric() {
1694      var self = this;
1695      var w = this._w(), h = this._h();
1696
1697      for (var i = 0; i < this.opts.count; i++) {
1698        (function() {
1699          var shape = randomFrom(GEOMETRIC_SHAPES);
1700          var sz = randomRange(15, 40) * self.opts.size;
1701          var color = randomColorFromArray(self.colors);
1702          var el = createEl('div', {
1703            position: 'absolute',
1704            width: sz + 'px',
1705            height: sz + 'px',
1706            border: '2px solid ' + color,
1707            backgroundColor: 'transparent',
1708            left: randomRange(0, w) + 'px',
1709            top: randomRange(0, h) + 'px',
1710            opacity: String(self.opts.opacity * 0.7)
1711          }, self.container);
1712          self._applyGlow(el, color);
1713
1714          if (shape === 'circle') {
1715            el.style.borderRadius = '50%';
1716          } else if (shape === 'square') {
1717            el.style.borderRadius = '4px';
1718          } else if (shape === 'triangle') {
1719            el.style.border = 'none';
1720            el.style.width = '0';
1721            el.style.height = '0';
1722            el.style.borderLeft = sz / 2 + 'px solid transparent';
1723            el.style.borderRight = sz / 2 + 'px solid transparent';
1724            el.style.borderBottom = sz + 'px solid ' + color;
1725            el.style.backgroundColor = 'transparent';
1726          } else if (shape === 'hexagon') {
1727            el.style.borderRadius = '25%';
1728            el.style.transform = 'rotate(30deg)';
1729          }
1730
1731          var dur = randomRange(5, 12) / self.opts.speed;
1732          self._addTween(gsap.to(el, {
1733            x: randomRange(-80, 80),
1734            y: randomRange(-80, 80),
1735            rotation: '+=' + randomRange(90, 360),
1736            duration: dur,
1737            yoyo: true, repeat: -1,
1738            ease: 'sine.inOut'
1739          }));
1740        })();
1741      }
1742    }
1743
1744    // 26. Pulse Rings -------------------------------------------------------
1745    _initPulseRings() {
1746      var self = this;
1747      var w = this._w(), h = this._h();
1748      var cx = w / 2, cy = h / 2;
1749
1750      var spawnRing = function() {
1751        var sz = 10 * self.opts.size;
1752        var maxSz = Math.min(w, h) * 0.8;
1753        var color = randomColorFromArray(self.colors);
1754        var el = createEl('div', {
1755          position: 'absolute',
1756          width: sz + 'px',
1757          height: sz + 'px',
1758          border: '2px solid ' + color,
1759          borderRadius: '50%',
1760          left: cx - sz / 2 + 'px',
1761          top: cy - sz / 2 + 'px',
1762          opacity: String(self.opts.opacity)
1763        }, self.container);
1764        if (self.opts.glow) {
1765          el.style.boxShadow = '0 0 ' + (10 * self.opts.glowIntensity) + 'px ' + color + ', inset 0 0 ' + (5 * 
1765self.opts.glowIntensity) + 'px ' + color;
1766        }
1767        var dur = randomRange(2, 4) / self.opts.speed;
1768        self._addTween(gsap.to(el, {
1769          width: maxSz, height: maxSz,
1770          left: cx - maxSz / 2, top: cy - maxSz / 2,
1771          opacity: 0,
1772          borderWidth: 0.5,
1773          duration: dur,
1774          ease: 'power1.out',
1775          onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
1776        }));
1777      };
1778
1779      spawnRing();
1780      var interval = Math.max(300, this.opts.burstInterval / this.opts.count * 3);
1781      this._addInterval(setInterval(function() {
1782        if (!self._paused) spawnRing();
1783      }, interval));
1784    }
1785
1786    // 27. Vortex ------------------------------------------------------------
1787    _initVortex() {
1788      var self = this;
1789      var w = this._w(), h = this._h();
1790      var cx = w / 2, cy = h / 2;
1791
1792      for (var i = 0; i < this.opts.count; i++) {
1793        (function(idx) {
1794          var sz = randomRange(3, 6) * self.opts.size;
1795          var color = randomColorFromArray(self.colors);
1796          var el = createEl('div', {
1797            position: 'absolute',
1798            width: sz + 'px',
1799            height: sz + 'px',
1800            backgroundColor: color,
1801            borderRadius: '50%',
1802            left: cx + 'px',
1803            top: cy + 'px',
1804            opacity: String(self.opts.opacity)
1805          }, self.container);
1806          self._applyGlow(el, color);
1807
1808          var angle = randomRange(0, Math.PI * 2);
1809          var maxRadius = randomRange(Math.min(w, h) * 0.3, Math.min(w, h) * 0.5);
1810          var dur = randomRange(3, 7) / self.opts.speed;
1811          var delay = randomRange(0, 3);
1812
1813          var tl = gsap.timeline({ repeat: -1, delay: delay });
1814          self._addTimeline(tl);
1815
1816          // Spiral inward
1817          var proxy = { r: maxRadius, a: angle };
1818          tl.to(proxy, {
1819            r: 5,
1820            a: angle + Math.PI * 6,
1821            duration: dur,
1822            ease: 'power1.in',
1823            onUpdate: function() {
1824              var x = Math.cos(proxy.a) * proxy.r;
1825              var y = Math.sin(proxy.a) * proxy.r;
1826              gsap.set(el, { x: x, y: y, scale: proxy.r / maxRadius });
1827            },
1828            onComplete: function() {
1829              proxy.r = maxRadius;
1830              proxy.a = randomRange(0, Math.PI * 2);
1831              gsap.set(el, { scale: 1 });
1832            }
1833          });
1834        })(i);
1835      }
1836    }
1837
1838    // 28. Sparks ------------------------------------------------------------
1839    _initSparks() {
1840      var self = this;
1841      var w = this._w(), h = this._h();
1842
1843      var spawnSpark = function() {
1844        var sz = randomRange(2, 5) * self.opts.size;
1845        var color = randomFrom(['#ff4500', '#ff6600', '#ff8c00', '#ffa500', '#ffd700', '#ffff00']);
1846        var startX = randomRange(w * 0.2, w * 0.8);
1847        var el = createEl('div', {
1848          position: 'absolute',
1849          width: sz + 'px',
1850          height: sz + 'px',
1851          backgroundColor: color,
1852          borderRadius: '50%',
1853          left: startX + 'px',
1854          top: h + 'px',
1855          opacity: String(self.opts.opacity)
1856        }, self.container);
1857        if (self.opts.glow) {
1858          el.style.boxShadow = '0 0 ' + (8 * self.opts.glowIntensity) + 'px ' + color;
1859        }
1860        var dur = randomRange(1, 3) / self.opts.speed;
1861        self._addTween(gsap.to(el, {
1862          y: -(h * randomRange(0.3, 0.9)),
1863          x: randomRange(-80, 80) + self._windOffset(dur),
1864          opacity: 0,
1865          scale: 0.1,
1866          duration: dur,
1867          ease: 'power2.out',
1868          onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
1869        }));
1870      };
1871
1872      for (var i = 0; i < this.opts.count; i++) {
1873        setTimeout(function() { spawnSpark(); }, randomRange(0, 1000));
1874      }
1875      this._addInterval(setInterval(function() {
1876        if (!self._paused) {
1877          for (var j = 0; j < 3; j++) spawnSpark();
1878        }
1879      }, 150 / this.opts.speed));
1880    }
1881
1882    // 29. Dust --------------------------------------------------------------
1883    _initDust() {
1884      var self = this;
1885      var w = this._w(), h = this._h();
1886      // Light beam area (center-ish diagonal band)
1887      var beamCx = w * 0.5, beamCy = h * 0.5;
1888      var beamW = w * 0.6, beamH = h * 0.8;
1889
1890      for (var i = 0; i < this.opts.count; i++) {
1891        (function() {
1892          var sz = randomRange(1, 3) * self.opts.size;
1893          var startX = beamCx + randomRange(-beamW / 2, beamW / 2);
1894          var startY = beamCy + randomRange(-beamH / 2, beamH / 2);
1895          var el = createEl('div', {
1896            position: 'absolute',
1897            width: sz + 'px',
1898            height: sz + 'px',
1899            backgroundColor: 'rgba(255,255,255,0.8)',
1900            borderRadius: '50%',
1901            left: startX + 'px',
1902            top: startY + 'px',
1903            opacity: String(randomRange(0.1, self.opts.opacity * 0.5))
1904          }, self.container);
1905          if (self.opts.glow) {
1906            el.style.boxShadow = '0 0 ' + (4 * self.opts.glowIntensity) + 'px rgba(255,255,255,0.3)';
1907          }
1908          var dur = randomRange(5, 15) / self.opts.speed;
1909          self._addTween(gsap.to(el, {
1910            x: randomRange(-40, 40) + self._windOffset(dur),
1911            y: randomRange(-30, 30),
1912            opacity: randomRange(0.05, self.opts.opacity * 0.4),
1913            duration: dur,
1914            yoyo: true, repeat: -1,
1915            ease: 'sine.inOut',
1916            delay: randomRange(0, 5)
1917          }));
1918        })();
1919      }
1920    }
1921
1922    // 30. Blobs -------------------------------------------------------------
1923    _initBlobs() {
1924      var self = this;
1925      var w = this._w(), h = this._h();
1926      var count = Math.min(this.opts.count, 12);
1927
1928      for (var i = 0; i < count; i++) {
1929        (function() {
1930          var sz = randomRange(60, 150) * self.opts.size;
1931          var color = randomColorFromArray(self.colors);
1932          var el = createEl('div', {
1933            position: 'absolute',
1934            width: sz + 'px',
1935            height: sz + 'px',
1936            backgroundColor: color,
1937            borderRadius: randomRange(30, 50) + '% ' + randomRange(50, 70) + '% ' + randomRange(30, 60) + '% ' + randomRange(40, 70) + '%',
1938            filter: 'blur(' + randomRange(20, 40) + 'px)',
1939            left: randomRange(-sz * 0.3, w - sz * 0.5) + 'px',
1940            top: randomRange(-sz * 0.3, h - sz * 0.5) + 'px',
1941            opacity: String(self.opts.opacity * 0.4)
1942          }, self.container);
1943
1944          var dur = randomRange(8, 18) / self.opts.speed;
1945          // Morph borderRadius
1946          self._addTween(gsap.to(el, {
1947            borderRadius: function() {
1948              return randomRange(30, 60) + '% ' + randomRange(40, 70) + '% ' + randomRange(30, 60) + '% ' + randomRange(40, 70) + '%';
1949            },
1950            x: randomRange(-60, 60),
1951            y: randomRange(-60, 60),
1952            scale: randomRange(0.7, 1.4),
1953            duration: dur,
1954            yoyo: true, repeat: -1,
1955            ease: 'sine.inOut'
1956          }));
1957        })();
1958      }
1959    }
1960
1961    // =========================================================================
1962    //  INTERACTIVE MOUSE EFFECTS (31-40)
1963    // =========================================================================
1964
1965    // 31. Mouse Attract -----------------------------------------------------
1966    _initMouseAttract() {
1967      var self = this;
1968      var w = this._w(), h = this._h();
1969      var nodes = [];
1970
1971      for (var i = 0; i < this.opts.count; i++) {
1972        (function() {
1973          var sz = randomRange(3, 7) * self.opts.size;
1974          var color = randomColorFromArray(self.colors);
1975          var baseX = randomRange(0, w);
1976          var baseY = randomRange(0, h);
1977          var el = createEl('div', {
1978            position: 'absolute',
1979            width: sz + 'px',
1980            height: sz + 'px',
1981            backgroundColor: color,
1982            borderRadius: '50%',
1983            left: baseX + 'px',
1984            top: baseY + 'px',
1985            opacity: String(self.opts.opacity)
1986          }, self.container);
1987          self._applyGlow(el, color);
1988          nodes.push({ el: el, baseX: baseX, baseY: baseY, x: baseX, y: baseY });
1989        })();
1990      }
1991
1992      var update = function() {
1993        if (self._destroyed) return;
1994        for (var i = 0; i < nodes.length; i++) {
1995          var n = nodes[i];
1996          if (self._mouseInside) {
1997            var dx = self._mouseX - n.x;
1998            var dy = self._mouseY - n.y;
1999            var d = Math.sqrt(dx * dx + dy * dy);
2000            if (d < self.opts.forceRadius) {
2001              var force = (1 - d / self.opts.forceRadius) * self.opts.forceStrength * 2;
2002              n.x += dx * force * 0.05;
2003              n.y += dy * force * 0.05;
2004            } else {
2005              n.x += (n.baseX - n.x) * 0.02;
2006              n.y += (n.baseY - n.y) * 0.02;
2007            }
2008          } else {
2009            n.x += (n.baseX - n.x) * 0.02;
2010            n.y += (n.baseY - n.y) * 0.02;
2011          }
2012          gsap.set(n.el, { left: n.x + 'px', top: n.y + 'px' });
2013        }
2014        self._addRaf(requestAnimationFrame(update));
2015      };
2016      this._addRaf(requestAnimationFrame(update));
2017    }
2018
2019    // 32. Mouse Repel -------------------------------------------------------
2020    _initMouseRepel() {
2021      var self = this;
2022      var w = this._w(), h = this._h();
2023      var nodes = [];
2024
2025      for (var i = 0; i < this.opts.count; i++) {
2026        (function() {
2027          var sz = randomRange(3, 7) * self.opts.size;
2028          var color = randomColorFromArray(self.colors);
2029          var baseX = randomRange(0, w);
2030          var baseY = randomRange(0, h);
2031          var el = createEl('div', {
2032            position: 'absolute',
2033            width: sz + 'px',
2034            height: sz + 'px',
2035            backgroundColor: color,
2036            borderRadius: '50%',
2037            left: baseX + 'px',
2038            top: baseY + 'px',
2039            opacity: String(self.opts.opacity)
2040          }, self.container);
2041          self._applyGlow(el, color);
2042          nodes.push({ el: el, baseX: baseX, baseY: baseY, x: baseX, y: baseY });
2043        })();
2044      }
2045
2046      var update = function() {
2047        if (self._destroyed) return;
2048        for (var i = 0; i < nodes.length; i++) {
2049          var n = nodes[i];
2050          if (self._mouseInside) {
2051            var dx = n.x - self._mouseX;
2052            var dy = n.y - self._mouseY;
2053            var d = Math.sqrt(dx * dx + dy * dy);
2054            if (d < self.opts.forceRadius && d > 0) {
2055              var force = (1 - d / self.opts.forceRadius) * self.opts.forceStrength * 5;
2056              n.x += (dx / d) * force;
2057              n.y += (dy / d) * force;
2058            } else {
2059              n.x += (n.baseX - n.x) * 0.03;
2060              n.y += (n.baseY - n.y) * 0.03;
2061            }
2062          } else {
2063            n.x += (n.baseX - n.x) * 0.03;
2064            n.y += (n.baseY - n.y) * 0.03;
2065          }
2066          gsap.set(n.el, { left: n.x + 'px', top: n.y + 'px' });
2067        }
2068        self._addRaf(requestAnimationFrame(update));
2069      };
2070      this._addRaf(requestAnimationFrame(update));
2071    }
2072
2073    // 33. Mouse Trail -------------------------------------------------------
2074    _initMouseTrail() {
2075      var self = this;
2076      var lastX = -1, lastY = -1;
2077
2078      var spawnTrail = function() {
2079        if (self._destroyed || !self._mouseInside) return;
2080        if (self._mouseX === lastX && self._mouseY === lastY) return;
2081        lastX = self._mouseX;
2082        lastY = self._mouseY;
2083        var sz = randomRange(4, 10) * self.opts.size;
2084        var color = randomColorFromArray(self.colors);
2085        var el = createEl('div', {
2086          position: 'absolute',
2087          width: sz + 'px',
2088          height: sz + 'px',
2089          backgroundColor: color,
2090          borderRadius: '50%',
2091          left: self._mouseX - sz / 2 + 'px',
2092          top: self._mouseY - sz / 2 + 'px',
2093          opacity: String(self.opts.opacity),
2094          pointerEvents: 'none'
2095        }, self.container);
2096        self._applyGlow(el, color);
2097        self._addTween(gsap.to(el, {
2098          scale: 0,
2099          opacity: 0,
2100          x: randomRange(-20, 20),
2101          y: randomRange(-20, 20),
2102          duration: randomRange(0.5, 1.2) / self.opts.speed,
2103          ease: 'power2.out',
2104          onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
2105        }));
2106      };
2107
2108      this._addInterval(setInterval(spawnTrail, 30));
2109    }
2110
2111    // 34. Hover Burst -------------------------------------------------------
2112    _initHoverBurst() {
2113      var self = this;
2114      var lastBurstX = -1, lastBurstY = -1;
2115      var minDist = 30;
2116
2117      var doBurst = function() {
2118        if (self._destroyed || !self._mouseInside) return;
2119        var dx = self._mouseX - lastBurstX;
2120        var dy = self._mouseY - lastBurstY;
2121        if (Math.sqrt(dx * dx + dy * dy) < minDist) return;
2122        lastBurstX = self._mouseX;
2123        lastBurstY = self._mouseY;
2124
2125        var bCount = Math.min(8, Math.ceil(self.opts.count * 0.2));
2126        for (var i = 0; i < bCount; i++) {
2127          var sz = randomRange(2, 5) * self.opts.size;
2128          var color = randomColorFromArray(self.colors);
2129          var el = createEl('div', {
2130            position: 'absolute',
2131            width: sz + 'px',
2132            height: sz + 'px',
2133            backgroundColor: color,
2134            borderRadius: '50%',
2135            left: self._mouseX + 'px',
2136            top: self._mouseY + 'px',
2137            opacity: String(self.opts.opacity),
2138            pointerEvents: 'none'
2139          }, self.container);
2140          self._applyGlow(el, color);
2141          var angle = randomRange(0, Math.PI * 2);
2142          var r = randomRange(20, 60) * self.opts.size;
2143          self._addTween(gsap.to(el, {
2144            x: Math.cos(angle) * r,
2145            y: Math.sin(angle) * r,
2146            opacity: 0,
2147            scale: 0.2,
2148            duration: randomRange(0.4, 0.8) / self.opts.speed,
2149            ease: 'power2.out',
2150            onComplete: function() { if (el.parentNode) el.parentNode.removeChild(el); }
2151          }));
2152        }
2153      };
2154
2155      this._addInterval(setInterval(doBurst, 50));
2156    }
2157
2158    // 35. Swarm -------------------------------------------------------------
2159    _initSwarm() {
2160      var self = this;
2161      var w = this._w(), h = this._h();
2162      var chain = [];
2163
2164      for (var i = 0; i < this.opts.count; i++) {
2165        (function(idx) {
2166          var sz = randomRange(4, 8) * self.opts.size * (1 - idx / self.opts.count * 0.5);
2167          var color = lerpColor(self.opts.color1, self.opts.color2, idx / self.opts.count);
2168          var el = createEl('div', {
2169            position: 'absolute',
2170            width: sz + 'px',
2171            height: sz + 'px',
2172            backgroundColor: color,
2173            borderRadius: '50%',
2174            left: w / 2 + 'px',
2175            top: h / 2 + 'px',
2176            opacity: String(self.opts.opacity * (1 - idx / self.opts.count * 0.4)),
2177            pointerEvents: 'none'
2178          }, self.container);
2179          self._applyGlow(el, color);
2180          chain.push({ el: el, x: w / 2, y: h / 2 });
2181        })(i);
2182      }
2183
2184      var update = function() {
2185        if (self._destroyed) return;
2186        for (var i = 0; i < chain.length; i++) {
2187          var target;
2188          if (i === 0) {
2189            target = self._mouseInside
2190              ? { x: self._mouseX, y: self._mouseY }
2191              : { x: w / 2, y: h / 2 };
2192          } else {
2193            target = chain[i - 1];
2194          }
2195          var easing = 0.15 - i * 0.002;
2196          if (easing < 0.02) easing = 0.02;
2197          chain[i].x += (target.x - chain[i].x) * easing;
2198          chain[i].y += (target.y - chain[i].y) * easing;
2199          gsap.set(chain[i].el, { left: chain[i].x + 'px', top: chain[i].y + 'px' });
2200        }
2201        self._addRaf(requestAnimationFrame(update));
2202      };
2203      this._addRaf(requestAnimationFrame(update));
2204    }
2205
2206    // 36. Gravity Well ------------------------------------------------------
2207    _initGravityWell() {
2208      var self = this;
2209      var w = this._w(), h = this._h();
2210      var particles = [];
2211
2212      for (var i = 0; i < this.opts.count; i++) {
2213        (function() {
2214          var sz = randomRange(3, 6) * self.opts.size;
2215          var color = randomColorFromArray(self.colors);
2216          var px = randomRange(0, w), py = randomRange(0, h);
2217          var el = createEl('div', {
2218            position: 'absolute',
2219            width: sz + 'px',
2220            height: sz + 'px',
2221            backgroundColor: color,
2222            borderRadius: '50%',
2223            left: px + 'px',
2224            top: py + 'px',
2225            opacity: String(self.opts.opacity),
2226            pointerEvents: 'none'
2227          }, self.container);
2228          self._applyGlow(el, color);
2229          particles.push({
2230            el: el, x: px, y: py,
2231            vx: randomRange(-0.5, 0.5),
2232            vy: randomRange(-0.5, 0.5)
2233          });
2234        })();
2235      }
2236
2237      var update = function() {
2238        if (self._destroyed) return;
2239        for (var i = 0; i < particles.length; i++) {
2240          var p = particles[i];
2241          if (self._mouseInside) {
2242            var dx = self._mouseX - p.x;
2243            var dy = self._mouseY - p.y;
2244            var d = Math.sqrt(dx * dx + dy * dy);
2245            if (d > 1) {
2246              var force = self.opts.forceStrength * 50 / (d * d + 100);
2247              p.vx += (dx / d) * force;
2248              p.vy += (dy / d) * force;
2249            }
2250          }
2251          // Damping
2252          p.vx *= 0.98;
2253          p.vy *= 0.98;
2254          p.x += p.vx;
2255          p.y += p.vy;
2256          // Bounds
2257          if (p.x < 0) { p.x = 0; p.vx *= -0.5; }
2258          if (p.x > w) { p.x = w; p.vx *= -0.5; }
2259          if (p.y < 0) { p.y = 0; p.vy *= -0.5; }
2260          if (p.y > h) { p.y = h; p.vy *= -0.5; }
2261          gsap.set(p.el, { left: p.x + 'px', top: p.y + 'px' });
2262        }
2263        self._addRaf(requestAnimationFrame(update));
2264      };
2265      this._addRaf(requestAnimationFrame(update));
2266    }
2267
2268    // 37. Connection Web ----------------------------------------------------
2269    _initConnectionWeb() {
2270      var self = this;
2271      var w = this._w(), h = this._h();
2272      var svg = createSVG(this.container, w, h);
2273      var nodes = [];
2274      var linePool = [];
2275      var maxLines = Math.min(this.opts.count * 2, 150);
2276
2277      for (var i = 0; i < this.opts.count; i++) {
2278        (function() {
2279          var sz = randomRange(3, 6) * self.opts.size;
2280          var color = randomColorFromArray(self.colors);
2281          var px = randomRange(0, w), py = randomRange(0, h);
2282          var el = createEl('div', {
2283            position: 'absolute',
2284            width: sz + 'px',
2285            height: sz + 'px',
2286            backgroundColor: color,
2287            borderRadius: '50%',
2288            left: px + 'px',
2289            top: py + 'px',
2290            opacity: String(self.opts.opacity),
2291            pointerEvents: 'none'
2292          }, self.container);
2293          self._applyGlow(el, color);
2294          nodes.push({ el: el, x: px, y: py });
2295          // Slow drift
2296          self._addTween(gsap.to(el, {
2297            x: '+=' + randomRange(-60, 60),
2298            y: '+=' + randomRange(-60, 60),
2299            duration: randomRange(5, 10) / self.opts.speed,
2300            yoyo: true, repeat: -1, ease: 'sine.inOut'
2301          }));
2302        })();
2303      }
2304
2305      for (var l = 0; l < maxLines; l++) {
2306        var line = createSVGLine(svg);
2307        line.setAttribute('stroke', self.opts.color1);
2308        line.setAttribute('stroke-width', '1');
2309        line.setAttribute('opacity', '0');
2310        linePool.push(line);
2311      }
2312
2313      var update = function() {
2314        if (self._destroyed) return;
2315        var lineIdx = 0;
2316        var cDist = self.opts.connectionDistance;
2317        var cr = self.container.getBoundingClientRect();
2318
2319        // Update node positions
2320        for (var i = 0; i < nodes.length; i++) {
2321          var rect = nodes[i].el.getBoundingClientRect();
2322          nodes[i].x = rect.left - cr.left + rect.width / 2;
2323          nodes[i].y = rect.top - cr.top + rect.height / 2;
2324        }
2325
2326        // Draw lines to cursor
2327        if (self._mouseInside) {
2328          for (var j = 0; j < nodes.length; j++) {
2329            var d = dist(nodes[j].x, nodes[j].y, self._mouseX, self._mouseY);
2330            if (d < cDist && lineIdx < linePool.length) {
2331              var op = (1 - d / cDist) * self.opts.opacity;
2332              linePool[lineIdx].setAttribute('x1', nodes[j].x);
2333              linePool[lineIdx].setAttribute('y1', nodes[j].y);
2334              linePool[lineIdx].setAttribute('x2', self._mouseX);
2335              linePool[lineIdx].setAttribute('y2', self._mouseY);
2336              linePool[lineIdx].setAttribute('opacity', op);
2337              linePool[lineIdx].setAttribute('stroke', self.opts.color2);
2338              lineIdx++;
2339            }
2340          }
2341        }
2342
2343        // Draw lines between nodes
2344        for (var a = 0; a < nodes.length; a++) {
2345          for (var b = a + 1; b < nodes.length; b++) {
2346            var dd = dist(nodes[a].x, nodes[a].y, nodes[b].x, nodes[b].y);
2347            if (dd < cDist && lineIdx < linePool.length) {
2348              var opa = (1 - dd / cDist) * self.opts.opacity * 0.5;
2349              linePool[lineIdx].setAttribute('x1', nodes[a].x);
2350              linePool[lineIdx].setAttribute('y1', nodes[a].y);
2351              linePool[lineIdx].setAttribute('x2', nodes[b].x);
2352              linePool[lineIdx].setAttribute('y2', nodes[b].y);
2353              linePool[lineIdx].setAttribute('opacity', opa);
2354              linePool[lineIdx].setAttribute('stroke', self.opts.c
2354olor1);
2355              lineIdx++;
2356            }
2357          }
2358        }
2359
2360        for (var k = lineIdx; k < linePool.length; k++) {
2361          linePool[k].setAttribute('opacity', '0');
2362        }
2363        self._addRaf(requestAnimationFrame(update));
2364      };
2365      this._addRaf(requestAnimationFrame(update));
2366    }
2367
2368    // 38. Ripple ------------------------------------------------------------
2369    _initRipple() {
2370      var self = this;
2371      var w = this._w(), h = this._h();
2372      var cols = Math.ceil(Math.sqrt(this.opts.count * (w / h)));
2373      var rows = Math.ceil(this.opts.count / cols);
2374      var spacingX = w / (cols + 1);
2375      var spacingY = h / (rows + 1);
2376      var dots = [];
2377
2378      for (var r = 0; r < rows; r++) {
2379        for (var c = 0; c < cols; c++) {
2380          (function(row, col) {
2381            var sz = randomRange(3, 5) * self.opts.size;
2382            var x = spacingX * (col + 1);
2383            var y = spacingY * (row + 1);
2384            var color = randomColorFromArray(self.colors);
2385            var el = createEl('div', {
2386              position: 'absolute',
2387              width: sz + 'px',
2388              height: sz + 'px',
2389              backgroundColor: color,
2390              borderRadius: '50%',
2391              left: x + 'px',
2392              top: y + 'px',
2393              opacity: String(self.opts.opacity)
2394            }, self.container);
2395            self._applyGlow(el, color);
2396            dots.push({ el: el, baseX: x, baseY: y });
2397          })(r, c);
2398        }
2399      }
2400
2401      var update = function() {
2402        if (self._destroyed) return;
2403        for (var i = 0; i < dots.length; i++) {
2404          var d = dots[i];
2405          if (self._mouseInside) {
2406            var dx = d.baseX - self._mouseX;
2407            var dy = d.baseY - self._mouseY;
2408            var distance = Math.sqrt(dx * dx + dy * dy);
2409            if (distance < self.opts.forceRadius * 2) {
2410              var force = (1 - distance / (self.opts.forceRadius * 2)) * self.opts.forceStrength * 30;
2411              var ax = distance > 0 ? (dx / distance) * force : 0;
2412              var ay = distance > 0 ? (dy / distance) * force : 0;
2413              gsap.to(d.el, { x: ax, y: ay, duration: 0.3, ease: 'power2.out' });
2414            } else {
2415              gsap.to(d.el, { x: 0, y: 0, duration: 0.6, ease: 'power2.out' });
2416            }
2417          } else {
2418            gsap.to(d.el, { x: 0, y: 0, duration: 0.6, ease: 'power2.out' });
2419          }
2420        }
2421        self._addRaf(requestAnimationFrame(update));
2422      };
2423      // Throttle to ~20fps for performance
2424      var throttledUpdate = function() {
2425        if (self._destroyed) return;
2426        update();
2427        setTimeout(function() {
2428          self._addRaf(requestAnimationFrame(throttledUpdate));
2429        }, 50);
2430      };
2431      this._addRaf(requestAnimationFrame(throttledUpdate));
2432    }
2433
2434    // 39. Orbit Cursor ------------------------------------------------------
2435    _initOrbitCursor() {
2436      var self = this;
2437      var w = this._w(), h = this._h();
2438      var orbiters = [];
2439      var ringCount = this.opts.count;
2440
2441      for (var i = 0; i < ringCount; i++) {
2442        (function(idx) {
2443          var sz = randomRange(3, 7) * self.opts.size;
2444          var color = lerpColor(self.opts.color1, self.opts.color2, idx / ringCount);
2445          var el = createEl('div', {
2446            position: 'absolute',
2447            width: sz + 'px',
2448            height: sz + 'px',
2449            backgroundColor: color,
2450            borderRadius: '50%',
2451            left: w / 2 + 'px',
2452            top: h / 2 + 'px',
2453            opacity: String(self.opts.opacity),
2454            pointerEvents: 'none'
2455          }, self.container);
2456          self._applyGlow(el, color);
2457          var radius = randomRange(20, 80) * self.opts.size;
2458          var speed = randomRange(1, 3) * self.opts.speed;
2459          var phase = (idx / ringCount) * Math.PI * 2;
2460          orbiters.push({ el: el, radius: radius, speed: speed, phase: phase, x: w / 2, y: h / 2 });
2461        })(i);
2462      }
2463
2464      var startTime = Date.now();
2465      var update = function() {
2466        if (self._destroyed) return;
2467        var t = (Date.now() - startTime) / 1000;
2468        var targetX = self._mouseInside ? self._mouseX : w / 2;
2469        var targetY = self._mouseInside ? self._mouseY : h / 2;
2470        for (var i = 0; i < orbiters.length; i++) {
2471          var o = orbiters[i];
2472          o.x += (targetX - o.x) * 0.08;
2473          o.y += (targetY - o.y) * 0.08;
2474          var angle = o.phase + t * o.speed;
2475          var px = o.x + Math.cos(angle) * o.radius;
2476          var py = o.y + Math.sin(angle) * o.radius;
2477          gsap.set(o.el, { left: px + 'px', top: py + 'px' });
2478        }
2479        self._addRaf(requestAnimationFrame(update));
2480      };
2481      this._addRaf(requestAnimationFrame(update));
2482    }
2483
2484    // 40. Color Wave --------------------------------------------------------
2485    _initColorWave() {
2486      var self = this;
2487      var w = this._w(), h = this._h();
2488      var cols = Math.ceil(Math.sqrt(this.opts.count * (w / h)));
2489      var rows = Math.ceil(this.opts.count / cols);
2490      var spacingX = w / (cols + 1);
2491      var spacingY = h / (rows + 1);
2492      var dots = [];
2493
2494      for (var r = 0; r < rows; r++) {
2495        for (var c = 0; c < cols; c++) {
2496          (function(row, col) {
2497            var sz = randomRange(4, 7) * self.opts.size;
2498            var x = spacingX * (col + 1);
2499            var y = spacingY * (row + 1);
2500            var el = createEl('div', {
2501              position: 'absolute',
2502              width: sz + 'px',
2503              height: sz + 'px',
2504              backgroundColor: self.opts.color1,
2505              borderRadius: '50%',
2506              left: x + 'px',
2507              top: y + 'px',
2508              opacity: String(self.opts.opacity)
2509            }, self.container);
2510            dots.push({ el: el, x: x, y: y });
2511          })(r, c);
2512        }
2513      }
2514
2515      var update = function() {
2516        if (self._destroyed) return;
2517        for (var i = 0; i < dots.length; i++) {
2518          var d = dots[i];
2519          if (self._mouseInside) {
2520            var distance = dist(d.x, d.y, self._mouseX, self._mouseY);
2521            var maxDist = self.opts.forceRadius * 3;
2522            var t = clamp(1 - distance / maxDist, 0, 1);
2523            var color = lerpColor(self.opts.color1, self.opts.color2, t);
2524            var scale = 1 + t * 0.5;
2525            d.el.style.backgroundColor = color;
2526            gsap.to(d.el, { scale: scale, duration: 0.3, ease: 'power2.out' });
2527            if (self.opts.glow && t > 0.3) {
2528              d.el.style.boxShadow = '0 0 ' + (t * 10 * self.opts.glowIntensity) + 'px ' + color;
2529            } else {
2530              d.el.style.boxShadow = 'none';
2531            }
2532          } else {
2533            d.el.style.backgroundColor = self.opts.color1;
2534            d.el.style.boxShadow = 'none';
2535            gsap.to(d.el, { scale: 1, duration: 0.5, ease: 'power2.out' });
2536          }
2537        }
2538        self._addRaf(requestAnimationFrame(update));
2539      };
2540      // Throttled
2541      var throttled = function() {
2542        if (self._destroyed) return;
2543        update();
2544        setTimeout(function() {
2545          self._addRaf(requestAnimationFrame(throttled));
2546        }, 50);
2547      };
2548      this._addRaf(requestAnimationFrame(throttled));
2549    }
2550
2551    // =========================================================================
2552    //  DESTROY
2553    // =========================================================================
2554    destroy() {
2555      this._destroyed = true;
2556
2557      // Kill intervals
2558      for (var i = 0; i < this._intervals.length; i++) {
2559        clearInterval(this._intervals[i]);
2560      }
2561      this._intervals = [];
2562
2563      // Cancel rAFs
2564      for (var j = 0; j < this._rafs.length; j++) {
2565        cancelAnimationFrame(this._rafs[j]);
2566      }
2567      this._rafs = [];
2568
2569      // Kill GSAP tweens
2570      for (var k = 0; k < this._tweens.length; k++) {
2571        if (this._tweens[k] && this._tweens[k].kill) this._tweens[k].kill();
2572      }
2573      this._tweens = [];
2574
2575      // Kill timelines
2576      for (var l = 0; l < this._timelines.length; l++) {
2577        if (this._timelines[l] && this._timelines[l].kill) this._timelines[l].kill();
2578      }
2579      this._timelines = [];
2580
2581      // Kill ScrollTrigger
2582      if (this._scrollTrigger) {
2583        this._scrollTrigger.kill();
2584        this._scrollTrigger = null;
2585      }
2586
2587      // Kill IntersectionObserver fallback
2588      if (this._observer) {
2589        this._observer.disconnect();
2590        this._observer = null;
2591      }
2592
2593      // Remove listeners
2594      for (var m = 0; m < this._listeners.length; m++) {
2595        var entry = this._listeners[m];
2596        entry[0].removeEventListener(entry[1], entry[2]);
2597      }
2598      this._listeners = [];
2599
2600      // Remove container
2601      if (this.container && this.container.parentNode) {
2602        // Kill all gsap animations on children first
2603        gsap.killTweensOf(this.container.querySelectorAll('*'));
2604        this.container.parentNode.removeChild(this.container);
2605      }
2606      this.container = null;
2607    }
2608  }
2609
2610  // ---------------------------------------------------------------------------
2611  // Auto-init on DOMContentLoaded
2612  // ---------------------------------------------------------------------------
2613  function autoInit() {
2614    var elements = document.querySelectorAll('[data-particle-type]');
2615    for (var i = 0; i < elements.length; i++) {
2616      if (!elements[i]._dnxteParticleEngine) {
2617        elements[i]._dnxteParticleEngine = new DnxteParticleEngine(elements[i]);
2618      }
2619    }
2620  }
2621
2622  if (document.readyState === 'loading') {
2623    document.addEventListener('DOMContentLoaded', autoInit);
2624  } else {
2625    autoInit();
2626  }
2627
2628  // Expose globally
2629  global.DnxteParticleEngine = DnxteParticleEngine;
2630
2631})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.