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.