@tsparticles/all 4.2.1 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  (function(g){g.__tsParticlesInternals=g.__tsParticlesInternals||{};g.__tsParticlesInternals.bundles=g.__tsParticlesInternals.bundles||{};g.__tsParticlesInternals.effects=g.__tsParticlesInternals.effects||{};g.__tsParticlesInternals.engine=g.__tsParticlesInternals.engine||{};g.__tsParticlesInternals.interactions=g.__tsParticlesInternals.interactions||{};g.__tsParticlesInternals.palettes=g.__tsParticlesInternals.palettes||{};g.__tsParticlesInternals.paths=g.__tsParticlesInternals.paths||{};g.__tsParticlesInternals.plugins=g.__tsParticlesInternals.plugins||{};g.__tsParticlesInternals.plugins=g.__tsParticlesInternals.plugins||{};g.__tsParticlesInternals.plugins.emittersShapes=g.__tsParticlesInternals.plugins.emittersShapes||{};g.__tsParticlesInternals.presets=g.__tsParticlesInternals.presets||{};g.__tsParticlesInternals.shapes=g.__tsParticlesInternals.shapes||{};g.__tsParticlesInternals.updaters=g.__tsParticlesInternals.updaters||{};g.__tsParticlesInternals.utils=g.__tsParticlesInternals.utils||{};g.__tsParticlesInternals.canvas=g.__tsParticlesInternals.canvas||{};g.__tsParticlesInternals.canvas=g.__tsParticlesInternals.canvas||{};g.__tsParticlesInternals.canvas.utils=g.__tsParticlesInternals.canvas.utils||{};g.__tsParticlesInternals.path=g.__tsParticlesInternals.path||{};g.__tsParticlesInternals.path=g.__tsParticlesInternals.path||{};g.__tsParticlesInternals.path.utils=g.__tsParticlesInternals.path.utils||{};var __tsProxyFactory=typeof Proxy!=="undefined"?function(obj){return new Proxy(obj,{get:function(target,key){if(!(key in target)){target[key]={};}return target[key];}});}:function(obj){return obj;};g.__tsParticlesInternals.bundles=__tsProxyFactory(g.__tsParticlesInternals.bundles);g.__tsParticlesInternals.effects=__tsProxyFactory(g.__tsParticlesInternals.effects);g.__tsParticlesInternals.interactions=__tsProxyFactory(g.__tsParticlesInternals.interactions);g.__tsParticlesInternals.palettes=__tsProxyFactory(g.__tsParticlesInternals.palettes);g.__tsParticlesInternals.paths=__tsProxyFactory(g.__tsParticlesInternals.paths);g.__tsParticlesInternals.plugins=__tsProxyFactory(g.__tsParticlesInternals.plugins);g.__tsParticlesInternals.plugins.emittersShapes=__tsProxyFactory(g.__tsParticlesInternals.plugins.emittersShapes);g.__tsParticlesInternals.presets=__tsProxyFactory(g.__tsParticlesInternals.presets);g.__tsParticlesInternals.shapes=__tsProxyFactory(g.__tsParticlesInternals.shapes);g.__tsParticlesInternals.updaters=__tsProxyFactory(g.__tsParticlesInternals.updaters);g.__tsParticlesInternals.utils=__tsProxyFactory(g.__tsParticlesInternals.utils);g.__tsParticlesInternals.canvas=__tsProxyFactory(g.__tsParticlesInternals.canvas);g.__tsParticlesInternals.path=__tsProxyFactory(g.__tsParticlesInternals.path);g.tsparticlesInternalExports=g.tsparticlesInternalExports||{};})(typeof globalThis!=="undefined"?globalThis:typeof window!=="undefined"?window:this);
2
- /* tsParticles v4.2.1 */
2
+ /* tsParticles v4.3.0 */
3
3
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
5
5
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -398,29 +398,27 @@
398
398
  }
399
399
  }
400
400
  else if (!isObject(destination) || Array.isArray(destination)) {
401
- destination = {};
401
+ destination = Object.create(null);
402
402
  }
403
- const sourceKeys = Object.keys(source), dangerousKeys = new Set(["__proto__", "constructor", "prototype"]), hasNested = sourceKeys.some(k => {
403
+ const sourceKeys = Object.keys(source), hasNested = sourceKeys.some(k => {
404
404
  const v = source[k];
405
405
  return isObject(v) || Array.isArray(v);
406
406
  });
407
407
  if (!hasNested) {
408
408
  const sourceDict = source, destDict = destination;
409
409
  for (const key of sourceKeys) {
410
- if (dangerousKeys.has(key)) {
410
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
411
411
  continue;
412
412
  }
413
- if (key in sourceDict) {
414
- const v = sourceDict[key];
415
- if (v !== undefined) {
416
- destDict[key] = v;
417
- }
413
+ const v = sourceDict[key];
414
+ if (v !== undefined) {
415
+ destDict[key] = v;
418
416
  }
419
417
  }
420
418
  continue;
421
419
  }
422
420
  for (const key of sourceKeys) {
423
- if (dangerousKeys.has(key)) {
421
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
424
422
  continue;
425
423
  }
426
424
  const sourceDict = source, destDict = destination, value = sourceDict[key];
@@ -1112,6 +1110,18 @@
1112
1110
  AnimationStatus["decreasing"] = "decreasing";
1113
1111
  })(exports.AnimationStatus || (exports.AnimationStatus = {}));
1114
1112
 
1113
+ exports.DrawLayer = void 0;
1114
+ (function (DrawLayer) {
1115
+ DrawLayer[DrawLayer["BackgroundElement"] = 0] = "BackgroundElement";
1116
+ DrawLayer[DrawLayer["BackgroundDraw"] = 1] = "BackgroundDraw";
1117
+ DrawLayer[DrawLayer["BackgroundMask"] = 2] = "BackgroundMask";
1118
+ DrawLayer[DrawLayer["CanvasSetup"] = 3] = "CanvasSetup";
1119
+ DrawLayer[DrawLayer["PluginContent"] = 4] = "PluginContent";
1120
+ DrawLayer[DrawLayer["Particles"] = 5] = "Particles";
1121
+ DrawLayer[DrawLayer["CanvasCleanup"] = 6] = "CanvasCleanup";
1122
+ DrawLayer[DrawLayer["Foreground"] = 7] = "Foreground";
1123
+ })(exports.DrawLayer || (exports.DrawLayer = {}));
1124
+
1115
1125
  class OptionLoader {
1116
1126
  load(data) {
1117
1127
  if (isNull(data)) {
@@ -1270,6 +1280,8 @@
1270
1280
 
1271
1281
  class Background extends OptionLoader {
1272
1282
  color;
1283
+ draw;
1284
+ element;
1273
1285
  image = "";
1274
1286
  opacity = 1;
1275
1287
  position = "";
@@ -1284,6 +1296,8 @@
1284
1296
  if (data.color !== undefined) {
1285
1297
  this.color = OptionsColor.create(this.color, data.color);
1286
1298
  }
1299
+ loadProperty(this, "element", data.element);
1300
+ loadProperty(this, "draw", data.draw);
1287
1301
  loadProperty(this, "image", data.image);
1288
1302
  loadProperty(this, "position", data.position);
1289
1303
  loadProperty(this, "repeat", data.repeat);
@@ -1829,7 +1843,7 @@
1829
1843
  }
1830
1844
  }
1831
1845
 
1832
- const styleCache = new Map(), maxStyleCacheSize = 2000, rgbFixedPrecision = 2, hslFixedPrecision = 2, sdrReferenceWhiteNits = 203;
1846
+ const styleCache = new Map(), maxStyleCacheSize = 2000, rgbFixedPrecision = 2, hslFixedPrecision = 2, hdrRgbFixedPrecision = 4, hdrHslFixedPrecision = 4, sdrReferenceWhiteNits = 203, hdrAnimationScale = sdrReferenceWhiteNits / maxNits;
1833
1847
  function getCachedStyle(key, generator) {
1834
1848
  let cached = styleCache.get(key);
1835
1849
  if (!cached) {
@@ -1972,6 +1986,17 @@
1972
1986
  : lNormalized + sNormalized - lNormalized * sNormalized, temp2 = double * lNormalized - temp1, phaseThird = phaseNumerator / triple, red = Math.min(rgbMax, rgbMax * hslChannel(temp2, temp1, hNormalized + phaseThird)), green = Math.min(rgbMax, rgbMax * hslChannel(temp2, temp1, hNormalized)), blue = Math.min(rgbMax, rgbMax * hslChannel(temp2, temp1, hNormalized - phaseThird));
1973
1987
  return { r: Math.round(red), g: Math.round(green), b: Math.round(blue) };
1974
1988
  }
1989
+ function hslToRgbFloat(hsl) {
1990
+ const h = ((hsl.h % hMax) + hMax) % hMax, s = Math.max(sMin, Math.min(sMax, hsl.s)), l = Math.max(lMin, Math.min(lMax, hsl.l)), hNormalized = h / hMax, sNormalized = s / sMax, lNormalized = l / lMax;
1991
+ if (s === sMin) {
1992
+ const grayscaleValue = lNormalized * rgbMax;
1993
+ return { r: grayscaleValue, g: grayscaleValue, b: grayscaleValue };
1994
+ }
1995
+ const temp1 = lNormalized < half$2
1996
+ ? lNormalized * (sNormalizedOffset + sNormalized)
1997
+ : lNormalized + sNormalized - lNormalized * sNormalized, temp2 = double * lNormalized - temp1, phaseThird = phaseNumerator / triple, red = Math.min(rgbMax, rgbMax * hslChannel(temp2, temp1, hNormalized + phaseThird)), green = Math.min(rgbMax, rgbMax * hslChannel(temp2, temp1, hNormalized)), blue = Math.min(rgbMax, rgbMax * hslChannel(temp2, temp1, hNormalized - phaseThird));
1998
+ return { r: red, g: green, b: blue };
1999
+ }
1975
2000
  function hslaToRgba(hsla) {
1976
2001
  const rgbResult = hslToRgb(hsla);
1977
2002
  return {
@@ -1981,8 +2006,16 @@
1981
2006
  r: rgbResult.r,
1982
2007
  };
1983
2008
  }
1984
- function getRandomRgbColor(min) {
1985
- const fixedMin = min ?? defaultRgbMin, fixedMax = rgbMax + identity$3, getRgbInRangeValue = () => Math.floor(getRandomInRange(fixedMin, fixedMax));
2009
+ function getRandomRgbColor(min, hdr) {
2010
+ const fixedMin = min ?? defaultRgbMin;
2011
+ if (hdr) {
2012
+ return {
2013
+ r: getRandomInRange(fixedMin, rgbMax),
2014
+ g: getRandomInRange(fixedMin, rgbMax),
2015
+ b: getRandomInRange(fixedMin, rgbMax),
2016
+ };
2017
+ }
2018
+ const fixedMax = rgbMax + identity$3, getRgbInRangeValue = () => Math.floor(getRandomInRange(fixedMin, fixedMax));
1986
2019
  return {
1987
2020
  b: getRgbInRangeValue(),
1988
2021
  g: getRgbInRangeValue(),
@@ -1990,7 +2023,7 @@
1990
2023
  };
1991
2024
  }
1992
2025
  function getStyleFromRgb(color, hdr, opacity) {
1993
- const op = opacity ?? defaultOpacity$3, key = `rgb-${color.r.toFixed(rgbFixedPrecision)}-${color.g.toFixed(rgbFixedPrecision)}-${color.b.toFixed(rgbFixedPrecision)}-${hdr ? "hdr" : "sdr"}-${op.toString()}`;
2026
+ const rgbPrecision = hdr ? hdrRgbFixedPrecision : rgbFixedPrecision, op = opacity ?? defaultOpacity$3, key = `rgb-${color.r.toFixed(rgbPrecision)}-${color.g.toFixed(rgbPrecision)}-${color.b.toFixed(rgbPrecision)}-${hdr ? "hdr" : "sdr"}-${op.toString()}`;
1994
2027
  return getCachedStyle(key, () => (hdr ? getHdrStyleFromRgb(color, opacity) : getSdrStyleFromRgb(color, opacity)));
1995
2028
  }
1996
2029
  function getHdrStyleFromRgb(color, opacity, peakNits = maxNits) {
@@ -2001,9 +2034,9 @@
2001
2034
  return `rgba(${color.r.toString()}, ${color.g.toString()}, ${color.b.toString()}, ${(opacity ?? defaultOpacity$3).toString()})`;
2002
2035
  }
2003
2036
  function getStyleFromHsl(color, hdr, opacity) {
2004
- const op = opacity ?? defaultOpacity$3, key = `hsl-${color.h.toFixed(hslFixedPrecision)}-${color.s.toFixed(hslFixedPrecision)}-${color.l.toFixed(hslFixedPrecision)}-${hdr ? "hdr" : "sdr"}-${op.toString()}`;
2037
+ const hslPrecision = hdr ? hdrHslFixedPrecision : hslFixedPrecision, op = opacity ?? defaultOpacity$3, key = `hsl-${color.h.toFixed(hslPrecision)}-${color.s.toFixed(hslPrecision)}-${color.l.toFixed(hslPrecision)}-${hdr ? "hdr" : "sdr"}-${op.toString()}`;
2005
2038
  return getCachedStyle(key, () => hdr
2006
- ? getStyleFromRgb(hslToRgb(color), true, opacity)
2039
+ ? getStyleFromRgb(hslToRgbFloat(color), true, opacity)
2007
2040
  : `hsla(${color.h.toString()}, ${color.s.toString()}%, ${color.l.toString()}%, ${op.toString()})`);
2008
2041
  }
2009
2042
  function colorMix(color1, color2, size1, size2) {
@@ -2123,7 +2156,7 @@
2123
2156
  colorValue.velocity = defaultVelocity;
2124
2157
  }
2125
2158
  }
2126
- function updateColorValue(data, decrease, delta) {
2159
+ function updateColorValue(data, decrease, delta, hdr) {
2127
2160
  const minLoops = 0, minDelay = 0, identity = 1, minVelocity = 0, minOffset = 0, velocityFactor = 3.6;
2128
2161
  if (!data.enable ||
2129
2162
  ((data.maxLoops ?? minLoops) > minLoops && (data.loops ?? minLoops) > (data.maxLoops ?? minLoops))) {
@@ -2136,7 +2169,7 @@
2136
2169
  if ((data.delayTime ?? minDelay) > minDelay && data.time < (data.delayTime ?? minDelay)) {
2137
2170
  return;
2138
2171
  }
2139
- const offset = data.offset ? randomInRangeValue(data.offset) : minOffset, velocity = (data.velocity ?? minVelocity) * delta.factor + offset * velocityFactor, decay = data.decay ?? identity, max = data.max, min = data.min;
2172
+ const offset = data.offset ? randomInRangeValue(data.offset) : minOffset, velocity = ((data.velocity ?? minVelocity) * delta.factor + offset * velocityFactor) * (hdr ? hdrAnimationScale : identity), decay = data.decay ?? identity, max = data.max, min = data.min;
2140
2173
  if (!decrease || data.status === exports.AnimationStatus.increasing) {
2141
2174
  data.value += velocity;
2142
2175
  if (data.value > max) {
@@ -2163,14 +2196,14 @@
2163
2196
  }
2164
2197
  data.value = clamp(data.value, min, max);
2165
2198
  }
2166
- function updateColor(color, delta) {
2199
+ function updateColor(color, delta, hdr) {
2167
2200
  if (!color) {
2168
2201
  return;
2169
2202
  }
2170
2203
  const { h, s, l } = color;
2171
- updateColorValue(h, false, delta);
2172
- updateColorValue(s, true, delta);
2173
- updateColorValue(l, true, delta);
2204
+ updateColorValue(h, false, delta, hdr);
2205
+ updateColorValue(s, true, delta, hdr);
2206
+ updateColorValue(l, true, delta, hdr);
2174
2207
  }
2175
2208
  function alterHsl(color, type, value) {
2176
2209
  return {
@@ -2218,6 +2251,8 @@
2218
2251
 
2219
2252
  class BackgroundMaskCover {
2220
2253
  color;
2254
+ draw;
2255
+ element;
2221
2256
  image;
2222
2257
  opacity = 1;
2223
2258
  load(data) {
@@ -2227,6 +2262,8 @@
2227
2262
  if (data.color !== undefined) {
2228
2263
  this.color = OptionsColor.create(this.color, data.color);
2229
2264
  }
2265
+ loadProperty(this, "draw", data.draw);
2266
+ loadProperty(this, "element", data.element);
2230
2267
  loadProperty(this, "image", data.image);
2231
2268
  loadProperty(this, "opacity", data.opacity);
2232
2269
  }
@@ -2243,7 +2280,12 @@
2243
2280
  loadProperty(this, "composite", data.composite);
2244
2281
  if (data.cover !== undefined) {
2245
2282
  const cover = data.cover, color = (isString(data.cover) ? { color: data.cover } : data.cover);
2246
- this.cover.load(cover.color !== undefined || cover.image !== undefined ? cover : { color: color });
2283
+ this.cover.load(cover.color !== undefined ||
2284
+ cover.draw !== undefined ||
2285
+ cover.element !== undefined ||
2286
+ cover.image !== undefined
2287
+ ? cover
2288
+ : { color: color });
2247
2289
  }
2248
2290
  loadProperty(this, "enable", data.enable);
2249
2291
  }
@@ -11108,8 +11150,8 @@
11108
11150
  if (!this.isEnabled(particle)) {
11109
11151
  return;
11110
11152
  }
11111
- updateColor(particle.fillColor, delta);
11112
- updateColor(particle.strokeColor, delta);
11153
+ updateColor(particle.fillColor, delta, this.#container.hdr);
11154
+ updateColor(particle.strokeColor, delta, this.#container.hdr);
11113
11155
  }
11114
11156
  }
11115
11157
 
@@ -11862,6 +11904,19 @@
11862
11904
  }
11863
11905
  }
11864
11906
 
11907
+ class BubbleModifier {
11908
+ div;
11909
+ enabled = false;
11910
+ fillColor;
11911
+ finalColor;
11912
+ id = "bubble";
11913
+ inRange = false;
11914
+ opacity;
11915
+ priority = 100;
11916
+ radius;
11917
+ strokeColor;
11918
+ }
11919
+
11865
11920
  var ProcessBubbleType;
11866
11921
  (function (ProcessBubbleType) {
11867
11922
  ProcessBubbleType["color"] = "color";
@@ -11885,6 +11940,7 @@
11885
11940
  class Bubbler extends ExternalInteractorBase {
11886
11941
  handleClickMode;
11887
11942
  #maxDistance;
11943
+ #modifiers = new WeakMap();
11888
11944
  #pluginManager;
11889
11945
  constructor(pluginManager, container) {
11890
11946
  super(container);
@@ -11903,13 +11959,21 @@
11903
11959
  return this.#maxDistance;
11904
11960
  }
11905
11961
  clear(particle, _delta, force) {
11906
- if (particle.bubble.inRange && !force) {
11962
+ const mod = this.#modifiers.get(particle);
11963
+ if (mod?.inRange && !force) {
11907
11964
  return;
11908
11965
  }
11909
- delete particle.bubble.div;
11910
- delete particle.bubble.opacity;
11911
- delete particle.bubble.radius;
11912
- delete particle.bubble.color;
11966
+ particle.removeModifier(bubbleMode);
11967
+ this.#modifiers.delete(particle);
11968
+ }
11969
+ getOrCreateModifier(particle) {
11970
+ let mod = this.#modifiers.get(particle);
11971
+ if (!mod) {
11972
+ mod = new BubbleModifier();
11973
+ this.#modifiers.set(particle, mod);
11974
+ particle.addModifier(mod);
11975
+ }
11976
+ return mod;
11913
11977
  }
11914
11978
  init() {
11915
11979
  const container = this.container, bubble = container.actualOptions.interactivity?.modes.bubble;
@@ -11955,7 +12019,11 @@
11955
12019
  loadOptionProperty(options, "bubble", Bubble, ...sources);
11956
12020
  }
11957
12021
  reset(_interactivityData, particle) {
11958
- particle.bubble.inRange = false;
12022
+ const mod = this.#modifiers.get(particle);
12023
+ if (mod) {
12024
+ mod.enabled = false;
12025
+ mod.inRange = false;
12026
+ }
11959
12027
  }
11960
12028
  #clickBubble(interactivityData) {
11961
12029
  const container = this.container, options = container.actualOptions, mouseClickPos = interactivityData.mouse.clickPosition, bubbleOptions = options.interactivity?.modes.bubble;
@@ -11972,7 +12040,9 @@
11972
12040
  if (!bubble.clicking) {
11973
12041
  continue;
11974
12042
  }
11975
- particle.bubble.inRange = !bubble.durationEnd;
12043
+ const mod = this.getOrCreateModifier(particle);
12044
+ mod.enabled = !bubble.durationEnd;
12045
+ mod.inRange = !bubble.durationEnd;
11976
12046
  const pos = particle.getPosition(), distMouse = getDistance(pos, mouseClickPos), timeSpent = (performance.now() - (interactivityData.mouse.clickTime ?? defaultClickTime)) / millisecondsToSeconds$1;
11977
12047
  if (timeSpent > bubbleOptions.duration) {
11978
12048
  bubble.durationEnd = true;
@@ -11984,7 +12054,7 @@
11984
12054
  const sizeData = {
11985
12055
  bubbleObj: {
11986
12056
  optValue: container.retina.bubbleModeSize,
11987
- value: particle.bubble.radius,
12057
+ value: mod.radius,
11988
12058
  },
11989
12059
  particlesObj: {
11990
12060
  optValue: particle.size.max,
@@ -11996,7 +12066,7 @@
11996
12066
  const opacityData = {
11997
12067
  bubbleObj: {
11998
12068
  optValue: bubbleOptions.opacity,
11999
- value: particle.bubble.opacity,
12069
+ value: mod.opacity,
12000
12070
  },
12001
12071
  particlesObj: {
12002
12072
  optValue: particle.opacity?.max ?? defaultOpacity$1,
@@ -12009,7 +12079,8 @@
12009
12079
  this.#hoverBubbleColor(particle, distMouse);
12010
12080
  }
12011
12081
  else {
12012
- delete particle.bubble.color;
12082
+ mod.fillColor = undefined;
12083
+ mod.strokeColor = undefined;
12013
12084
  }
12014
12085
  }
12015
12086
  }
@@ -12020,7 +12091,9 @@
12020
12091
  }
12021
12092
  const query = container.particles.grid.queryCircle(mousePos, distance, p => this.isEnabled(interactivityData, p));
12022
12093
  for (const particle of query) {
12023
- particle.bubble.inRange = true;
12094
+ const mod = this.getOrCreateModifier(particle);
12095
+ mod.enabled = true;
12096
+ mod.inRange = true;
12024
12097
  const pos = particle.getPosition(), pointDistance = getDistance(pos, mousePos), ratio = ratioOffset - pointDistance / distance;
12025
12098
  if (pointDistance <= distance) {
12026
12099
  if (ratio >= minRatio && interactivityData.status === mouseMoveEvent) {
@@ -12038,30 +12111,38 @@
12038
12111
  }
12039
12112
  }
12040
12113
  #hoverBubbleColor(particle, ratio, divBubble) {
12041
- const options = this.container.actualOptions, bubbleOptions = divBubble ?? options.interactivity?.modes.bubble;
12114
+ const options = this.container.actualOptions, bubbleOptions = divBubble ?? options.interactivity?.modes.bubble, mod = this.getOrCreateModifier(particle);
12042
12115
  if (!bubbleOptions) {
12043
12116
  return;
12044
12117
  }
12045
- if (!particle.bubble.finalColor) {
12118
+ if (!mod.finalColor) {
12046
12119
  const modeColor = bubbleOptions.color;
12047
12120
  if (!modeColor) {
12048
12121
  return;
12049
12122
  }
12050
12123
  const bubbleColor = itemFromSingleOrMultiple(modeColor);
12051
- particle.bubble.finalColor = rangeColorToHsl(this.#pluginManager, bubbleColor);
12124
+ mod.finalColor = rangeColorToHsl(this.#pluginManager, bubbleColor);
12052
12125
  }
12053
- if (!particle.bubble.finalColor) {
12126
+ if (!mod.finalColor) {
12054
12127
  return;
12055
12128
  }
12056
12129
  if (bubbleOptions.mix) {
12057
- particle.bubble.color = undefined;
12130
+ mod.fillColor = undefined;
12131
+ mod.strokeColor = undefined;
12058
12132
  const pColor = particle.getFillColor();
12059
- particle.bubble.color = pColor
12060
- ? rgbToHsl(colorMix(pColor, particle.bubble.finalColor, ratioOffset - ratio, ratio))
12061
- : particle.bubble.finalColor;
12133
+ if (pColor) {
12134
+ const mixedColor = rgbToHsl(colorMix(pColor, mod.finalColor, ratioOffset - ratio, ratio));
12135
+ mod.fillColor = mixedColor;
12136
+ mod.strokeColor = mixedColor;
12137
+ }
12138
+ else {
12139
+ mod.fillColor = mod.finalColor;
12140
+ mod.strokeColor = mod.finalColor;
12141
+ }
12062
12142
  }
12063
12143
  else {
12064
- particle.bubble.color = particle.bubble.finalColor;
12144
+ mod.fillColor = mod.finalColor;
12145
+ mod.strokeColor = mod.finalColor;
12065
12146
  }
12066
12147
  }
12067
12148
  #hoverBubbleOpacity(particle, ratio, divBubble) {
@@ -12071,7 +12152,8 @@
12071
12152
  }
12072
12153
  const pOpacity = particle.opacity?.value ?? defaultOpacity$1, opacity = calculateBubbleValue(pOpacity, modeOpacity, particle.opacity?.max ?? defaultOpacity$1, ratio);
12073
12154
  if (opacity !== undefined) {
12074
- particle.bubble.opacity = opacity;
12155
+ const mod = this.getOrCreateModifier(particle);
12156
+ mod.opacity = opacity;
12075
12157
  }
12076
12158
  }
12077
12159
  #hoverBubbleSize(particle, ratio, divBubble) {
@@ -12081,7 +12163,8 @@
12081
12163
  }
12082
12164
  const pSize = particle.size.value, size = calculateBubbleValue(pSize, modeSize, particle.size.max, ratio);
12083
12165
  if (size !== undefined) {
12084
- particle.bubble.radius = size;
12166
+ const mod = this.getOrCreateModifier(particle);
12167
+ mod.radius = size;
12085
12168
  }
12086
12169
  }
12087
12170
  #process(particle, distMouse, timeSpent, data) {
@@ -12094,13 +12177,14 @@
12094
12177
  return;
12095
12178
  }
12096
12179
  container.bubble ??= {};
12180
+ const mod = this.getOrCreateModifier(particle);
12097
12181
  if (container.bubble.durationEnd) {
12098
12182
  if (pObjBubble) {
12099
12183
  if (type === ProcessBubbleType.size) {
12100
- delete particle.bubble.radius;
12184
+ mod.radius = undefined;
12101
12185
  }
12102
12186
  if (type === ProcessBubbleType.opacity) {
12103
- delete particle.bubble.opacity;
12187
+ mod.opacity = undefined;
12104
12188
  }
12105
12189
  }
12106
12190
  }
@@ -12110,19 +12194,19 @@
12110
12194
  if (obj !== bubbleParam) {
12111
12195
  const value = pObj - (timeSpent * (pObj - bubbleParam)) / bubbleDuration;
12112
12196
  if (type === ProcessBubbleType.size) {
12113
- particle.bubble.radius = value;
12197
+ mod.radius = value;
12114
12198
  }
12115
12199
  if (type === ProcessBubbleType.opacity) {
12116
- particle.bubble.opacity = value;
12200
+ mod.opacity = value;
12117
12201
  }
12118
12202
  }
12119
12203
  }
12120
12204
  else {
12121
12205
  if (type === ProcessBubbleType.size) {
12122
- delete particle.bubble.radius;
12206
+ mod.radius = undefined;
12123
12207
  }
12124
12208
  if (type === ProcessBubbleType.opacity) {
12125
- delete particle.bubble.opacity;
12209
+ mod.opacity = undefined;
12126
12210
  }
12127
12211
  }
12128
12212
  }
@@ -12143,11 +12227,13 @@
12143
12227
  if (!area.contains(particle.getPosition())) {
12144
12228
  continue;
12145
12229
  }
12146
- particle.bubble.inRange = true;
12230
+ const mod = this.getOrCreateModifier(particle);
12231
+ mod.enabled = true;
12232
+ mod.inRange = true;
12147
12233
  const divs = bubble.divs, divBubble = divMode(divs, elem);
12148
- if (!particle.bubble.div || particle.bubble.div !== elem) {
12234
+ if (!mod.div || mod.div !== elem) {
12149
12235
  this.clear(particle, delta, true);
12150
- particle.bubble.div = elem;
12236
+ mod.div = elem;
12151
12237
  }
12152
12238
  this.#hoverBubbleSize(particle, defaultRatio, divBubble);
12153
12239
  this.#hoverBubbleOpacity(particle, defaultRatio, divBubble);
@@ -13081,9 +13167,17 @@
13081
13167
  }
13082
13168
  }
13083
13169
 
13170
+ class SlowModifier {
13171
+ enabled = false;
13172
+ id = "slow";
13173
+ priority = 100;
13174
+ speedFactor = 1;
13175
+ }
13176
+
13084
13177
  const slowMode = "slow", minRadius$3 = 0;
13085
13178
  class Slower extends ExternalInteractorBase {
13086
13179
  #maxDistance;
13180
+ #modifiers = new WeakMap();
13087
13181
  constructor(container) {
13088
13182
  super(container);
13089
13183
  this.#maxDistance = 0;
@@ -13092,10 +13186,21 @@
13092
13186
  return this.#maxDistance;
13093
13187
  }
13094
13188
  clear(particle, _delta, force) {
13095
- if (particle.slow.inRange && !force) {
13189
+ const mod = this.#modifiers.get(particle);
13190
+ if (mod?.enabled && !force) {
13096
13191
  return;
13097
13192
  }
13098
- particle.slow.factor = 1;
13193
+ particle.removeModifier(slowMode);
13194
+ this.#modifiers.delete(particle);
13195
+ }
13196
+ getOrCreateModifier(particle) {
13197
+ let mod = this.#modifiers.get(particle);
13198
+ if (!mod) {
13199
+ mod = new SlowModifier();
13200
+ this.#modifiers.set(particle, mod);
13201
+ particle.addModifier(mod);
13202
+ }
13203
+ return mod;
13099
13204
  }
13100
13205
  init() {
13101
13206
  const container = this.container, slow = container.actualOptions.interactivity?.modes.slow;
@@ -13115,17 +13220,21 @@
13115
13220
  loadOptionProperty(options, "slow", Slow, ...sources);
13116
13221
  }
13117
13222
  reset(interactivityData, particle) {
13118
- particle.slow.inRange = false;
13223
+ const mod = this.#modifiers.get(particle);
13224
+ if (mod) {
13225
+ mod.enabled = false;
13226
+ }
13119
13227
  const container = this.container, options = container.actualOptions, mousePos = interactivityData.mouse.position, radius = container.retina.slowModeRadius, slowOptions = options.interactivity?.modes.slow;
13120
13228
  if (!slowOptions || !radius || radius < minRadius$3 || !mousePos) {
13121
13229
  return;
13122
13230
  }
13123
- const particlePos = particle.getPosition(), dist = getDistance(mousePos, particlePos), proximityFactor = dist / radius, slowFactor = slowOptions.factor, { slow } = particle;
13231
+ const particlePos = particle.getPosition(), dist = getDistance(mousePos, particlePos), proximityFactor = dist / radius, slowFactor = slowOptions.factor;
13124
13232
  if (dist > radius) {
13125
13233
  return;
13126
13234
  }
13127
- slow.inRange = true;
13128
- slow.factor = proximityFactor / slowFactor;
13235
+ const activeMod = this.getOrCreateModifier(particle);
13236
+ activeMod.enabled = true;
13237
+ activeMod.speedFactor = proximityFactor / slowFactor;
13129
13238
  }
13130
13239
  }
13131
13240
 
@@ -13192,7 +13301,6 @@
13192
13301
  function replaceImageColor(image, imageData, color, particle, hdr = false) {
13193
13302
  const svgColoredData = replaceColorSvg(image, color, particle.opacity?.value ?? defaultOpacity, hdr), imageRes = {
13194
13303
  color,
13195
- gif: imageData.gif,
13196
13304
  data: {
13197
13305
  ...image,
13198
13306
  svgData: svgColoredData,
@@ -13227,932 +13335,827 @@
13227
13335
  });
13228
13336
  }
13229
13337
 
13230
- const InterlaceOffsets = [0, 4, 2, 1];
13231
- const InterlaceSteps = [8, 8, 4, 2];
13232
-
13233
- class ByteStream {
13234
- data;
13235
- pos;
13236
- constructor(bytes) {
13237
- this.pos = 0;
13238
- this.data = new Uint8ClampedArray(bytes);
13239
- }
13240
- getString(count) {
13241
- const slice = this.data.slice(this.pos, this.pos + count);
13242
- this.pos += slice.length;
13243
- return slice.reduce((acc, curr) => acc + String.fromCharCode(curr), "");
13244
- }
13245
- nextByte() {
13246
- return this.data[this.pos++];
13338
+ const sides$3 = 12;
13339
+ class ImageDrawer {
13340
+ #container;
13341
+ #engine;
13342
+ constructor(engine, container) {
13343
+ this.#engine = engine;
13344
+ this.#container = container;
13247
13345
  }
13248
- nextTwoBytes() {
13249
- const increment = 2, previous = 1, shift = 8;
13250
- this.pos += increment;
13251
- return this.data[this.pos - increment] + (this.data[this.pos - previous] << shift);
13346
+ draw(data) {
13347
+ const { context, radius, particle, opacity } = data, image = particle.image, element = image?.element;
13348
+ if (!image) {
13349
+ return;
13350
+ }
13351
+ context.globalAlpha = opacity;
13352
+ if (element) {
13353
+ const ratio = image.ratio, pos = {
13354
+ x: -radius,
13355
+ y: -radius,
13356
+ }, diameter = radius * double;
13357
+ context.drawImage(element, pos.x, pos.y, diameter, diameter / ratio);
13358
+ }
13359
+ context.globalAlpha = defaultAlpha$1;
13252
13360
  }
13253
- readSubBlocks() {
13254
- let blockString = "", size;
13255
- const minCount = 0, emptySize = 0;
13256
- do {
13257
- size = this.data[this.pos++];
13258
- for (let count = size; --count >= minCount; blockString += String.fromCharCode(this.data[this.pos++])) {
13259
- }
13260
- } while (size !== emptySize);
13261
- return blockString;
13361
+ getSidesCount() {
13362
+ return sides$3;
13262
13363
  }
13263
- readSubBlocksBin() {
13264
- let size = this.data[this.pos], len = 0;
13265
- const emptySize = 0, increment = 1;
13266
- for (let offset = 0; size !== emptySize; offset += size + increment, size = this.data[this.pos + offset]) {
13267
- len += size;
13364
+ async init(container) {
13365
+ const options = container.actualOptions;
13366
+ if (!options.preload || !this.#engine.loadImage) {
13367
+ return;
13268
13368
  }
13269
- const blockData = new Uint8Array(len);
13270
- size = this.data[this.pos++];
13271
- for (let i = 0; size !== emptySize; size = this.data[this.pos++]) {
13272
- for (let count = size; --count >= emptySize; blockData[i++] = this.data[this.pos++]) {
13273
- }
13369
+ const promises = [];
13370
+ for (const imageData of options.preload) {
13371
+ promises.push(this.#engine.loadImage(container, imageData));
13274
13372
  }
13275
- return blockData;
13373
+ await Promise.all(promises);
13276
13374
  }
13277
- skipSubBlocks() {
13278
- for (const increment = 1, noData = 0; this.data[this.pos] !== noData; this.pos += this.data[this.pos] + increment) {
13375
+ loadShape(particle) {
13376
+ const container = this.#container;
13377
+ if (!particle.shape || !shapeTypes.includes(particle.shape)) {
13378
+ return;
13279
13379
  }
13280
- this.pos++;
13281
- }
13282
- }
13283
-
13284
- var DisposalMethod;
13285
- (function (DisposalMethod) {
13286
- DisposalMethod[DisposalMethod["Replace"] = 0] = "Replace";
13287
- DisposalMethod[DisposalMethod["Combine"] = 1] = "Combine";
13288
- DisposalMethod[DisposalMethod["RestoreBackground"] = 2] = "RestoreBackground";
13289
- DisposalMethod[DisposalMethod["RestorePrevious"] = 3] = "RestorePrevious";
13290
- DisposalMethod[DisposalMethod["UndefinedA"] = 4] = "UndefinedA";
13291
- DisposalMethod[DisposalMethod["UndefinedB"] = 5] = "UndefinedB";
13292
- DisposalMethod[DisposalMethod["UndefinedC"] = 6] = "UndefinedC";
13293
- DisposalMethod[DisposalMethod["UndefinedD"] = 7] = "UndefinedD";
13294
- })(DisposalMethod || (DisposalMethod = {}));
13295
-
13296
- var GIFDataHeaders;
13297
- (function (GIFDataHeaders) {
13298
- GIFDataHeaders[GIFDataHeaders["Extension"] = 33] = "Extension";
13299
- GIFDataHeaders[GIFDataHeaders["ApplicationExtension"] = 255] = "ApplicationExtension";
13300
- GIFDataHeaders[GIFDataHeaders["GraphicsControlExtension"] = 249] = "GraphicsControlExtension";
13301
- GIFDataHeaders[GIFDataHeaders["PlainTextExtension"] = 1] = "PlainTextExtension";
13302
- GIFDataHeaders[GIFDataHeaders["CommentExtension"] = 254] = "CommentExtension";
13303
- GIFDataHeaders[GIFDataHeaders["Image"] = 44] = "Image";
13304
- GIFDataHeaders[GIFDataHeaders["EndOfFile"] = 59] = "EndOfFile";
13305
- })(GIFDataHeaders || (GIFDataHeaders = {}));
13306
-
13307
- const defaultFrame = 0, initialTime = 0, firstIndex$3 = 0, defaultLoopCount = 0;
13308
- function parseColorTable(byteStream, count) {
13309
- const colors = [];
13310
- for (let i = 0; i < count; i++) {
13311
- colors.push({
13312
- r: byteStream.data[byteStream.pos],
13313
- g: byteStream.data[byteStream.pos + 1],
13314
- b: byteStream.data[byteStream.pos + 2],
13380
+ const imageData = particle.shapeData;
13381
+ if (!imageData) {
13382
+ return;
13383
+ }
13384
+ const images = this.#engine.getImages?.(container), image = images?.find((t) => t.name === imageData.name || t.source === imageData.src);
13385
+ if (image) {
13386
+ return;
13387
+ }
13388
+ void this.#loadImageShape(container, imageData).then(() => {
13389
+ this.loadShape(particle);
13315
13390
  });
13316
- byteStream.pos += 3;
13317
13391
  }
13318
- return colors;
13319
- }
13320
- function parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex) {
13321
- switch (byteStream.nextByte()) {
13322
- case GIFDataHeaders.GraphicsControlExtension: {
13323
- const frame = gif.frames[getFrameIndex(false)];
13324
- byteStream.pos++;
13325
- const packedByte = byteStream.nextByte();
13326
- frame.GCreserved = (packedByte & 0xe0) >>> 5;
13327
- frame.disposalMethod = (packedByte & 0x1c) >>> 2;
13328
- frame.userInputDelayFlag = (packedByte & 2) === 2;
13329
- const transparencyFlag = (packedByte & 1) === 1;
13330
- frame.delayTime = byteStream.nextTwoBytes() * 0xa;
13331
- const transparencyIndex = byteStream.nextByte();
13332
- if (transparencyFlag) {
13333
- getTransparencyIndex(transparencyIndex);
13334
- }
13335
- byteStream.pos++;
13336
- break;
13392
+ particleInit(container, particle) {
13393
+ if (particle.shape !== "image" && particle.shape !== "images") {
13394
+ return;
13337
13395
  }
13338
- case GIFDataHeaders.ApplicationExtension: {
13339
- byteStream.pos++;
13340
- const applicationExtension = {
13341
- identifier: byteStream.getString(8),
13342
- authenticationCode: byteStream.getString(3),
13343
- data: byteStream.readSubBlocksBin(),
13344
- };
13345
- gif.applicationExtensions.push(applicationExtension);
13346
- break;
13396
+ const images = this.#engine.getImages?.(container), imageData = particle.shapeData;
13397
+ if (!imageData) {
13398
+ return;
13347
13399
  }
13348
- case GIFDataHeaders.CommentExtension: {
13349
- gif.comments.push([getFrameIndex(false), byteStream.readSubBlocks()]);
13350
- break;
13400
+ const color = particle.getFillColor(), image = images?.find((t) => t.name === imageData.name || t.source === imageData.src);
13401
+ if (!image) {
13402
+ return;
13351
13403
  }
13352
- case GIFDataHeaders.PlainTextExtension: {
13353
- if (gif.globalColorTable.length === 0) {
13354
- throw new EvalError("plain text extension without global color table");
13404
+ const replaceColor = imageData.replaceColor;
13405
+ if (image.loading) {
13406
+ setTimeout(() => {
13407
+ this.particleInit(container, particle);
13408
+ });
13409
+ return;
13410
+ }
13411
+ void (async () => {
13412
+ let imageRes;
13413
+ if (image.svgData && color) {
13414
+ imageRes = await replaceImageColor(image, imageData, color, particle, container.hdr);
13355
13415
  }
13356
- byteStream.pos++;
13357
- gif.frames[getFrameIndex(false)].plainTextData = {
13358
- left: byteStream.nextTwoBytes(),
13359
- top: byteStream.nextTwoBytes(),
13360
- width: byteStream.nextTwoBytes(),
13361
- height: byteStream.nextTwoBytes(),
13362
- charSize: {
13363
- width: byteStream.nextTwoBytes(),
13364
- height: byteStream.nextTwoBytes(),
13365
- },
13366
- foregroundColor: byteStream.nextByte(),
13367
- backgroundColor: byteStream.nextByte(),
13368
- text: byteStream.readSubBlocks(),
13416
+ else {
13417
+ imageRes = {
13418
+ color,
13419
+ data: image,
13420
+ element: image.element,
13421
+ loaded: true,
13422
+ ratio: imageData.width && imageData.height ? imageData.width / imageData.height : (image.ratio ?? defaultRatio$1),
13423
+ replaceColor: replaceColor,
13424
+ source: imageData.src,
13425
+ };
13426
+ }
13427
+ if (!imageRes.ratio) {
13428
+ imageRes.ratio = 1;
13429
+ }
13430
+ const close = imageData.close ?? particle.shapeClose, imageShape = {
13431
+ image: imageRes,
13432
+ close,
13369
13433
  };
13370
- break;
13371
- }
13372
- default:
13373
- byteStream.skipSubBlocks();
13374
- break;
13434
+ particle.image = imageShape.image;
13435
+ particle.shapeClose = imageShape.close;
13436
+ })();
13375
13437
  }
13438
+ #loadImageShape = async (container, imageShape) => {
13439
+ if (!this.#engine.loadImage) {
13440
+ throw new Error(`Image shape not initialized`);
13441
+ }
13442
+ await this.#engine.loadImage(container, {
13443
+ name: imageShape.name,
13444
+ replaceColor: imageShape.replaceColor,
13445
+ src: imageShape.src,
13446
+ });
13447
+ };
13376
13448
  }
13377
- function readBits(imageData, pos, len) {
13378
- const bytePos = pos >>> 3, bitPos = pos & 7;
13379
- return (((imageData[bytePos] + (imageData[bytePos + 1] << 8) + (imageData[bytePos + 2] << 16)) &
13380
- (((1 << len) - 1) << bitPos)) >>>
13381
- bitPos);
13382
- }
13383
- async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, canvasSettings, progressCallback) {
13384
- const frame = gif.frames[getFrameIndex(true)];
13385
- frame.left = byteStream.nextTwoBytes();
13386
- frame.top = byteStream.nextTwoBytes();
13387
- frame.width = byteStream.nextTwoBytes();
13388
- frame.height = byteStream.nextTwoBytes();
13389
- const packedByte = byteStream.nextByte(), localColorTableFlag = (packedByte & 0x80) === 0x80, interlacedFlag = (packedByte & 0x40) === 0x40;
13390
- frame.sortFlag = (packedByte & 0x20) === 0x20;
13391
- frame.reserved = (packedByte & 0x18) >>> 3;
13392
- const localColorCount = 1 << ((packedByte & 7) + 1);
13393
- if (localColorTableFlag) {
13394
- frame.localColorTable = parseColorTable(byteStream, localColorCount);
13395
- }
13396
- const getColor = (index) => {
13397
- const { r, g, b } = (localColorTableFlag ? frame.localColorTable : gif.globalColorTable)[index];
13398
- if (index !== getTransparencyIndex(null)) {
13399
- return { r, g, b, a: 255 };
13449
+
13450
+ class Preload {
13451
+ height;
13452
+ name;
13453
+ replaceColor;
13454
+ src = "";
13455
+ width;
13456
+ load(data) {
13457
+ if (isNull(data)) {
13458
+ return;
13400
13459
  }
13401
- return { r, g, b, a: avgAlpha ? Math.trunc((r + g + b) / 3) : 0 };
13402
- }, image = (() => {
13403
- try {
13404
- return new ImageData(frame.width, frame.height, canvasSettings);
13460
+ loadProperty(this, "height", data.height);
13461
+ loadProperty(this, "name", data.name);
13462
+ loadProperty(this, "replaceColor", data.replaceColor);
13463
+ loadProperty(this, "src", data.src);
13464
+ loadProperty(this, "width", data.width);
13465
+ }
13466
+ }
13467
+
13468
+ class ImagePreloaderPlugin {
13469
+ id = "image-preloader";
13470
+ #engine;
13471
+ constructor(engine) {
13472
+ this.#engine = engine;
13473
+ }
13474
+ async getPlugin(container) {
13475
+ const { ImagePreloaderInstance } = await Promise.resolve().then(function () { return ImagePreloaderInstance$1; });
13476
+ return new ImagePreloaderInstance(this.#engine, container);
13477
+ }
13478
+ loadOptions(_container, options, source) {
13479
+ if (!source?.preload) {
13480
+ return;
13405
13481
  }
13406
- catch (error) {
13407
- if (error instanceof DOMException && error.name === "IndexSizeError") {
13408
- return null;
13482
+ options.preload ??= [];
13483
+ const preloadOptions = options.preload;
13484
+ for (const item of source.preload) {
13485
+ const existing = preloadOptions.find(t => t.name === item.name || t.src === item.src);
13486
+ if (existing) {
13487
+ existing.load(item);
13409
13488
  }
13410
- throw error;
13411
- }
13412
- })();
13413
- if (image == null) {
13414
- throw new EvalError("GIF frame size is to large");
13415
- }
13416
- const minCodeSize = byteStream.nextByte(), imageData = byteStream.readSubBlocksBin(), clearCode = 1 << minCodeSize;
13417
- if (interlacedFlag) {
13418
- for (let code = 0, size = minCodeSize + 1, pos = 0, dic = [[0]], pass = 0; pass < 4; pass++) {
13419
- if (InterlaceOffsets[pass] < frame.height) {
13420
- let pixelPos = 0, lineIndex = 0, exit = false;
13421
- while (!exit) {
13422
- const last = code;
13423
- code = readBits(imageData, pos, size);
13424
- pos += size + 1;
13425
- if (code === clearCode) {
13426
- size = minCodeSize + 1;
13427
- dic.length = clearCode + 2;
13428
- for (let i = 0; i < dic.length; i++) {
13429
- dic[i] = i < clearCode ? [i] : [];
13430
- }
13431
- }
13432
- else {
13433
- if (code >= dic.length) {
13434
- dic.push(dic[last].concat(dic[last][0]));
13435
- }
13436
- else if (last !== clearCode) {
13437
- dic.push(dic[last].concat(dic[code][0]));
13438
- }
13439
- for (const item of dic[code]) {
13440
- const { r, g, b, a } = getColor(item);
13441
- image.data.set([r, g, b, a], InterlaceOffsets[pass] * frame.width +
13442
- InterlaceSteps[pass] * lineIndex +
13443
- (pixelPos % (frame.width * 4)));
13444
- pixelPos += 4;
13445
- }
13446
- if (dic.length === 1 << size && size < 0xc) {
13447
- size++;
13448
- }
13449
- }
13450
- if (pixelPos === frame.width * 4 * (lineIndex + 1)) {
13451
- lineIndex++;
13452
- if (InterlaceOffsets[pass] + InterlaceSteps[pass] * lineIndex >= frame.height) {
13453
- exit = true;
13454
- }
13455
- }
13456
- }
13489
+ else {
13490
+ const preload = new Preload();
13491
+ preload.load(item);
13492
+ preloadOptions.push(preload);
13457
13493
  }
13458
13494
  }
13459
- frame.image = image;
13460
- frame.bitmap = await createImageBitmap(image);
13461
13495
  }
13462
- else {
13463
- let code = 0, size = minCodeSize + 1, pos = 0, pixelPos = -4;
13464
- const dic = [[0]];
13465
- for (;;) {
13466
- const last = code;
13467
- code = readBits(imageData, pos, size);
13468
- pos += size;
13469
- if (code === clearCode) {
13470
- size = minCodeSize + 1;
13471
- dic.length = clearCode + 2;
13472
- for (let i = 0; i < dic.length; i++) {
13473
- dic[i] = i < clearCode ? [i] : [];
13474
- }
13496
+ needsPlugin() {
13497
+ return true;
13498
+ }
13499
+ }
13500
+
13501
+ const extLength = 3;
13502
+ function addLoadImageToEngine(engine) {
13503
+ engine.getImages ??= (container) => {
13504
+ engine.images ??= new Map();
13505
+ let images = engine.images.get(container);
13506
+ if (!images) {
13507
+ images = [];
13508
+ engine.images.set(container, images);
13509
+ }
13510
+ return images;
13511
+ };
13512
+ engine.loadImage ??= async (container, data) => {
13513
+ if (!engine.getImages) {
13514
+ throw new Error("No images collection found");
13515
+ }
13516
+ if (!data.name && !data.src) {
13517
+ throw new Error("No image source provided");
13518
+ }
13519
+ engine.images ??= new Map();
13520
+ const containerImages = engine.getImages(container);
13521
+ if (containerImages.some((t) => t.name === data.name || t.source === data.src)) {
13522
+ return;
13523
+ }
13524
+ try {
13525
+ const image = {
13526
+ name: data.name ?? data.src,
13527
+ source: data.src,
13528
+ type: data.src.substring(data.src.length - extLength),
13529
+ error: false,
13530
+ loading: true,
13531
+ replaceColor: data.replaceColor,
13532
+ ratio: data.width && data.height ? data.width / data.height : undefined,
13533
+ };
13534
+ containerImages.push(image);
13535
+ engine.images.set(container, containerImages);
13536
+ let imageFunc;
13537
+ if (data.replaceColor) {
13538
+ imageFunc = downloadSvgImage;
13475
13539
  }
13476
13540
  else {
13477
- if (code === clearCode + 1) {
13478
- break;
13479
- }
13480
- if (code >= dic.length) {
13481
- dic.push(dic[last].concat(dic[last][0]));
13482
- }
13483
- else if (last !== clearCode) {
13484
- dic.push(dic[last].concat(dic[code][0]));
13485
- }
13486
- for (const item of dic[code]) {
13487
- const { r, g, b, a } = getColor(item);
13488
- pixelPos += 4;
13489
- image.data.set([r, g, b, a], pixelPos);
13490
- }
13491
- if (dic.length >= 1 << size && size < 0xc) {
13492
- size++;
13493
- }
13541
+ imageFunc = loadImage;
13494
13542
  }
13543
+ await imageFunc(image);
13495
13544
  }
13496
- frame.image = image;
13497
- frame.bitmap = await createImageBitmap(image);
13498
- }
13545
+ catch {
13546
+ throw new Error(`${data.name ?? data.src} not found`);
13547
+ }
13548
+ };
13499
13549
  }
13500
- async function parseBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, canvasSettings, progressCallback) {
13501
- switch (byteStream.nextByte()) {
13502
- case GIFDataHeaders.EndOfFile:
13503
- return true;
13504
- case GIFDataHeaders.Image:
13505
- await parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, canvasSettings);
13506
- break;
13507
- case GIFDataHeaders.Extension:
13508
- parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex);
13509
- break;
13510
- default:
13511
- throw new EvalError("undefined block found");
13512
- }
13513
- return false;
13550
+ async function loadImageShape(engine) {
13551
+ engine.checkVersion("4.2.1");
13552
+ await engine.pluginManager.register(e => {
13553
+ addLoadImageToEngine(e);
13554
+ e.pluginManager.addPlugin(new ImagePreloaderPlugin(e));
13555
+ e.pluginManager.addShape(shapeTypes, container => Promise.resolve(new ImageDrawer(e, container)));
13556
+ });
13514
13557
  }
13515
- function getGIFLoopAmount(gif) {
13516
- for (const extension of gif.applicationExtensions) {
13517
- if (extension.identifier + extension.authenticationCode !== "NETSCAPE2.0") {
13518
- continue;
13558
+
13559
+ class LifeDelay extends ValueWithRandom {
13560
+ sync = false;
13561
+ load(data) {
13562
+ if (isNull(data)) {
13563
+ return;
13519
13564
  }
13520
- return extension.data[1] + (extension.data[2] << 8);
13565
+ super.load(data);
13566
+ loadProperty(this, "sync", data.sync);
13521
13567
  }
13522
- return Number.NaN;
13523
13568
  }
13524
- async function decodeGIF(gifURL, canvasSettings, progressCallback, avgAlpha) {
13525
- avgAlpha ??= false;
13526
- const res = await fetch(gifURL);
13527
- if (!res.ok && res.status === 404) {
13528
- throw new EvalError("file not found");
13569
+
13570
+ class LifeDuration extends ValueWithRandom {
13571
+ sync = false;
13572
+ load(data) {
13573
+ if (isNull(data)) {
13574
+ return;
13575
+ }
13576
+ super.load(data);
13577
+ loadProperty(this, "sync", data.sync);
13529
13578
  }
13530
- const buffer = await res.arrayBuffer(), gif = {
13531
- width: 0,
13532
- height: 0,
13533
- totalTime: 0,
13534
- colorRes: 0,
13535
- pixelAspectRatio: 0,
13536
- frames: [],
13537
- sortFlag: false,
13538
- globalColorTable: [],
13539
- backgroundImage: new ImageData(1, 1, canvasSettings),
13540
- comments: [],
13541
- applicationExtensions: [],
13542
- }, byteStream = new ByteStream(new Uint8ClampedArray(buffer));
13543
- if (byteStream.getString(6) !== "GIF89a") {
13544
- throw new Error("not a supported GIF file");
13545
- }
13546
- gif.width = byteStream.nextTwoBytes();
13547
- gif.height = byteStream.nextTwoBytes();
13548
- const packedByte = byteStream.nextByte(), globalColorTableFlag = (packedByte & 0x80) === 0x80;
13549
- gif.colorRes = (packedByte & 0x70) >>> 4;
13550
- gif.sortFlag = (packedByte & 8) === 8;
13551
- const globalColorCount = 1 << ((packedByte & 7) + 1), backgroundColorIndex = byteStream.nextByte();
13552
- gif.pixelAspectRatio = byteStream.nextByte();
13553
- if (gif.pixelAspectRatio !== 0) {
13554
- gif.pixelAspectRatio = (gif.pixelAspectRatio + 0xf) / 0x40;
13555
- }
13556
- if (globalColorTableFlag) {
13557
- gif.globalColorTable = parseColorTable(byteStream, globalColorCount);
13558
- }
13559
- const backgroundImage = (() => {
13560
- try {
13561
- return new ImageData(gif.width, gif.height, canvasSettings);
13562
- }
13563
- catch (error) {
13564
- if (error instanceof DOMException && error.name === "IndexSizeError") {
13565
- return null;
13566
- }
13567
- throw error;
13579
+ }
13580
+
13581
+ class Life {
13582
+ count = 0;
13583
+ delay = new LifeDelay();
13584
+ duration = new LifeDuration();
13585
+ load(data) {
13586
+ if (isNull(data)) {
13587
+ return;
13568
13588
  }
13569
- })();
13570
- if (backgroundImage == null) {
13571
- throw new Error("GIF frame size is to large");
13589
+ loadProperty(this, "count", data.count);
13590
+ this.delay.load(data.delay);
13591
+ this.duration.load(data.duration);
13572
13592
  }
13573
- const { r, g, b } = gif.globalColorTable[backgroundColorIndex];
13574
- backgroundImage.data.set(globalColorTableFlag ? [r, g, b, 255] : [0, 0, 0, 0]);
13575
- for (let i = 4; i < backgroundImage.data.length; i *= 2) {
13576
- backgroundImage.data.copyWithin(i, 0, i);
13593
+ }
13594
+
13595
+ const noTime$1 = 0, infiniteValue$1 = -1, noLife = 0, minCanvasSize = 0;
13596
+ function updateLife(particle, delta, canvasSize) {
13597
+ if (!particle.life) {
13598
+ return;
13577
13599
  }
13578
- gif.backgroundImage = backgroundImage;
13579
- let frameIndex = -1, incrementFrameIndex = true, transparencyIndex = -1;
13580
- const getframeIndex = (increment) => {
13581
- if (increment) {
13582
- incrementFrameIndex = true;
13583
- }
13584
- return frameIndex;
13585
- }, getTransparencyIndex = (newValue) => {
13586
- if (newValue != null) {
13587
- transparencyIndex = newValue;
13588
- }
13589
- return transparencyIndex;
13590
- };
13591
- try {
13592
- do {
13593
- if (incrementFrameIndex) {
13594
- gif.frames.push({
13595
- left: 0,
13596
- top: 0,
13597
- width: 0,
13598
- height: 0,
13599
- disposalMethod: DisposalMethod.Replace,
13600
- image: new ImageData(1, 1, canvasSettings),
13601
- plainTextData: null,
13602
- userInputDelayFlag: false,
13603
- delayTime: 0,
13604
- sortFlag: false,
13605
- localColorTable: [],
13606
- reserved: 0,
13607
- GCreserved: 0,
13608
- });
13609
- frameIndex++;
13610
- transparencyIndex = -1;
13611
- incrementFrameIndex = false;
13612
- }
13613
- } while (!(await parseBlock(byteStream, gif, avgAlpha, getframeIndex, getTransparencyIndex, canvasSettings, progressCallback)));
13614
- gif.frames.length--;
13615
- for (const frame of gif.frames) {
13616
- if (frame.userInputDelayFlag && frame.delayTime === 0) {
13617
- gif.totalTime = Infinity;
13618
- break;
13619
- }
13620
- gif.totalTime += frame.delayTime;
13600
+ const life = particle.life;
13601
+ let justSpawned = false;
13602
+ if (particle.spawning) {
13603
+ life.delayTime += delta.value;
13604
+ if (life.delayTime >= particle.life.delay) {
13605
+ justSpawned = true;
13606
+ particle.spawning = false;
13607
+ life.delayTime = noTime$1;
13608
+ life.time = noTime$1;
13621
13609
  }
13622
- return gif;
13623
- }
13624
- catch (error) {
13625
- if (error instanceof EvalError) {
13626
- throw new Error(`error while parsing frame ${frameIndex.toString()} "${error.message}"`, { cause: error });
13610
+ else {
13611
+ return;
13627
13612
  }
13628
- throw error;
13629
13613
  }
13630
- }
13631
- function drawGif(data, canvasSettings) {
13632
- const { context, radius, particle, delta } = data, image = particle.image;
13633
- if (!image?.gifData || !image.gif) {
13614
+ if (life.duration === infiniteValue$1) {
13634
13615
  return;
13635
13616
  }
13636
- const offscreenCanvas = new OffscreenCanvas(image.gifData.width, image.gifData.height), offscreenContext = offscreenCanvas.getContext("2d", canvasSettings);
13637
- if (!offscreenContext) {
13638
- throw new Error("could not create offscreen canvas context");
13617
+ if (justSpawned) {
13618
+ life.time = noTime$1;
13639
13619
  }
13640
- offscreenContext.imageSmoothingQuality = "low";
13641
- offscreenContext.imageSmoothingEnabled = false;
13642
- offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
13643
- particle.gifLoopCount ??= image.gifLoopCount ?? defaultLoopCount;
13644
- let frameIndex = particle.gifFrame ?? defaultFrame;
13645
- const pos = { x: -image.gifData.width * half$2, y: -image.gifData.height * half$2 }, frame = image.gifData.frames[frameIndex];
13646
- particle.gifTime ??= initialTime;
13647
- if (!frame.bitmap) {
13648
- return;
13620
+ else {
13621
+ life.time += delta.value;
13649
13622
  }
13650
- context.scale(radius / image.gifData.width, radius / image.gifData.height);
13651
- switch (frame.disposalMethod) {
13652
- case DisposalMethod.UndefinedA:
13653
- case DisposalMethod.UndefinedB:
13654
- case DisposalMethod.UndefinedC:
13655
- case DisposalMethod.UndefinedD:
13656
- case DisposalMethod.Replace:
13657
- offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
13658
- context.drawImage(offscreenCanvas, pos.x, pos.y);
13659
- offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
13660
- break;
13661
- case DisposalMethod.Combine:
13662
- offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
13663
- context.drawImage(offscreenCanvas, pos.x, pos.y);
13664
- break;
13665
- case DisposalMethod.RestoreBackground:
13666
- offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
13667
- context.drawImage(offscreenCanvas, pos.x, pos.y);
13668
- offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
13669
- if (!image.gifData.globalColorTable.length) {
13670
- offscreenContext.putImageData(image.gifData.frames[firstIndex$3].image, pos.x + frame.left, pos.y + frame.top);
13671
- }
13672
- else {
13673
- offscreenContext.putImageData(image.gifData.backgroundImage, pos.x, pos.y);
13674
- }
13675
- break;
13676
- case DisposalMethod.RestorePrevious:
13677
- {
13678
- const previousImageData = offscreenContext.getImageData(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
13679
- offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
13680
- context.drawImage(offscreenCanvas, pos.x, pos.y);
13681
- offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
13682
- offscreenContext.putImageData(previousImageData, originPoint.x, originPoint.y);
13683
- }
13684
- break;
13623
+ if (life.time < life.duration) {
13624
+ return;
13685
13625
  }
13686
- particle.gifTime += delta.value;
13687
- if (particle.gifTime > frame.delayTime) {
13688
- particle.gifTime -= frame.delayTime;
13689
- if (++frameIndex >= image.gifData.frames.length) {
13690
- if (--particle.gifLoopCount <= defaultLoopCount) {
13691
- return;
13692
- }
13693
- frameIndex = firstIndex$3;
13694
- offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
13695
- }
13696
- particle.gifFrame = frameIndex;
13626
+ life.time = noTime$1;
13627
+ if (particle.life.count > noLife) {
13628
+ particle.life.count--;
13697
13629
  }
13698
- context.scale(image.gifData.width / radius, image.gifData.height / radius);
13699
- }
13700
- async function loadGifImage(image, canvasSettings) {
13701
- if (image.type !== "gif") {
13702
- await loadImage(image);
13630
+ if (particle.life.count === noLife) {
13631
+ particle.destroy();
13703
13632
  return;
13704
13633
  }
13705
- image.loading = true;
13706
- try {
13707
- image.gifData = await decodeGIF(image.source, canvasSettings);
13708
- image.gifLoopCount = getGIFLoopAmount(image.gifData);
13709
- if (!image.gifLoopCount) {
13710
- image.gifLoopCount = Infinity;
13711
- }
13712
- }
13713
- catch {
13714
- image.error = true;
13634
+ const widthRange = setRangeValue(minCanvasSize, canvasSize.width), heightRange = setRangeValue(minCanvasSize, canvasSize.width);
13635
+ particle.position.x = randomInRangeValue(widthRange);
13636
+ particle.position.y = randomInRangeValue(heightRange);
13637
+ particle.spawning = true;
13638
+ life.delayTime = noTime$1;
13639
+ life.time = noTime$1;
13640
+ particle.reset();
13641
+ const lifeOptions = particle.options.life;
13642
+ if (lifeOptions) {
13643
+ life.delay = getRangeValue(lifeOptions.delay.value) * millisecondsToSeconds$1;
13644
+ life.duration = getRangeValue(lifeOptions.duration.value) * millisecondsToSeconds$1;
13715
13645
  }
13716
- image.loading = false;
13717
13646
  }
13718
13647
 
13719
- const sides$3 = 12;
13720
- class ImageDrawer {
13648
+ const noTime = 0, identity$2 = 1, infiniteValue = -1;
13649
+ class LifeUpdater {
13721
13650
  #container;
13722
- #engine;
13723
- constructor(engine, container) {
13724
- this.#engine = engine;
13651
+ constructor(container) {
13725
13652
  this.#container = container;
13726
13653
  }
13727
- draw(data) {
13728
- const { context, radius, particle, opacity } = data, image = particle.image, element = image?.element;
13729
- if (!image) {
13654
+ init(particle) {
13655
+ const container = this.#container, particlesOptions = particle.options, lifeOptions = particlesOptions.life;
13656
+ if (!lifeOptions) {
13730
13657
  return;
13731
13658
  }
13732
- context.globalAlpha = opacity;
13733
- if (image.gif && image.gifData) {
13734
- drawGif(data, this.#container.canvas.render.settings);
13659
+ const delayFactor = lifeOptions.delay.sync ? identity$2 : getRandom(), durationFactor = lifeOptions.duration.sync ? identity$2 : getRandom();
13660
+ particle.life = {
13661
+ delay: container.retina.reduceFactor
13662
+ ? ((getRangeValue(lifeOptions.delay.value) * delayFactor) / container.retina.reduceFactor) *
13663
+ millisecondsToSeconds$1
13664
+ : noTime,
13665
+ delayTime: noTime,
13666
+ duration: container.retina.reduceFactor
13667
+ ? ((getRangeValue(lifeOptions.duration.value) * durationFactor) / container.retina.reduceFactor) *
13668
+ millisecondsToSeconds$1
13669
+ : noTime,
13670
+ time: noTime,
13671
+ count: lifeOptions.count,
13672
+ };
13673
+ if (particle.life.duration <= noTime) {
13674
+ particle.life.duration = infiniteValue;
13735
13675
  }
13736
- else if (element) {
13737
- const ratio = image.ratio, pos = {
13738
- x: -radius,
13739
- y: -radius,
13740
- }, diameter = radius * double;
13741
- context.drawImage(element, pos.x, pos.y, diameter, diameter / ratio);
13676
+ if (particle.life.count <= noTime) {
13677
+ particle.life.count = infiniteValue;
13742
13678
  }
13743
- context.globalAlpha = defaultAlpha$1;
13679
+ particle.spawning = particle.life.delay > noTime;
13744
13680
  }
13745
- getSidesCount() {
13746
- return sides$3;
13681
+ isEnabled(particle) {
13682
+ return !particle.destroyed;
13747
13683
  }
13748
- async init(container) {
13749
- const options = container.actualOptions;
13750
- if (!options.preload || !this.#engine.loadImage) {
13684
+ loadOptions(options, ...sources) {
13685
+ loadOptionProperty(options, "life", Life, ...sources);
13686
+ }
13687
+ update(particle, delta) {
13688
+ if (!this.isEnabled(particle) || !particle.life) {
13751
13689
  return;
13752
13690
  }
13753
- const promises = [];
13754
- for (const imageData of options.preload) {
13755
- promises.push(this.#engine.loadImage(container, imageData));
13756
- }
13757
- await Promise.all(promises);
13691
+ updateLife(particle, delta, this.#container.canvas.size);
13758
13692
  }
13759
- loadShape(particle) {
13760
- const container = this.#container;
13761
- if (!particle.shape || !shapeTypes.includes(particle.shape)) {
13762
- return;
13763
- }
13764
- const imageData = particle.shapeData;
13765
- if (!imageData) {
13766
- return;
13767
- }
13768
- const images = this.#engine.getImages?.(container), image = images?.find((t) => t.name === imageData.name || t.source === imageData.src);
13769
- if (image) {
13770
- return;
13771
- }
13772
- void this.#loadImageShape(container, imageData).then(() => {
13773
- this.loadShape(particle);
13693
+ }
13694
+
13695
+ async function loadLifeUpdater(engine) {
13696
+ engine.checkVersion("4.2.1");
13697
+ await engine.pluginManager.register(e => {
13698
+ e.pluginManager.addParticleUpdater("life", container => {
13699
+ return Promise.resolve(new LifeUpdater(container));
13774
13700
  });
13701
+ });
13702
+ }
13703
+
13704
+ function drawLine(data) {
13705
+ const { context, particle, radius } = data, shapeData = particle.shapeData, centerY = 0;
13706
+ context.moveTo(-radius, centerY);
13707
+ context.lineTo(radius, centerY);
13708
+ context.lineCap = shapeData?.cap ?? "butt";
13709
+ }
13710
+
13711
+ const sides$2 = 1;
13712
+ class LineDrawer {
13713
+ draw(data) {
13714
+ drawLine(data);
13775
13715
  }
13776
- particleInit(container, particle) {
13777
- if (particle.shape !== "image" && particle.shape !== "images") {
13778
- return;
13779
- }
13780
- const images = this.#engine.getImages?.(container), imageData = particle.shapeData;
13781
- if (!imageData) {
13782
- return;
13783
- }
13784
- const color = particle.getFillColor(), image = images?.find((t) => t.name === imageData.name || t.source === imageData.src);
13785
- if (!image) {
13786
- return;
13787
- }
13788
- const replaceColor = imageData.replaceColor;
13789
- if (image.loading) {
13790
- setTimeout(() => {
13791
- this.particleInit(container, particle);
13792
- });
13793
- return;
13794
- }
13795
- void (async () => {
13796
- let imageRes;
13797
- if (image.svgData && color) {
13798
- imageRes = await replaceImageColor(image, imageData, color, particle, container.hdr);
13799
- }
13800
- else {
13801
- imageRes = {
13802
- color,
13803
- data: image,
13804
- element: image.element,
13805
- gif: image.gif,
13806
- gifData: image.gifData,
13807
- gifLoopCount: image.gifLoopCount,
13808
- loaded: true,
13809
- ratio: imageData.width && imageData.height ? imageData.width / imageData.height : (image.ratio ?? defaultRatio$1),
13810
- replaceColor: replaceColor,
13811
- source: imageData.src,
13812
- };
13813
- }
13814
- if (!imageRes.ratio) {
13815
- imageRes.ratio = 1;
13816
- }
13817
- const close = imageData.close ?? particle.shapeClose, imageShape = {
13818
- image: imageRes,
13819
- close,
13820
- };
13821
- particle.image = imageShape.image;
13822
- particle.shapeClose = imageShape.close;
13823
- })();
13716
+ getSidesCount() {
13717
+ return sides$2;
13824
13718
  }
13825
- #loadImageShape = async (container, imageShape) => {
13826
- if (!this.#engine.loadImage) {
13827
- throw new Error(`Image shape not initialized`);
13828
- }
13829
- await this.#engine.loadImage(container, {
13830
- gif: imageShape.gif,
13831
- name: imageShape.name,
13832
- replaceColor: imageShape.replaceColor,
13833
- src: imageShape.src,
13834
- });
13835
- };
13836
13719
  }
13837
13720
 
13838
- class Preload {
13839
- gif = false;
13840
- height;
13841
- name;
13842
- replaceColor;
13843
- src = "";
13844
- width;
13721
+ async function loadLineShape(engine) {
13722
+ engine.checkVersion("4.2.1");
13723
+ await engine.pluginManager.register(e => {
13724
+ e.pluginManager.addShape(["line"], () => Promise.resolve(new LineDrawer()));
13725
+ });
13726
+ }
13727
+
13728
+ class Attract {
13729
+ distance = 200;
13730
+ enable = false;
13731
+ rotate;
13732
+ constructor() {
13733
+ this.rotate = {
13734
+ x: 3000,
13735
+ y: 3000,
13736
+ };
13737
+ }
13845
13738
  load(data) {
13846
13739
  if (isNull(data)) {
13847
13740
  return;
13848
13741
  }
13849
- loadProperty(this, "gif", data.gif);
13850
- loadProperty(this, "height", data.height);
13851
- loadProperty(this, "name", data.name);
13852
- loadProperty(this, "replaceColor", data.replaceColor);
13853
- loadProperty(this, "src", data.src);
13854
- loadProperty(this, "width", data.width);
13742
+ loadRangeProperty(this, "distance", data.distance);
13743
+ loadProperty(this, "enable", data.enable);
13744
+ if (data.rotate) {
13745
+ const rotateX = data.rotate.x;
13746
+ if (rotateX !== undefined) {
13747
+ this.rotate.x = rotateX;
13748
+ }
13749
+ const rotateY = data.rotate.y;
13750
+ if (rotateY !== undefined) {
13751
+ this.rotate.y = rotateY;
13752
+ }
13753
+ }
13855
13754
  }
13856
13755
  }
13857
13756
 
13858
- class ImagePreloaderPlugin {
13859
- id = "image-preloader";
13860
- #engine;
13861
- constructor(engine) {
13862
- this.#engine = engine;
13757
+ const attractFactor = 1000, identity$1 = 1;
13758
+ class Attractor extends ParticlesInteractorBase {
13759
+ #maxDistance;
13760
+ constructor(container) {
13761
+ super(container);
13762
+ this.#maxDistance = 0;
13863
13763
  }
13864
- async getPlugin(container) {
13865
- const { ImagePreloaderInstance } = await Promise.resolve().then(function () { return ImagePreloaderInstance$1; });
13866
- return new ImagePreloaderInstance(this.#engine, container);
13764
+ get maxDistance() {
13765
+ return this.#maxDistance;
13867
13766
  }
13868
- loadOptions(_container, options, source) {
13869
- if (!source?.preload) {
13767
+ clear() {
13768
+ }
13769
+ init() {
13770
+ }
13771
+ interact(p1) {
13772
+ if (!p1.options.attract?.enable) {
13870
13773
  return;
13871
13774
  }
13872
- options.preload ??= [];
13873
- const preloadOptions = options.preload;
13874
- for (const item of source.preload) {
13875
- const existing = preloadOptions.find(t => t.name === item.name || t.src === item.src);
13876
- if (existing) {
13877
- existing.load(item);
13775
+ const container = this.container;
13776
+ if (isNull(p1.attractDistance)) {
13777
+ const attractDistance = getRangeValue(p1.options.attract.distance);
13778
+ if (attractDistance > this.#maxDistance) {
13779
+ this.#maxDistance = attractDistance;
13878
13780
  }
13879
- else {
13880
- const preload = new Preload();
13881
- preload.load(item);
13882
- preloadOptions.push(preload);
13781
+ p1.attractDistance = attractDistance * container.retina.pixelRatio;
13782
+ }
13783
+ const distance = p1.attractDistance, pos1 = p1.getPosition(), query = container.particles.grid.queryCircle(pos1, distance);
13784
+ for (const p2 of query) {
13785
+ if (p1 === p2 || !p2.options.attract?.enable || p2.destroyed || p2.spawning) {
13786
+ continue;
13883
13787
  }
13788
+ const pos2 = p2.getPosition(), { dx, dy } = getDistances(pos1, pos2), rotate = p1.options.attract.rotate, ax = dx / (rotate.x * attractFactor), ay = dy / (rotate.y * attractFactor), p1Factor = p2.size.value / p1.size.value, p2Factor = identity$1 / p1Factor;
13789
+ p1.velocity.x -= ax * p1Factor;
13790
+ p1.velocity.y -= ay * p1Factor;
13791
+ p2.velocity.x += ax * p2Factor;
13792
+ p2.velocity.y += ay * p2Factor;
13884
13793
  }
13885
13794
  }
13886
- needsPlugin() {
13887
- return true;
13795
+ isEnabled(particle) {
13796
+ return particle.options.attract?.enable ?? false;
13797
+ }
13798
+ loadParticlesOptions(options, ...sources) {
13799
+ loadOptionProperty(options, "attract", Attract, ...sources);
13800
+ }
13801
+ reset() {
13888
13802
  }
13889
13803
  }
13890
13804
 
13891
- const extLength = 3;
13892
- function addLoadImageToEngine(engine) {
13893
- engine.getImages ??= (container) => {
13894
- engine.images ??= new Map();
13895
- let images = engine.images.get(container);
13896
- if (!images) {
13897
- images = [];
13898
- engine.images.set(container, images);
13899
- }
13900
- return images;
13901
- };
13902
- engine.loadImage ??= async (container, data) => {
13903
- if (!engine.getImages) {
13904
- throw new Error("No images collection found");
13905
- }
13906
- if (!data.name && !data.src) {
13907
- throw new Error("No image source provided");
13805
+ async function loadParticlesAttractInteraction(engine) {
13806
+ engine.checkVersion("4.2.1");
13807
+ await engine.pluginManager.register((e) => {
13808
+ ensureInteractivityPluginLoaded(e);
13809
+ e.pluginManager.addInteractor?.("particlesAttract", container => {
13810
+ return Promise.resolve(new Attractor(container));
13811
+ });
13812
+ });
13813
+ }
13814
+
13815
+ var CollisionMode;
13816
+ (function (CollisionMode) {
13817
+ CollisionMode["absorb"] = "absorb";
13818
+ CollisionMode["bounce"] = "bounce";
13819
+ CollisionMode["destroy"] = "destroy";
13820
+ })(CollisionMode || (CollisionMode = {}));
13821
+
13822
+ class CollisionsAbsorb {
13823
+ speed = 2;
13824
+ load(data) {
13825
+ if (isNull(data)) {
13826
+ return;
13908
13827
  }
13909
- engine.images ??= new Map();
13910
- const containerImages = engine.getImages(container);
13911
- if (containerImages.some((t) => t.name === data.name || t.source === data.src)) {
13828
+ loadProperty(this, "speed", data.speed);
13829
+ }
13830
+ }
13831
+
13832
+ class CollisionsOverlap {
13833
+ enable = true;
13834
+ retries = 0;
13835
+ load(data) {
13836
+ if (isNull(data)) {
13912
13837
  return;
13913
13838
  }
13914
- try {
13915
- const image = {
13916
- gif: data.gif,
13917
- name: data.name ?? data.src,
13918
- source: data.src,
13919
- type: data.src.substring(data.src.length - extLength),
13920
- error: false,
13921
- loading: true,
13922
- replaceColor: data.replaceColor,
13923
- ratio: data.width && data.height ? data.width / data.height : undefined,
13924
- };
13925
- containerImages.push(image);
13926
- engine.images.set(container, containerImages);
13927
- let imageFunc;
13928
- if (data.gif) {
13929
- imageFunc = (img) => loadGifImage(img, { colorSpace: "srgb" });
13930
- }
13931
- else if (data.replaceColor) {
13932
- imageFunc = downloadSvgImage;
13933
- }
13934
- else {
13935
- imageFunc = loadImage;
13936
- }
13937
- await imageFunc(image);
13938
- }
13939
- catch {
13940
- throw new Error(`${data.name ?? data.src} not found`);
13941
- }
13942
- };
13943
- }
13944
- async function loadImageShape(engine) {
13945
- engine.checkVersion("4.2.1");
13946
- await engine.pluginManager.register(e => {
13947
- addLoadImageToEngine(e);
13948
- e.pluginManager.addPlugin(new ImagePreloaderPlugin(e));
13949
- e.pluginManager.addShape(shapeTypes, container => Promise.resolve(new ImageDrawer(e, container)));
13950
- });
13951
- }
13952
-
13953
- class LifeDelay extends ValueWithRandom {
13954
- sync = false;
13955
- load(data) {
13956
- if (isNull(data)) {
13957
- return;
13958
- }
13959
- super.load(data);
13960
- loadProperty(this, "sync", data.sync);
13839
+ loadProperty(this, "enable", data.enable);
13840
+ loadProperty(this, "retries", data.retries);
13961
13841
  }
13962
13842
  }
13963
13843
 
13964
- class LifeDuration extends ValueWithRandom {
13965
- sync = false;
13844
+ class Collisions {
13845
+ absorb = new CollisionsAbsorb();
13846
+ bounce = new ParticlesBounce();
13847
+ enable = false;
13848
+ maxSpeed = 50;
13849
+ mode = CollisionMode.bounce;
13850
+ overlap = new CollisionsOverlap();
13966
13851
  load(data) {
13967
13852
  if (isNull(data)) {
13968
13853
  return;
13969
13854
  }
13970
- super.load(data);
13971
- loadProperty(this, "sync", data.sync);
13855
+ this.absorb.load(data.absorb);
13856
+ this.bounce.load(data.bounce);
13857
+ loadProperty(this, "enable", data.enable);
13858
+ loadRangeProperty(this, "maxSpeed", data.maxSpeed);
13859
+ loadProperty(this, "mode", data.mode);
13860
+ this.overlap.load(data.overlap);
13972
13861
  }
13973
13862
  }
13974
13863
 
13975
- class Life {
13976
- count = 0;
13977
- delay = new LifeDelay();
13978
- duration = new LifeDuration();
13979
- load(data) {
13980
- if (isNull(data)) {
13981
- return;
13982
- }
13983
- loadProperty(this, "count", data.count);
13984
- this.delay.load(data.delay);
13985
- this.duration.load(data.duration);
13864
+ const minAbsorbAmount = 0;
13865
+ function updateAbsorb(p1, r1, p2, r2, delta, pixelRatio) {
13866
+ if (!p1.options.collisions || !p2.options.collisions) {
13867
+ return;
13868
+ }
13869
+ const absorbSpeed = p1.options.collisions.absorb.speed, shrinkAmount = clamp(absorbSpeed * delta.factor, minAbsorbAmount, r2);
13870
+ p1.size.value = Math.sqrt(r1 * r1 + shrinkAmount * shrinkAmount);
13871
+ p2.size.value -= shrinkAmount;
13872
+ if (p2.size.value <= pixelRatio) {
13873
+ p2.size.value = 0;
13874
+ p2.destroy();
13986
13875
  }
13987
13876
  }
13988
-
13989
- const noTime$1 = 0, infiniteValue$1 = -1, noLife = 0, minCanvasSize = 0;
13990
- function updateLife(particle, delta, canvasSize) {
13991
- if (!particle.life) {
13992
- return;
13877
+ function absorb(p1, p2, delta, pixelRatio) {
13878
+ const r1 = p1.getRadius(), r2 = p2.getRadius();
13879
+ if (!r1 && r2) {
13880
+ p1.destroy();
13993
13881
  }
13994
- const life = particle.life;
13995
- let justSpawned = false;
13996
- if (particle.spawning) {
13997
- life.delayTime += delta.value;
13998
- if (life.delayTime >= particle.life.delay) {
13999
- justSpawned = true;
14000
- particle.spawning = false;
14001
- life.delayTime = noTime$1;
14002
- life.time = noTime$1;
13882
+ else if (r1 && !r2) {
13883
+ p2.destroy();
13884
+ }
13885
+ else if (r1 && r2) {
13886
+ if (r1 >= r2) {
13887
+ updateAbsorb(p1, r1, p2, r2, delta, pixelRatio);
14003
13888
  }
14004
13889
  else {
14005
- return;
13890
+ updateAbsorb(p2, r2, p1, r1, delta, pixelRatio);
14006
13891
  }
14007
13892
  }
14008
- if (life.duration === infiniteValue$1) {
13893
+ }
13894
+
13895
+ const energyCorrectionMinRatio = 1e-6, energyDriftThreshold = 1e-4, correctionFactorNeutral = 1, fixBounceSpeed = (p) => {
13896
+ if (!p.options.collisions) {
14009
13897
  return;
14010
13898
  }
14011
- if (justSpawned) {
14012
- life.time = noTime$1;
13899
+ p.collisionMaxSpeed ??= getRangeValue(p.options.collisions.maxSpeed);
13900
+ if (p.velocity.length > p.collisionMaxSpeed) {
13901
+ p.velocity.length = p.collisionMaxSpeed;
14013
13902
  }
14014
- else {
14015
- life.time += delta.value;
13903
+ };
13904
+ function bounce(p1, p2) {
13905
+ const m1 = p1.getMass(), m2 = p2.getMass(), speed1Before = p1.velocity.length, speed2Before = p2.velocity.length, keBefore = m1 * speed1Before * speed1Before + m2 * speed2Before * speed2Before;
13906
+ circleBounce(circleBounceDataFromParticle(p1), circleBounceDataFromParticle(p2));
13907
+ const speed1After = p1.velocity.length, speed2After = p2.velocity.length, keAfter = m1 * speed1After * speed1After + m2 * speed2After * speed2After;
13908
+ if (keAfter > keBefore * energyCorrectionMinRatio) {
13909
+ const correctionFactor = Math.sqrt(keBefore / keAfter);
13910
+ if (Math.abs(correctionFactor - correctionFactorNeutral) > energyDriftThreshold) {
13911
+ p1.velocity.length = speed1After * correctionFactor;
13912
+ p2.velocity.length = speed2After * correctionFactor;
13913
+ }
14016
13914
  }
14017
- if (life.time < life.duration) {
14018
- return;
13915
+ fixBounceSpeed(p1);
13916
+ fixBounceSpeed(p2);
13917
+ }
13918
+
13919
+ function destroy(p1, p2) {
13920
+ if (!p1.unbreakable && !p2.unbreakable) {
13921
+ bounce(p1, p2);
14019
13922
  }
14020
- life.time = noTime$1;
14021
- if (particle.life.count > noLife) {
14022
- particle.life.count--;
13923
+ const p1Radius = p1.getRadius(), p2Radius = p2.getRadius();
13924
+ if (!p1Radius && p2Radius) {
13925
+ p1.destroy();
14023
13926
  }
14024
- if (particle.life.count === noLife) {
14025
- particle.destroy();
13927
+ else if (p1Radius && !p2Radius) {
13928
+ p2.destroy();
13929
+ }
13930
+ else if (p1Radius && p2Radius) {
13931
+ const deleteP = p1.getRadius() >= p2.getRadius() ? p2 : p1;
13932
+ deleteP.destroy();
13933
+ }
13934
+ }
13935
+
13936
+ function resolveCollision(p1, p2, delta, pixelRatio) {
13937
+ if (!p1.options.collisions || !p2.options.collisions) {
14026
13938
  return;
14027
13939
  }
14028
- const widthRange = setRangeValue(minCanvasSize, canvasSize.width), heightRange = setRangeValue(minCanvasSize, canvasSize.width);
14029
- particle.position.x = randomInRangeValue(widthRange);
14030
- particle.position.y = randomInRangeValue(heightRange);
14031
- particle.spawning = true;
14032
- life.delayTime = noTime$1;
14033
- life.time = noTime$1;
14034
- particle.reset();
14035
- const lifeOptions = particle.options.life;
14036
- if (lifeOptions) {
14037
- life.delay = getRangeValue(lifeOptions.delay.value) * millisecondsToSeconds$1;
14038
- life.duration = getRangeValue(lifeOptions.duration.value) * millisecondsToSeconds$1;
13940
+ switch (p1.options.collisions.mode) {
13941
+ case CollisionMode.absorb: {
13942
+ absorb(p1, p2, delta, pixelRatio);
13943
+ break;
13944
+ }
13945
+ case CollisionMode.bounce: {
13946
+ bounce(p1, p2);
13947
+ break;
13948
+ }
13949
+ case CollisionMode.destroy: {
13950
+ destroy(p1, p2);
13951
+ break;
13952
+ }
14039
13953
  }
14040
13954
  }
14041
13955
 
14042
- const noTime = 0, identity$2 = 1, infiniteValue = -1;
14043
- class LifeUpdater {
14044
- #container;
13956
+ class Collider extends ParticlesInteractorBase {
13957
+ maxDistance;
14045
13958
  constructor(container) {
14046
- this.#container = container;
13959
+ super(container);
13960
+ this.maxDistance = 0;
14047
13961
  }
14048
- init(particle) {
14049
- const container = this.#container, particlesOptions = particle.options, lifeOptions = particlesOptions.life;
14050
- if (!lifeOptions) {
13962
+ clear() {
13963
+ }
13964
+ init() {
13965
+ }
13966
+ interact(p1, _interactivityData, delta) {
13967
+ if (p1.destroyed || p1.spawning) {
14051
13968
  return;
14052
13969
  }
14053
- const delayFactor = lifeOptions.delay.sync ? identity$2 : getRandom(), durationFactor = lifeOptions.duration.sync ? identity$2 : getRandom();
14054
- particle.life = {
14055
- delay: container.retina.reduceFactor
14056
- ? ((getRangeValue(lifeOptions.delay.value) * delayFactor) / container.retina.reduceFactor) *
14057
- millisecondsToSeconds$1
14058
- : noTime,
14059
- delayTime: noTime,
14060
- duration: container.retina.reduceFactor
14061
- ? ((getRangeValue(lifeOptions.duration.value) * durationFactor) / container.retina.reduceFactor) *
14062
- millisecondsToSeconds$1
14063
- : noTime,
14064
- time: noTime,
14065
- count: lifeOptions.count,
14066
- };
14067
- if (particle.life.duration <= noTime) {
14068
- particle.life.duration = infiniteValue;
14069
- }
14070
- if (particle.life.count <= noTime) {
14071
- particle.life.count = infiniteValue;
13970
+ const container = this.container, pos1 = p1.getPosition(), radius1 = p1.getRadius(), query = container.particles.grid.queryCircle(pos1, radius1 * double);
13971
+ for (const p2 of query) {
13972
+ if (p1 === p2 ||
13973
+ p1.id >= p2.id ||
13974
+ !p1.options.collisions?.enable ||
13975
+ !p2.options.collisions?.enable ||
13976
+ p1.options.collisions.mode !== p2.options.collisions.mode ||
13977
+ p2.destroyed ||
13978
+ p2.spawning) {
13979
+ continue;
13980
+ }
13981
+ const pos2 = p2.getPosition(), radius2 = p2.getRadius();
13982
+ if (Math.abs(Math.round(pos1.z) - Math.round(pos2.z)) > radius1 + radius2) {
13983
+ continue;
13984
+ }
13985
+ const dist = getDistance(pos1, pos2), distP = radius1 + radius2;
13986
+ if (dist > distP) {
13987
+ continue;
13988
+ }
13989
+ resolveCollision(p1, p2, delta, container.retina.pixelRatio);
14072
13990
  }
14073
- particle.spawning = particle.life.delay > noTime;
14074
13991
  }
14075
13992
  isEnabled(particle) {
14076
- return !particle.destroyed;
13993
+ return !!particle.options.collisions?.enable;
14077
13994
  }
14078
- loadOptions(options, ...sources) {
14079
- loadOptionProperty(options, "life", Life, ...sources);
13995
+ loadParticlesOptions(options, ...sources) {
13996
+ loadOptionProperty(options, "collisions", Collisions, ...sources);
14080
13997
  }
14081
- update(particle, delta) {
14082
- if (!this.isEnabled(particle) || !particle.life) {
14083
- return;
14084
- }
14085
- updateLife(particle, delta, this.#container.canvas.size);
13998
+ reset() {
14086
13999
  }
14087
14000
  }
14088
14001
 
14089
- async function loadLifeUpdater(engine) {
14090
- engine.checkVersion("4.2.1");
14091
- await engine.pluginManager.register(e => {
14092
- e.pluginManager.addParticleUpdater("life", container => {
14093
- return Promise.resolve(new LifeUpdater(container));
14094
- });
14095
- });
14096
- }
14097
-
14098
- function drawLine(data) {
14099
- const { context, particle, radius } = data, shapeData = particle.shapeData, centerY = 0;
14100
- context.moveTo(-radius, centerY);
14101
- context.lineTo(radius, centerY);
14102
- context.lineCap = shapeData?.cap ?? "butt";
14103
- }
14104
-
14105
- const sides$2 = 1;
14106
- class LineDrawer {
14107
- draw(data) {
14108
- drawLine(data);
14002
+ class OverlapPlugin {
14003
+ id = "overlap";
14004
+ async getPlugin(container) {
14005
+ const { OverlapPluginInstance } = await Promise.resolve().then(function () { return OverlapPluginInstance$1; });
14006
+ return new OverlapPluginInstance(container);
14109
14007
  }
14110
- getSidesCount() {
14111
- return sides$2;
14008
+ loadOptions() {
14009
+ }
14010
+ needsPlugin() {
14011
+ return true;
14112
14012
  }
14113
14013
  }
14114
14014
 
14115
- async function loadLineShape(engine) {
14015
+ async function loadParticlesCollisionsInteraction(engine) {
14116
14016
  engine.checkVersion("4.2.1");
14117
- await engine.pluginManager.register(e => {
14118
- e.pluginManager.addShape(["line"], () => Promise.resolve(new LineDrawer()));
14017
+ await engine.pluginManager.register((e) => {
14018
+ ensureInteractivityPluginLoaded(e);
14019
+ e.pluginManager.addPlugin(new OverlapPlugin());
14020
+ e.pluginManager.addInteractor?.("particlesCollisions", container => {
14021
+ return Promise.resolve(new Collider(container));
14022
+ });
14119
14023
  });
14120
14024
  }
14121
14025
 
14122
- class Attract {
14123
- distance = 200;
14026
+ class CircleWarp extends Circle {
14027
+ #canvasSize;
14028
+ constructor(x, y, radius, canvasSize) {
14029
+ super(x, y, radius);
14030
+ this.#canvasSize = canvasSize;
14031
+ }
14032
+ contains(point) {
14033
+ if (super.contains(point))
14034
+ return true;
14035
+ const { width, height } = this.#canvasSize, { x, y } = point;
14036
+ return (super.contains({ x: x - width, y }) ||
14037
+ super.contains({ x: x + width, y }) ||
14038
+ super.contains({ x, y: y - height }) ||
14039
+ super.contains({ x, y: y + height }) ||
14040
+ super.contains({ x: x - width, y: y - height }) ||
14041
+ super.contains({ x: x + width, y: y + height }) ||
14042
+ super.contains({ x: x - width, y: y + height }) ||
14043
+ super.contains({ x: x + width, y: y - height }));
14044
+ }
14045
+ intersects(range) {
14046
+ if (super.intersects(range))
14047
+ return true;
14048
+ const { width, height } = this.#canvasSize, pos = range.position, shifts = [
14049
+ { x: -width, y: 0 },
14050
+ { x: width, y: 0 },
14051
+ { x: 0, y: -height },
14052
+ { x: 0, y: height },
14053
+ { x: -width, y: -height },
14054
+ { x: width, y: height },
14055
+ { x: -width, y: height },
14056
+ { x: width, y: -height },
14057
+ ];
14058
+ for (const shift of shifts) {
14059
+ const shiftedPos = { x: pos.x + shift.x, y: pos.y + shift.y };
14060
+ let shiftedRange;
14061
+ if (range instanceof Circle) {
14062
+ shiftedRange = new Circle(shiftedPos.x, shiftedPos.y, range.radius);
14063
+ }
14064
+ else {
14065
+ const rect = range;
14066
+ shiftedRange = new Rectangle(shiftedPos.x, shiftedPos.y, rect.size.width, rect.size.height);
14067
+ }
14068
+ if (super.intersects(shiftedRange))
14069
+ return true;
14070
+ }
14071
+ return false;
14072
+ }
14073
+ }
14074
+
14075
+ class LinksShadow {
14076
+ blur = 5;
14077
+ color = new OptionsColor();
14124
14078
  enable = false;
14125
- rotate;
14126
14079
  constructor() {
14127
- this.rotate = {
14128
- x: 3000,
14129
- y: 3000,
14130
- };
14080
+ this.color.value = "#000";
14131
14081
  }
14132
14082
  load(data) {
14133
14083
  if (isNull(data)) {
14134
14084
  return;
14135
14085
  }
14136
- loadRangeProperty(this, "distance", data.distance);
14086
+ loadProperty(this, "blur", data.blur);
14087
+ this.color = OptionsColor.create(this.color, data.color);
14137
14088
  loadProperty(this, "enable", data.enable);
14138
- if (data.rotate) {
14139
- const rotateX = data.rotate.x;
14140
- if (rotateX !== undefined) {
14141
- this.rotate.x = rotateX;
14142
- }
14143
- const rotateY = data.rotate.y;
14144
- if (rotateY !== undefined) {
14145
- this.rotate.y = rotateY;
14146
- }
14089
+ }
14090
+ }
14091
+
14092
+ class LinksTriangle {
14093
+ color;
14094
+ enable = false;
14095
+ frequency = 1;
14096
+ opacity;
14097
+ load(data) {
14098
+ if (isNull(data)) {
14099
+ return;
14100
+ }
14101
+ if (data.color !== undefined) {
14102
+ this.color = OptionsColor.create(this.color, data.color);
14147
14103
  }
14104
+ loadProperty(this, "enable", data.enable);
14105
+ loadProperty(this, "frequency", data.frequency);
14106
+ loadProperty(this, "opacity", data.opacity);
14148
14107
  }
14149
14108
  }
14150
14109
 
14151
- const attractFactor = 1000, identity$1 = 1;
14152
- class Attractor extends ParticlesInteractorBase {
14110
+ class Links {
14111
+ blink = false;
14112
+ color = new OptionsColor();
14113
+ consent = false;
14114
+ distance = 100;
14115
+ enable = false;
14116
+ frequency = 1;
14117
+ id;
14118
+ opacity = 1;
14119
+ shadow = new LinksShadow();
14120
+ triangles = new LinksTriangle();
14121
+ warp = false;
14122
+ width = 1;
14123
+ constructor() {
14124
+ this.color.value = "#fff";
14125
+ }
14126
+ load(data) {
14127
+ if (isNull(data)) {
14128
+ return;
14129
+ }
14130
+ loadProperty(this, "id", data.id);
14131
+ loadProperty(this, "blink", data.blink);
14132
+ this.color = OptionsColor.create(this.color, data.color);
14133
+ loadProperty(this, "consent", data.consent);
14134
+ loadProperty(this, "distance", data.distance);
14135
+ loadProperty(this, "enable", data.enable);
14136
+ loadProperty(this, "frequency", data.frequency);
14137
+ loadProperty(this, "opacity", data.opacity);
14138
+ this.shadow.load(data.shadow);
14139
+ this.triangles.load(data.triangles);
14140
+ loadProperty(this, "width", data.width);
14141
+ loadProperty(this, "warp", data.warp);
14142
+ }
14143
+ }
14144
+
14145
+ const opacityOffset = 1, minDistance$1 = 0;
14146
+ function getWarpDistance(pos1, pos2, canvasSize) {
14147
+ const { dx, dy } = getDistances(pos1, pos2), absDiffs = { x: Math.abs(dx), y: Math.abs(dy) }, warpDistances = {
14148
+ x: Math.min(absDiffs.x, canvasSize.width - absDiffs.x),
14149
+ y: Math.min(absDiffs.y, canvasSize.height - absDiffs.y),
14150
+ };
14151
+ return Math.hypot(warpDistances.x, warpDistances.y);
14152
+ }
14153
+ class Linker extends ParticlesInteractorBase {
14153
14154
  #maxDistance;
14154
- constructor(container) {
14155
+ #pluginManager;
14156
+ constructor(pluginManager, container) {
14155
14157
  super(container);
14158
+ this.#pluginManager = pluginManager;
14156
14159
  this.#maxDistance = 0;
14157
14160
  }
14158
14161
  get maxDistance() {
@@ -14161,705 +14164,308 @@
14161
14164
  clear() {
14162
14165
  }
14163
14166
  init() {
14167
+ this.container.particles.linksColor = undefined;
14168
+ this.container.particles.linksColors = new Map();
14164
14169
  }
14165
14170
  interact(p1) {
14166
- if (!p1.options.attract?.enable) {
14171
+ if (!p1.options.links) {
14167
14172
  return;
14168
14173
  }
14169
- const container = this.container;
14170
- if (isNull(p1.attractDistance)) {
14171
- const attractDistance = getRangeValue(p1.options.attract.distance);
14172
- if (attractDistance > this.#maxDistance) {
14173
- this.#maxDistance = attractDistance;
14174
- }
14175
- p1.attractDistance = attractDistance * container.retina.pixelRatio;
14174
+ p1.links = [];
14175
+ if (p1.linksDistance && p1.linksDistance > this.#maxDistance) {
14176
+ this.#maxDistance = p1.linksDistance;
14176
14177
  }
14177
- const distance = p1.attractDistance, pos1 = p1.getPosition(), query = container.particles.grid.queryCircle(pos1, distance);
14178
+ const pos1 = p1.getPosition(), container = this.container, canvasSize = container.canvas.size;
14179
+ if (pos1.x < originPoint.x || pos1.y < originPoint.y || pos1.x > canvasSize.width || pos1.y > canvasSize.height) {
14180
+ return;
14181
+ }
14182
+ const linkOpt1 = p1.options.links, optOpacity = linkOpt1.opacity, optDistance = p1.retina.linksDistance ?? minDistance$1, warp = linkOpt1.warp, range = warp ? new CircleWarp(pos1.x, pos1.y, optDistance, canvasSize) : new Circle(pos1.x, pos1.y, optDistance), query = container.particles.grid.query(range);
14178
14183
  for (const p2 of query) {
14179
- if (p1 === p2 || !p2.options.attract?.enable || p2.destroyed || p2.spawning) {
14184
+ const linkOpt2 = p2.options.links;
14185
+ if (p1 === p2 ||
14186
+ !linkOpt2?.enable ||
14187
+ linkOpt1.id !== linkOpt2.id ||
14188
+ p2.spawning ||
14189
+ p2.destroyed ||
14190
+ !p2.links ||
14191
+ p1.links.some(t => t.destination === p2) ||
14192
+ p2.links.some(t => t.destination === p1)) {
14180
14193
  continue;
14181
14194
  }
14182
- const pos2 = p2.getPosition(), { dx, dy } = getDistances(pos1, pos2), rotate = p1.options.attract.rotate, ax = dx / (rotate.x * attractFactor), ay = dy / (rotate.y * attractFactor), p1Factor = p2.size.value / p1.size.value, p2Factor = identity$1 / p1Factor;
14183
- p1.velocity.x -= ax * p1Factor;
14184
- p1.velocity.y -= ay * p1Factor;
14185
- p2.velocity.x += ax * p2Factor;
14186
- p2.velocity.y += ay * p2Factor;
14195
+ const pos2 = p2.getPosition();
14196
+ if (pos2.x < originPoint.x || pos2.y < originPoint.y || pos2.x > canvasSize.width || pos2.y > canvasSize.height) {
14197
+ continue;
14198
+ }
14199
+ const distDirect = getDistances(pos1, pos2).distance, distWarp = warp && linkOpt2.warp ? getWarpDistance(pos1, pos2, canvasSize) : distDirect, distance = Math.min(distDirect, distWarp);
14200
+ if (distance > optDistance) {
14201
+ continue;
14202
+ }
14203
+ const opacityLine = (opacityOffset - distance / optDistance) * optOpacity;
14204
+ this.#setColor(p1);
14205
+ p1.links.push({
14206
+ destination: p2,
14207
+ opacity: opacityLine,
14208
+ color: this.#getLinkColor(p1, p2),
14209
+ isWarped: distWarp < distDirect,
14210
+ });
14187
14211
  }
14188
14212
  }
14189
14213
  isEnabled(particle) {
14190
- return particle.options.attract?.enable ?? false;
14214
+ return !!particle.options.links?.enable;
14191
14215
  }
14192
14216
  loadParticlesOptions(options, ...sources) {
14193
- loadOptionProperty(options, "attract", Attract, ...sources);
14217
+ loadOptionProperty(options, "links", Links, ...sources);
14194
14218
  }
14195
14219
  reset() {
14196
14220
  }
14197
- }
14198
-
14199
- async function loadParticlesAttractInteraction(engine) {
14200
- engine.checkVersion("4.2.1");
14201
- await engine.pluginManager.register((e) => {
14202
- ensureInteractivityPluginLoaded(e);
14203
- e.pluginManager.addInteractor?.("particlesAttract", container => {
14204
- return Promise.resolve(new Attractor(container));
14205
- });
14206
- });
14207
- }
14208
-
14209
- var CollisionMode;
14210
- (function (CollisionMode) {
14211
- CollisionMode["absorb"] = "absorb";
14212
- CollisionMode["bounce"] = "bounce";
14213
- CollisionMode["destroy"] = "destroy";
14214
- })(CollisionMode || (CollisionMode = {}));
14215
-
14216
- class CollisionsAbsorb {
14217
- speed = 2;
14218
- load(data) {
14219
- if (isNull(data)) {
14221
+ #getLinkColor(p1, p2) {
14222
+ const container = this.container, linksOptions = p1.options.links;
14223
+ if (!linksOptions) {
14220
14224
  return;
14221
14225
  }
14222
- loadProperty(this, "speed", data.speed);
14223
- }
14224
- }
14225
-
14226
- class CollisionsOverlap {
14227
- enable = true;
14228
- retries = 0;
14229
- load(data) {
14230
- if (isNull(data)) {
14226
+ const linkColor = linksOptions.id !== undefined
14227
+ ? container.particles.linksColors.get(linksOptions.id)
14228
+ : container.particles.linksColor;
14229
+ return getLinkColor(p1, p2, linkColor);
14230
+ }
14231
+ #setColor(p1) {
14232
+ if (!p1.options.links) {
14231
14233
  return;
14232
14234
  }
14233
- loadProperty(this, "enable", data.enable);
14234
- loadProperty(this, "retries", data.retries);
14235
- }
14236
- }
14237
-
14238
- class Collisions {
14239
- absorb = new CollisionsAbsorb();
14240
- bounce = new ParticlesBounce();
14241
- enable = false;
14242
- maxSpeed = 50;
14243
- mode = CollisionMode.bounce;
14244
- overlap = new CollisionsOverlap();
14245
- load(data) {
14246
- if (isNull(data)) {
14235
+ const container = this.container, linksOptions = p1.options.links;
14236
+ let linkColor = linksOptions.id === undefined
14237
+ ? container.particles.linksColor
14238
+ : container.particles.linksColors.get(linksOptions.id);
14239
+ if (linkColor) {
14247
14240
  return;
14248
14241
  }
14249
- this.absorb.load(data.absorb);
14250
- this.bounce.load(data.bounce);
14251
- loadProperty(this, "enable", data.enable);
14252
- loadRangeProperty(this, "maxSpeed", data.maxSpeed);
14253
- loadProperty(this, "mode", data.mode);
14254
- this.overlap.load(data.overlap);
14255
- }
14256
- }
14257
-
14258
- const minAbsorbAmount = 0;
14259
- function updateAbsorb(p1, r1, p2, r2, delta, pixelRatio) {
14260
- if (!p1.options.collisions || !p2.options.collisions) {
14261
- return;
14262
- }
14263
- const absorbSpeed = p1.options.collisions.absorb.speed, shrinkAmount = clamp(absorbSpeed * delta.factor, minAbsorbAmount, r2);
14264
- p1.size.value = Math.sqrt(r1 * r1 + shrinkAmount * shrinkAmount);
14265
- p2.size.value -= shrinkAmount;
14266
- if (p2.size.value <= pixelRatio) {
14267
- p2.size.value = 0;
14268
- p2.destroy();
14269
- }
14270
- }
14271
- function absorb(p1, p2, delta, pixelRatio) {
14272
- const r1 = p1.getRadius(), r2 = p2.getRadius();
14273
- if (!r1 && r2) {
14274
- p1.destroy();
14275
- }
14276
- else if (r1 && !r2) {
14277
- p2.destroy();
14278
- }
14279
- else if (r1 && r2) {
14280
- if (r1 >= r2) {
14281
- updateAbsorb(p1, r1, p2, r2, delta, pixelRatio);
14242
+ linkColor = getLinkRandomColor(this.#pluginManager, linksOptions.color, linksOptions.blink, linksOptions.consent);
14243
+ if (linksOptions.id === undefined) {
14244
+ container.particles.linksColor = linkColor;
14282
14245
  }
14283
14246
  else {
14284
- updateAbsorb(p2, r2, p1, r1, delta, pixelRatio);
14247
+ container.particles.linksColors.set(linksOptions.id, linkColor);
14285
14248
  }
14286
14249
  }
14287
14250
  }
14288
14251
 
14289
- const energyCorrectionMinRatio = 1e-6, energyDriftThreshold = 1e-4, correctionFactorNeutral = 1, fixBounceSpeed = (p) => {
14290
- if (!p.options.collisions) {
14291
- return;
14252
+ class LinksPlugin {
14253
+ id = "links";
14254
+ #pluginManager;
14255
+ constructor(pluginManager) {
14256
+ this.#pluginManager = pluginManager;
14292
14257
  }
14293
- p.collisionMaxSpeed ??= getRangeValue(p.options.collisions.maxSpeed);
14294
- if (p.velocity.length > p.collisionMaxSpeed) {
14295
- p.velocity.length = p.collisionMaxSpeed;
14258
+ async getPlugin(container) {
14259
+ const { LinkInstance } = await Promise.resolve().then(function () { return LinkInstance$1; });
14260
+ return new LinkInstance(this.#pluginManager, container);
14296
14261
  }
14297
- };
14298
- function bounce(p1, p2) {
14299
- const m1 = p1.getMass(), m2 = p2.getMass(), speed1Before = p1.velocity.length, speed2Before = p2.velocity.length, keBefore = m1 * speed1Before * speed1Before + m2 * speed2Before * speed2Before;
14300
- circleBounce(circleBounceDataFromParticle(p1), circleBounceDataFromParticle(p2));
14301
- const speed1After = p1.velocity.length, speed2After = p2.velocity.length, keAfter = m1 * speed1After * speed1After + m2 * speed2After * speed2After;
14302
- if (keAfter > keBefore * energyCorrectionMinRatio) {
14303
- const correctionFactor = Math.sqrt(keBefore / keAfter);
14304
- if (Math.abs(correctionFactor - correctionFactorNeutral) > energyDriftThreshold) {
14305
- p1.velocity.length = speed1After * correctionFactor;
14306
- p2.velocity.length = speed2After * correctionFactor;
14307
- }
14262
+ loadOptions() {
14263
+ }
14264
+ needsPlugin() {
14265
+ return true;
14308
14266
  }
14309
- fixBounceSpeed(p1);
14310
- fixBounceSpeed(p2);
14311
14267
  }
14312
14268
 
14313
- function destroy(p1, p2) {
14314
- if (!p1.unbreakable && !p2.unbreakable) {
14315
- bounce(p1, p2);
14316
- }
14317
- const p1Radius = p1.getRadius(), p2Radius = p2.getRadius();
14318
- if (!p1Radius && p2Radius) {
14319
- p1.destroy();
14320
- }
14321
- else if (p1Radius && !p2Radius) {
14322
- p2.destroy();
14323
- }
14324
- else if (p1Radius && p2Radius) {
14325
- const deleteP = p1.getRadius() >= p2.getRadius() ? p2 : p1;
14326
- deleteP.destroy();
14327
- }
14269
+ async function loadParticlesLinksInteraction(engine) {
14270
+ engine.checkVersion("4.2.1");
14271
+ await engine.pluginManager.register((e) => {
14272
+ const pluginManager = e.pluginManager;
14273
+ ensureInteractivityPluginLoaded(e);
14274
+ pluginManager.addPlugin(new LinksPlugin(pluginManager));
14275
+ pluginManager.addInteractor?.("particlesLinks", container => {
14276
+ return Promise.resolve(new Linker(pluginManager, container));
14277
+ });
14278
+ });
14328
14279
  }
14329
14280
 
14330
- function resolveCollision(p1, p2, delta, pixelRatio) {
14331
- if (!p1.options.collisions || !p2.options.collisions) {
14332
- return;
14281
+ const polygonCache = new Map(), noOffset = 0;
14282
+ function getUnitPolygon(sides) {
14283
+ const cached = polygonCache.get(sides);
14284
+ if (cached) {
14285
+ return cached;
14333
14286
  }
14334
- switch (p1.options.collisions.mode) {
14335
- case CollisionMode.absorb: {
14336
- absorb(p1, p2, delta, pixelRatio);
14337
- break;
14287
+ const step = doublePI / sides, isOdd = !!(sides % double), baseAngle = (-Math.PI + (isOdd ? noOffset : step)) * half$2, verts = [];
14288
+ for (let i = 0; i < sides; i++) {
14289
+ const angle = baseAngle + i * step;
14290
+ verts[i] = {
14291
+ x: Math.cos(angle),
14292
+ y: Math.sin(angle),
14293
+ };
14294
+ }
14295
+ polygonCache.set(sides, verts);
14296
+ return verts;
14297
+ }
14298
+ function drawPolygon(data, side) {
14299
+ const { context, radius } = data, sides = side.count.numerator / side.count.denominator, verts = getUnitPolygon(sides);
14300
+ context.beginPath();
14301
+ for (let i = 0; i < verts.length; i++) {
14302
+ const vert = verts[i];
14303
+ if (!vert) {
14304
+ continue;
14338
14305
  }
14339
- case CollisionMode.bounce: {
14340
- bounce(p1, p2);
14341
- break;
14306
+ const x = vert.x * radius, y = vert.y * radius;
14307
+ if (i) {
14308
+ context.lineTo(x, y);
14342
14309
  }
14343
- case CollisionMode.destroy: {
14344
- destroy(p1, p2);
14345
- break;
14310
+ else {
14311
+ context.moveTo(x, y);
14346
14312
  }
14347
14313
  }
14314
+ context.closePath();
14348
14315
  }
14349
14316
 
14350
- class Collider extends ParticlesInteractorBase {
14351
- maxDistance;
14352
- constructor(container) {
14353
- super(container);
14354
- this.maxDistance = 0;
14355
- }
14356
- clear() {
14357
- }
14358
- init() {
14359
- }
14360
- interact(p1, _interactivityData, delta) {
14361
- if (p1.destroyed || p1.spawning) {
14362
- return;
14363
- }
14364
- const container = this.container, pos1 = p1.getPosition(), radius1 = p1.getRadius(), query = container.particles.grid.queryCircle(pos1, radius1 * double);
14365
- for (const p2 of query) {
14366
- if (p1 === p2 ||
14367
- p1.id >= p2.id ||
14368
- !p1.options.collisions?.enable ||
14369
- !p2.options.collisions?.enable ||
14370
- p1.options.collisions.mode !== p2.options.collisions.mode ||
14371
- p2.destroyed ||
14372
- p2.spawning) {
14373
- continue;
14374
- }
14375
- const pos2 = p2.getPosition(), radius2 = p2.getRadius();
14376
- if (Math.abs(Math.round(pos1.z) - Math.round(pos2.z)) > radius1 + radius2) {
14377
- continue;
14378
- }
14379
- const dist = getDistance(pos1, pos2), distP = radius1 + radius2;
14380
- if (dist > distP) {
14381
- continue;
14382
- }
14383
- resolveCollision(p1, p2, delta, container.retina.pixelRatio);
14384
- }
14385
- }
14386
- isEnabled(particle) {
14387
- return !!particle.options.collisions?.enable;
14388
- }
14389
- loadParticlesOptions(options, ...sources) {
14390
- loadOptionProperty(options, "collisions", Collisions, ...sources);
14317
+ const defaultSides$3 = 5;
14318
+ class PolygonDrawerBase {
14319
+ draw(data) {
14320
+ const { particle, radius } = data, side = this.getSidesData(particle, radius);
14321
+ drawPolygon(data, side);
14391
14322
  }
14392
- reset() {
14323
+ getSidesCount(particle) {
14324
+ const polygon = particle.shapeData;
14325
+ return Math.round(getRangeValue(polygon?.sides ?? defaultSides$3));
14393
14326
  }
14394
14327
  }
14395
14328
 
14396
- class OverlapPlugin {
14397
- id = "overlap";
14398
- async getPlugin(container) {
14399
- const { OverlapPluginInstance } = await Promise.resolve().then(function () { return OverlapPluginInstance$1; });
14400
- return new OverlapPluginInstance(container);
14329
+ const yFactor$1 = 2.66, sidesFactor$1 = 3;
14330
+ class PolygonDrawer extends PolygonDrawerBase {
14331
+ getSidesData(particle, radius) {
14332
+ const { sides } = particle;
14333
+ return {
14334
+ count: {
14335
+ denominator: 1,
14336
+ numerator: sides,
14337
+ },
14338
+ length: (radius * yFactor$1) / (sides / sidesFactor$1),
14339
+ };
14401
14340
  }
14402
- loadOptions() {
14341
+ }
14342
+
14343
+ const sides$1 = 3, yFactor = 2.66, sidesFactor = 3;
14344
+ class TriangleDrawer extends PolygonDrawerBase {
14345
+ getSidesCount() {
14346
+ return sides$1;
14403
14347
  }
14404
- needsPlugin() {
14405
- return true;
14348
+ getSidesData(_particle, radius) {
14349
+ return {
14350
+ count: {
14351
+ denominator: 1,
14352
+ numerator: sides$1,
14353
+ },
14354
+ length: (radius * yFactor) / (sides$1 / sidesFactor),
14355
+ };
14406
14356
  }
14407
14357
  }
14408
14358
 
14409
- async function loadParticlesCollisionsInteraction(engine) {
14359
+ async function loadGenericPolygonShape(engine) {
14410
14360
  engine.checkVersion("4.2.1");
14411
- await engine.pluginManager.register((e) => {
14412
- ensureInteractivityPluginLoaded(e);
14413
- e.pluginManager.addPlugin(new OverlapPlugin());
14414
- e.pluginManager.addInteractor?.("particlesCollisions", container => {
14415
- return Promise.resolve(new Collider(container));
14416
- });
14361
+ await engine.pluginManager.register(e => {
14362
+ e.pluginManager.addShape(["polygon"], () => Promise.resolve(new PolygonDrawer()));
14417
14363
  });
14418
14364
  }
14419
-
14420
- class CircleWarp extends Circle {
14421
- #canvasSize;
14422
- constructor(x, y, radius, canvasSize) {
14423
- super(x, y, radius);
14424
- this.#canvasSize = canvasSize;
14425
- }
14426
- contains(point) {
14427
- if (super.contains(point))
14428
- return true;
14429
- const { width, height } = this.#canvasSize, { x, y } = point;
14430
- return (super.contains({ x: x - width, y }) ||
14431
- super.contains({ x: x + width, y }) ||
14432
- super.contains({ x, y: y - height }) ||
14433
- super.contains({ x, y: y + height }) ||
14434
- super.contains({ x: x - width, y: y - height }) ||
14435
- super.contains({ x: x + width, y: y + height }) ||
14436
- super.contains({ x: x - width, y: y + height }) ||
14437
- super.contains({ x: x + width, y: y - height }));
14438
- }
14439
- intersects(range) {
14440
- if (super.intersects(range))
14441
- return true;
14442
- const { width, height } = this.#canvasSize, pos = range.position, shifts = [
14443
- { x: -width, y: 0 },
14444
- { x: width, y: 0 },
14445
- { x: 0, y: -height },
14446
- { x: 0, y: height },
14447
- { x: -width, y: -height },
14448
- { x: width, y: height },
14449
- { x: -width, y: height },
14450
- { x: width, y: -height },
14451
- ];
14452
- for (const shift of shifts) {
14453
- const shiftedPos = { x: pos.x + shift.x, y: pos.y + shift.y };
14454
- let shiftedRange;
14455
- if (range instanceof Circle) {
14456
- shiftedRange = new Circle(shiftedPos.x, shiftedPos.y, range.radius);
14457
- }
14458
- else {
14459
- const rect = range;
14460
- shiftedRange = new Rectangle(shiftedPos.x, shiftedPos.y, rect.size.width, rect.size.height);
14461
- }
14462
- if (super.intersects(shiftedRange))
14463
- return true;
14464
- }
14465
- return false;
14466
- }
14365
+ async function loadTriangleShape(engine) {
14366
+ engine.checkVersion("4.2.1");
14367
+ await engine.pluginManager.register(e => {
14368
+ e.pluginManager.addShape(["triangle"], () => Promise.resolve(new TriangleDrawer()));
14369
+ });
14467
14370
  }
14468
-
14469
- class LinksShadow {
14470
- blur = 5;
14471
- color = new OptionsColor();
14472
- enable = false;
14473
- constructor() {
14474
- this.color.value = "#000";
14475
- }
14476
- load(data) {
14477
- if (isNull(data)) {
14478
- return;
14479
- }
14480
- loadProperty(this, "blur", data.blur);
14481
- this.color = OptionsColor.create(this.color, data.color);
14482
- loadProperty(this, "enable", data.enable);
14483
- }
14371
+ async function loadPolygonShape(engine) {
14372
+ engine.checkVersion("4.2.1");
14373
+ await Promise.all([
14374
+ loadGenericPolygonShape(engine),
14375
+ loadTriangleShape(engine),
14376
+ ]);
14484
14377
  }
14485
14378
 
14486
- class LinksTriangle {
14487
- color;
14379
+ class RotateAnimation {
14380
+ decay = 0;
14488
14381
  enable = false;
14489
- frequency = 1;
14490
- opacity;
14382
+ speed = 0;
14383
+ sync = false;
14491
14384
  load(data) {
14492
14385
  if (isNull(data)) {
14493
14386
  return;
14494
14387
  }
14495
- if (data.color !== undefined) {
14496
- this.color = OptionsColor.create(this.color, data.color);
14497
- }
14498
14388
  loadProperty(this, "enable", data.enable);
14499
- loadProperty(this, "frequency", data.frequency);
14500
- loadProperty(this, "opacity", data.opacity);
14389
+ loadRangeProperty(this, "speed", data.speed);
14390
+ loadRangeProperty(this, "decay", data.decay);
14391
+ loadProperty(this, "sync", data.sync);
14501
14392
  }
14502
14393
  }
14503
14394
 
14504
- class Links {
14505
- blink = false;
14506
- color = new OptionsColor();
14507
- consent = false;
14508
- distance = 100;
14509
- enable = false;
14510
- frequency = 1;
14511
- id;
14512
- opacity = 1;
14513
- shadow = new LinksShadow();
14514
- triangles = new LinksTriangle();
14515
- warp = false;
14516
- width = 1;
14517
- constructor() {
14518
- this.color.value = "#fff";
14519
- }
14395
+ class Rotate extends ValueWithRandom {
14396
+ animation = new RotateAnimation();
14397
+ direction = exports.RotateDirection.clockwise;
14398
+ path = false;
14520
14399
  load(data) {
14521
14400
  if (isNull(data)) {
14522
14401
  return;
14523
14402
  }
14524
- loadProperty(this, "id", data.id);
14525
- loadProperty(this, "blink", data.blink);
14526
- this.color = OptionsColor.create(this.color, data.color);
14527
- loadProperty(this, "consent", data.consent);
14528
- loadProperty(this, "distance", data.distance);
14529
- loadProperty(this, "enable", data.enable);
14530
- loadProperty(this, "frequency", data.frequency);
14531
- loadProperty(this, "opacity", data.opacity);
14532
- this.shadow.load(data.shadow);
14533
- this.triangles.load(data.triangles);
14534
- loadProperty(this, "width", data.width);
14535
- loadProperty(this, "warp", data.warp);
14403
+ super.load(data);
14404
+ loadProperty(this, "direction", data.direction);
14405
+ this.animation.load(data.animation);
14406
+ loadProperty(this, "path", data.path);
14536
14407
  }
14537
14408
  }
14538
14409
 
14539
- const opacityOffset = 1, minDistance$1 = 0;
14540
- function getWarpDistance(pos1, pos2, canvasSize) {
14541
- const { dx, dy } = getDistances(pos1, pos2), absDiffs = { x: Math.abs(dx), y: Math.abs(dy) }, warpDistances = {
14542
- x: Math.min(absDiffs.x, canvasSize.width - absDiffs.x),
14543
- y: Math.min(absDiffs.y, canvasSize.height - absDiffs.y),
14544
- };
14545
- return Math.hypot(warpDistances.x, warpDistances.y);
14546
- }
14547
- class Linker extends ParticlesInteractorBase {
14548
- #maxDistance;
14549
- #pluginManager;
14550
- constructor(pluginManager, container) {
14551
- super(container);
14552
- this.#pluginManager = pluginManager;
14553
- this.#maxDistance = 0;
14554
- }
14555
- get maxDistance() {
14556
- return this.#maxDistance;
14557
- }
14558
- clear() {
14559
- }
14560
- init() {
14561
- this.container.particles.linksColor = undefined;
14562
- this.container.particles.linksColors = new Map();
14410
+ const doublePIDeg = 360;
14411
+ class RotateUpdater {
14412
+ #container;
14413
+ constructor(container) {
14414
+ this.#container = container;
14563
14415
  }
14564
- interact(p1) {
14565
- if (!p1.options.links) {
14416
+ init(particle) {
14417
+ const rotateOptions = particle.options.rotate;
14418
+ if (!rotateOptions) {
14566
14419
  return;
14567
14420
  }
14568
- p1.links = [];
14569
- if (p1.linksDistance && p1.linksDistance > this.#maxDistance) {
14570
- this.#maxDistance = p1.linksDistance;
14421
+ particle.rotate = {
14422
+ enable: rotateOptions.animation.enable,
14423
+ value: degToRad(getRangeValue(rotateOptions.value)),
14424
+ min: 0,
14425
+ max: doublePI,
14426
+ };
14427
+ particle.pathRotation = rotateOptions.path;
14428
+ let rotateDirection = rotateOptions.direction;
14429
+ if (rotateDirection === exports.RotateDirection.random) {
14430
+ const index = Math.floor(getRandom() * double), minIndex = 0;
14431
+ rotateDirection = index > minIndex ? exports.RotateDirection.counterClockwise : exports.RotateDirection.clockwise;
14571
14432
  }
14572
- const pos1 = p1.getPosition(), container = this.container, canvasSize = container.canvas.size;
14573
- if (pos1.x < originPoint.x || pos1.y < originPoint.y || pos1.x > canvasSize.width || pos1.y > canvasSize.height) {
14574
- return;
14433
+ switch (rotateDirection) {
14434
+ case exports.RotateDirection.counterClockwise:
14435
+ case "counterClockwise":
14436
+ particle.rotate.status = exports.AnimationStatus.decreasing;
14437
+ break;
14438
+ case exports.RotateDirection.clockwise:
14439
+ particle.rotate.status = exports.AnimationStatus.increasing;
14440
+ break;
14575
14441
  }
14576
- const linkOpt1 = p1.options.links, optOpacity = linkOpt1.opacity, optDistance = p1.retina.linksDistance ?? minDistance$1, warp = linkOpt1.warp, range = warp ? new CircleWarp(pos1.x, pos1.y, optDistance, canvasSize) : new Circle(pos1.x, pos1.y, optDistance), query = container.particles.grid.query(range);
14577
- for (const p2 of query) {
14578
- const linkOpt2 = p2.options.links;
14579
- if (p1 === p2 ||
14580
- !linkOpt2?.enable ||
14581
- linkOpt1.id !== linkOpt2.id ||
14582
- p2.spawning ||
14583
- p2.destroyed ||
14584
- !p2.links ||
14585
- p1.links.some(t => t.destination === p2) ||
14586
- p2.links.some(t => t.destination === p1)) {
14587
- continue;
14588
- }
14589
- const pos2 = p2.getPosition();
14590
- if (pos2.x < originPoint.x || pos2.y < originPoint.y || pos2.x > canvasSize.width || pos2.y > canvasSize.height) {
14591
- continue;
14592
- }
14593
- const distDirect = getDistances(pos1, pos2).distance, distWarp = warp && linkOpt2.warp ? getWarpDistance(pos1, pos2, canvasSize) : distDirect, distance = Math.min(distDirect, distWarp);
14594
- if (distance > optDistance) {
14595
- continue;
14442
+ const rotateAnimation = rotateOptions.animation;
14443
+ if (rotateAnimation.enable) {
14444
+ particle.rotate.decay = identity$3 - getRangeValue(rotateAnimation.decay);
14445
+ particle.rotate.velocity =
14446
+ (getRangeValue(rotateAnimation.speed) / doublePIDeg) * this.#container.retina.reduceFactor;
14447
+ if (!rotateAnimation.sync) {
14448
+ particle.rotate.velocity *= getRandom();
14596
14449
  }
14597
- const opacityLine = (opacityOffset - distance / optDistance) * optOpacity;
14598
- this.#setColor(p1);
14599
- p1.links.push({
14600
- destination: p2,
14601
- opacity: opacityLine,
14602
- color: this.#getLinkColor(p1, p2),
14603
- isWarped: distWarp < distDirect,
14604
- });
14605
14450
  }
14451
+ particle.rotation = particle.rotate.value;
14606
14452
  }
14607
14453
  isEnabled(particle) {
14608
- return !!particle.options.links?.enable;
14609
- }
14610
- loadParticlesOptions(options, ...sources) {
14611
- loadOptionProperty(options, "links", Links, ...sources);
14454
+ const rotate = particle.options.rotate;
14455
+ if (!rotate) {
14456
+ return false;
14457
+ }
14458
+ return !particle.destroyed && !particle.spawning && (!!rotate.value || rotate.animation.enable || rotate.path);
14612
14459
  }
14613
- reset() {
14460
+ loadOptions(options, ...sources) {
14461
+ loadOptionProperty(options, "rotate", Rotate, ...sources);
14614
14462
  }
14615
- #getLinkColor(p1, p2) {
14616
- const container = this.container, linksOptions = p1.options.links;
14617
- if (!linksOptions) {
14463
+ update(particle, delta) {
14464
+ if (!this.isEnabled(particle)) {
14618
14465
  return;
14619
14466
  }
14620
- const linkColor = linksOptions.id !== undefined
14621
- ? container.particles.linksColors.get(linksOptions.id)
14622
- : container.particles.linksColor;
14623
- return getLinkColor(p1, p2, linkColor);
14624
- }
14625
- #setColor(p1) {
14626
- if (!p1.options.links) {
14627
- return;
14628
- }
14629
- const container = this.container, linksOptions = p1.options.links;
14630
- let linkColor = linksOptions.id === undefined
14631
- ? container.particles.linksColor
14632
- : container.particles.linksColors.get(linksOptions.id);
14633
- if (linkColor) {
14634
- return;
14635
- }
14636
- linkColor = getLinkRandomColor(this.#pluginManager, linksOptions.color, linksOptions.blink, linksOptions.consent);
14637
- if (linksOptions.id === undefined) {
14638
- container.particles.linksColor = linkColor;
14639
- }
14640
- else {
14641
- container.particles.linksColors.set(linksOptions.id, linkColor);
14642
- }
14643
- }
14644
- }
14645
-
14646
- class LinksPlugin {
14647
- id = "links";
14648
- #pluginManager;
14649
- constructor(pluginManager) {
14650
- this.#pluginManager = pluginManager;
14651
- }
14652
- async getPlugin(container) {
14653
- const { LinkInstance } = await Promise.resolve().then(function () { return LinkInstance$1; });
14654
- return new LinkInstance(this.#pluginManager, container);
14655
- }
14656
- loadOptions() {
14657
- }
14658
- needsPlugin() {
14659
- return true;
14660
- }
14661
- }
14662
-
14663
- async function loadParticlesLinksInteraction(engine) {
14664
- engine.checkVersion("4.2.1");
14665
- await engine.pluginManager.register((e) => {
14666
- const pluginManager = e.pluginManager;
14667
- ensureInteractivityPluginLoaded(e);
14668
- pluginManager.addPlugin(new LinksPlugin(pluginManager));
14669
- pluginManager.addInteractor?.("particlesLinks", container => {
14670
- return Promise.resolve(new Linker(pluginManager, container));
14671
- });
14672
- });
14673
- }
14674
-
14675
- const polygonCache = new Map(), noOffset = 0;
14676
- function getUnitPolygon(sides) {
14677
- const cached = polygonCache.get(sides);
14678
- if (cached) {
14679
- return cached;
14680
- }
14681
- const step = doublePI / sides, isOdd = !!(sides % double), baseAngle = (-Math.PI + (isOdd ? noOffset : step)) * half$2, verts = [];
14682
- for (let i = 0; i < sides; i++) {
14683
- const angle = baseAngle + i * step;
14684
- verts[i] = {
14685
- x: Math.cos(angle),
14686
- y: Math.sin(angle),
14687
- };
14688
- }
14689
- polygonCache.set(sides, verts);
14690
- return verts;
14691
- }
14692
- function drawPolygon(data, side) {
14693
- const { context, radius } = data, sides = side.count.numerator / side.count.denominator, verts = getUnitPolygon(sides);
14694
- context.beginPath();
14695
- for (let i = 0; i < verts.length; i++) {
14696
- const vert = verts[i];
14697
- if (!vert) {
14698
- continue;
14699
- }
14700
- const x = vert.x * radius, y = vert.y * radius;
14701
- if (i) {
14702
- context.lineTo(x, y);
14703
- }
14704
- else {
14705
- context.moveTo(x, y);
14706
- }
14707
- }
14708
- context.closePath();
14709
- }
14710
-
14711
- const defaultSides$3 = 5;
14712
- class PolygonDrawerBase {
14713
- draw(data) {
14714
- const { particle, radius } = data, side = this.getSidesData(particle, radius);
14715
- drawPolygon(data, side);
14716
- }
14717
- getSidesCount(particle) {
14718
- const polygon = particle.shapeData;
14719
- return Math.round(getRangeValue(polygon?.sides ?? defaultSides$3));
14720
- }
14721
- }
14722
-
14723
- const yFactor$1 = 2.66, sidesFactor$1 = 3;
14724
- class PolygonDrawer extends PolygonDrawerBase {
14725
- getSidesData(particle, radius) {
14726
- const { sides } = particle;
14727
- return {
14728
- count: {
14729
- denominator: 1,
14730
- numerator: sides,
14731
- },
14732
- length: (radius * yFactor$1) / (sides / sidesFactor$1),
14733
- };
14734
- }
14735
- }
14736
-
14737
- const sides$1 = 3, yFactor = 2.66, sidesFactor = 3;
14738
- class TriangleDrawer extends PolygonDrawerBase {
14739
- getSidesCount() {
14740
- return sides$1;
14741
- }
14742
- getSidesData(_particle, radius) {
14743
- return {
14744
- count: {
14745
- denominator: 1,
14746
- numerator: sides$1,
14747
- },
14748
- length: (radius * yFactor) / (sides$1 / sidesFactor),
14749
- };
14750
- }
14751
- }
14752
-
14753
- async function loadGenericPolygonShape(engine) {
14754
- engine.checkVersion("4.2.1");
14755
- await engine.pluginManager.register(e => {
14756
- e.pluginManager.addShape(["polygon"], () => Promise.resolve(new PolygonDrawer()));
14757
- });
14758
- }
14759
- async function loadTriangleShape(engine) {
14760
- engine.checkVersion("4.2.1");
14761
- await engine.pluginManager.register(e => {
14762
- e.pluginManager.addShape(["triangle"], () => Promise.resolve(new TriangleDrawer()));
14763
- });
14764
- }
14765
- async function loadPolygonShape(engine) {
14766
- engine.checkVersion("4.2.1");
14767
- await Promise.all([
14768
- loadGenericPolygonShape(engine),
14769
- loadTriangleShape(engine),
14770
- ]);
14771
- }
14772
-
14773
- class RotateAnimation {
14774
- decay = 0;
14775
- enable = false;
14776
- speed = 0;
14777
- sync = false;
14778
- load(data) {
14779
- if (isNull(data)) {
14780
- return;
14781
- }
14782
- loadProperty(this, "enable", data.enable);
14783
- loadRangeProperty(this, "speed", data.speed);
14784
- loadRangeProperty(this, "decay", data.decay);
14785
- loadProperty(this, "sync", data.sync);
14786
- }
14787
- }
14788
-
14789
- class Rotate extends ValueWithRandom {
14790
- animation = new RotateAnimation();
14791
- direction = exports.RotateDirection.clockwise;
14792
- path = false;
14793
- load(data) {
14794
- if (isNull(data)) {
14795
- return;
14796
- }
14797
- super.load(data);
14798
- loadProperty(this, "direction", data.direction);
14799
- this.animation.load(data.animation);
14800
- loadProperty(this, "path", data.path);
14801
- }
14802
- }
14803
-
14804
- const doublePIDeg = 360;
14805
- class RotateUpdater {
14806
- #container;
14807
- constructor(container) {
14808
- this.#container = container;
14809
- }
14810
- init(particle) {
14811
- const rotateOptions = particle.options.rotate;
14812
- if (!rotateOptions) {
14813
- return;
14814
- }
14815
- particle.rotate = {
14816
- enable: rotateOptions.animation.enable,
14817
- value: degToRad(getRangeValue(rotateOptions.value)),
14818
- min: 0,
14819
- max: doublePI,
14820
- };
14821
- particle.pathRotation = rotateOptions.path;
14822
- let rotateDirection = rotateOptions.direction;
14823
- if (rotateDirection === exports.RotateDirection.random) {
14824
- const index = Math.floor(getRandom() * double), minIndex = 0;
14825
- rotateDirection = index > minIndex ? exports.RotateDirection.counterClockwise : exports.RotateDirection.clockwise;
14826
- }
14827
- switch (rotateDirection) {
14828
- case exports.RotateDirection.counterClockwise:
14829
- case "counterClockwise":
14830
- particle.rotate.status = exports.AnimationStatus.decreasing;
14831
- break;
14832
- case exports.RotateDirection.clockwise:
14833
- particle.rotate.status = exports.AnimationStatus.increasing;
14834
- break;
14835
- }
14836
- const rotateAnimation = rotateOptions.animation;
14837
- if (rotateAnimation.enable) {
14838
- particle.rotate.decay = identity$3 - getRangeValue(rotateAnimation.decay);
14839
- particle.rotate.velocity =
14840
- (getRangeValue(rotateAnimation.speed) / doublePIDeg) * this.#container.retina.reduceFactor;
14841
- if (!rotateAnimation.sync) {
14842
- particle.rotate.velocity *= getRandom();
14843
- }
14844
- }
14845
- particle.rotation = particle.rotate.value;
14846
- }
14847
- isEnabled(particle) {
14848
- const rotate = particle.options.rotate;
14849
- if (!rotate) {
14850
- return false;
14851
- }
14852
- return !particle.destroyed && !particle.spawning && (!!rotate.value || rotate.animation.enable || rotate.path);
14853
- }
14854
- loadOptions(options, ...sources) {
14855
- loadOptionProperty(options, "rotate", Rotate, ...sources);
14856
- }
14857
- update(particle, delta) {
14858
- if (!this.isEnabled(particle)) {
14859
- return;
14860
- }
14861
- particle.isRotating = !!particle.rotate;
14862
- if (!particle.rotate) {
14467
+ particle.isRotating = !!particle.rotate;
14468
+ if (!particle.rotate) {
14863
14469
  return;
14864
14470
  }
14865
14471
  updateAnimation(particle, particle.rotate, false, exports.DestroyType.none, delta);
@@ -14973,7 +14579,7 @@
14973
14579
  }
14974
14580
 
14975
14581
  const validTypes = ["text", "character", "char", "multiline-text"];
14976
- const firstIndex$2 = 0, minLength$2 = 0;
14582
+ const firstIndex$3 = 0, minLength$2 = 0;
14977
14583
  function drawText(data) {
14978
14584
  const { context, particle, fill, stroke, radius, opacity } = data, character = particle.shapeData;
14979
14585
  if (!character) {
@@ -14983,7 +14589,7 @@
14983
14589
  particle.textLines ??= itemFromSingleOrMultiple(textData, particle.randomIndexData)?.split("\n") ?? [];
14984
14590
  particle.maxTextLength ??= particle.textLines.length
14985
14591
  ? Math.max(...particle.textLines.map(t => t.length))
14986
- : (particle.textLines[firstIndex$2]?.length ?? minLength$2);
14592
+ : (particle.textLines[firstIndex$3]?.length ?? minLength$2);
14987
14593
  if (!particle.textLines.length || !particle.maxTextLength) {
14988
14594
  return;
14989
14595
  }
@@ -15013,7 +14619,7 @@
15013
14619
  }
15014
14620
  }
15015
14621
 
15016
- const firstIndex$1 = 0, minLength$1 = 0;
14622
+ const firstIndex$2 = 0, minLength$1 = 0;
15017
14623
  class TextDrawer {
15018
14624
  draw(data) {
15019
14625
  drawText(data);
@@ -15045,7 +14651,7 @@
15045
14651
  particle.textLines = itemFromSingleOrMultiple(textData, particle.randomIndexData)?.split("\n") ?? [];
15046
14652
  particle.maxTextLength = particle.textLines.length
15047
14653
  ? Math.max(...particle.textLines.map(t => t.length))
15048
- : (particle.textLines[firstIndex$1]?.length ?? minLength$1);
14654
+ : (particle.textLines[firstIndex$2]?.length ?? minLength$1);
15049
14655
  }
15050
14656
  }
15051
14657
 
@@ -15143,263 +14749,792 @@
15143
14749
  }
15144
14750
  }
15145
14751
  }
15146
- isEnabled(particle) {
15147
- const tiltAnimation = particle.options.tilt?.animation;
15148
- return !particle.destroyed && !particle.spawning && !!tiltAnimation?.enable;
15149
- }
15150
- loadOptions(options, ...sources) {
15151
- loadOptionProperty(options, "tilt", Tilt, ...sources);
15152
- }
15153
- update(particle, delta) {
15154
- if (!this.isEnabled(particle) || !particle.tilt) {
15155
- return;
15156
- }
15157
- updateAnimation(particle, particle.tilt, false, exports.DestroyType.none, delta);
15158
- }
15159
- }
15160
-
15161
- async function loadTiltUpdater(engine) {
15162
- engine.checkVersion("4.2.1");
15163
- await engine.pluginManager.register(e => {
15164
- e.pluginManager.addParticleUpdater("tilt", container => {
15165
- return Promise.resolve(new TiltUpdater(container));
15166
- });
15167
- });
14752
+ isEnabled(particle) {
14753
+ const tiltAnimation = particle.options.tilt?.animation;
14754
+ return !particle.destroyed && !particle.spawning && !!tiltAnimation?.enable;
14755
+ }
14756
+ loadOptions(options, ...sources) {
14757
+ loadOptionProperty(options, "tilt", Tilt, ...sources);
14758
+ }
14759
+ update(particle, delta) {
14760
+ if (!this.isEnabled(particle) || !particle.tilt) {
14761
+ return;
14762
+ }
14763
+ updateAnimation(particle, particle.tilt, false, exports.DestroyType.none, delta);
14764
+ }
14765
+ }
14766
+
14767
+ async function loadTiltUpdater(engine) {
14768
+ engine.checkVersion("4.2.1");
14769
+ await engine.pluginManager.register(e => {
14770
+ e.pluginManager.addParticleUpdater("tilt", container => {
14771
+ return Promise.resolve(new TiltUpdater(container));
14772
+ });
14773
+ });
14774
+ }
14775
+
14776
+ class TwinkleLinksValues {
14777
+ color;
14778
+ enable = false;
14779
+ frequency = 0.05;
14780
+ opacity = 1;
14781
+ load(data) {
14782
+ if (isNull(data)) {
14783
+ return;
14784
+ }
14785
+ if (data.color !== undefined) {
14786
+ this.color = OptionsColor.create(this.color, data.color);
14787
+ }
14788
+ loadProperty(this, "enable", data.enable);
14789
+ loadProperty(this, "frequency", data.frequency);
14790
+ loadRangeProperty(this, "opacity", data.opacity);
14791
+ }
14792
+ }
14793
+
14794
+ class TwinkleParticlesValues {
14795
+ enable = false;
14796
+ fillColor;
14797
+ frequency = 0.05;
14798
+ opacity = 1;
14799
+ strokeColor;
14800
+ load(data) {
14801
+ if (isNull(data)) {
14802
+ return;
14803
+ }
14804
+ if (data.fillColor !== undefined) {
14805
+ this.fillColor = OptionsColor.create(this.fillColor, data.fillColor);
14806
+ }
14807
+ if (data.strokeColor !== undefined) {
14808
+ this.strokeColor = OptionsColor.create(this.strokeColor, data.strokeColor);
14809
+ }
14810
+ loadProperty(this, "enable", data.enable);
14811
+ loadProperty(this, "frequency", data.frequency);
14812
+ loadRangeProperty(this, "opacity", data.opacity);
14813
+ }
14814
+ }
14815
+
14816
+ class Twinkle {
14817
+ links = new TwinkleLinksValues();
14818
+ particles = new TwinkleParticlesValues();
14819
+ load(data) {
14820
+ if (isNull(data)) {
14821
+ return;
14822
+ }
14823
+ this.links.load(data.links);
14824
+ this.particles.load(data.particles);
14825
+ }
14826
+ }
14827
+
14828
+ class TwinkleUpdater {
14829
+ #container;
14830
+ #pluginManager;
14831
+ constructor(pluginManager, container) {
14832
+ this.#pluginManager = pluginManager;
14833
+ this.#container = container;
14834
+ }
14835
+ getColorStyles(particle, _context, _radius, opacity) {
14836
+ const pOptions = particle.options, container = this.#container, twinkleOptions = pOptions["twinkle"];
14837
+ if (!twinkleOptions) {
14838
+ return {};
14839
+ }
14840
+ const twinkle = twinkleOptions.particles, twinkling = twinkle.enable && getRandom() < twinkle.frequency, zIndexOptions = particle.options.zIndex, zOffset = 1, zOpacityFactor = (zOffset - particle.zIndexFactor) ** zIndexOptions.opacityRate, twinklingOpacity = twinkling ? getRangeValue(twinkle.opacity) * zOpacityFactor : opacity, twinkleFillRgb = rangeColorToHsl(this.#pluginManager, twinkle.fillColor), twinkleStrokeRgb = rangeColorToHsl(this.#pluginManager, twinkle.strokeColor), getTwinkleFillStyle = () => {
14841
+ if (!twinkleFillRgb) {
14842
+ return undefined;
14843
+ }
14844
+ return getStyleFromHsl(twinkleFillRgb, container.hdr, twinklingOpacity);
14845
+ }, getTwinkleStrokeStyle = () => {
14846
+ if (!twinkleStrokeRgb) {
14847
+ return undefined;
14848
+ }
14849
+ return getStyleFromHsl(twinkleStrokeRgb, container.hdr, twinklingOpacity);
14850
+ }, twinkleFillStyle = getTwinkleFillStyle(), twinkleStrokeStyle = getTwinkleStrokeStyle(), res = {}, needsTwinkle = twinkling && (!!twinkleFillStyle || !!twinkleStrokeStyle);
14851
+ res.fill = needsTwinkle ? twinkleFillStyle : undefined;
14852
+ res.stroke = needsTwinkle ? twinkleStrokeStyle : undefined;
14853
+ return res;
14854
+ }
14855
+ init() {
14856
+ }
14857
+ isEnabled(particle) {
14858
+ const pOptions = particle.options, twinkleOptions = pOptions.twinkle;
14859
+ if (!twinkleOptions) {
14860
+ return false;
14861
+ }
14862
+ return twinkleOptions.particles.enable;
14863
+ }
14864
+ loadOptions(options, ...sources) {
14865
+ loadOptionProperty(options, "twinkle", Twinkle, ...sources);
14866
+ }
14867
+ update() {
14868
+ }
14869
+ }
14870
+
14871
+ async function loadTwinkleUpdater(engine) {
14872
+ engine.checkVersion("4.2.1");
14873
+ await engine.pluginManager.register(e => {
14874
+ e.pluginManager.addParticleUpdater("twinkle", container => {
14875
+ return Promise.resolve(new TwinkleUpdater(e.pluginManager, container));
14876
+ });
14877
+ });
14878
+ }
14879
+
14880
+ class WobbleSpeed {
14881
+ angle = 50;
14882
+ move = 10;
14883
+ load(data) {
14884
+ if (isNull(data)) {
14885
+ return;
14886
+ }
14887
+ loadRangeProperty(this, "angle", data.angle);
14888
+ loadRangeProperty(this, "move", data.move);
14889
+ }
14890
+ }
14891
+
14892
+ class Wobble {
14893
+ distance = 5;
14894
+ enable = false;
14895
+ speed = new WobbleSpeed();
14896
+ load(data) {
14897
+ if (isNull(data)) {
14898
+ return;
14899
+ }
14900
+ loadRangeProperty(this, "distance", data.distance);
14901
+ loadProperty(this, "enable", data.enable);
14902
+ if (data.speed !== undefined) {
14903
+ if (isNumber(data.speed)) {
14904
+ this.speed.load({ angle: data.speed });
14905
+ }
14906
+ else {
14907
+ const rangeSpeed = data.speed;
14908
+ if ("min" in rangeSpeed) {
14909
+ this.speed.load({ angle: rangeSpeed });
14910
+ }
14911
+ else {
14912
+ this.speed.load(data.speed);
14913
+ }
14914
+ }
14915
+ }
14916
+ }
14917
+ }
14918
+
14919
+ const defaultDistance$1 = 0, distanceFactor = 60;
14920
+ function updateWobble(container, particle, delta) {
14921
+ const { wobble: wobbleOptions } = particle.options, { wobble } = particle;
14922
+ if (!wobbleOptions?.enable || !wobble) {
14923
+ return;
14924
+ }
14925
+ const reduceFactor = container.retina.reduceFactor, angleSpeed = wobble.angleSpeed * delta.factor * reduceFactor, moveSpeed = wobble.moveSpeed * delta.factor * reduceFactor, distance = (moveSpeed * (particle.retina.wobbleDistance ?? defaultDistance$1)) / (millisecondsToSeconds$1 / distanceFactor), max = doublePI, { position } = particle;
14926
+ wobble.angle += angleSpeed;
14927
+ if (wobble.angle > max) {
14928
+ wobble.angle -= max;
14929
+ }
14930
+ position.x += distance * Math.cos(wobble.angle);
14931
+ position.y += distance * Math.abs(Math.sin(wobble.angle));
14932
+ }
14933
+
14934
+ const maxAngle$1 = 360, moveSpeedFactor$1 = 10, defaultDistance = 0;
14935
+ class WobbleUpdater {
14936
+ #container;
14937
+ constructor(container) {
14938
+ this.#container = container;
14939
+ }
14940
+ init(particle) {
14941
+ const wobbleOpt = particle.options.wobble;
14942
+ if (wobbleOpt?.enable) {
14943
+ particle.wobble = {
14944
+ angle: getRandom() * doublePI,
14945
+ angleSpeed: getRangeValue(wobbleOpt.speed.angle) / maxAngle$1,
14946
+ moveSpeed: getRangeValue(wobbleOpt.speed.move) / moveSpeedFactor$1,
14947
+ };
14948
+ }
14949
+ else {
14950
+ particle.wobble = {
14951
+ angle: 0,
14952
+ angleSpeed: 0,
14953
+ moveSpeed: 0,
14954
+ };
14955
+ }
14956
+ particle.retina.wobbleDistance =
14957
+ getRangeValue(wobbleOpt?.distance ?? defaultDistance) * this.#container.retina.pixelRatio;
14958
+ }
14959
+ isEnabled(particle) {
14960
+ return !particle.destroyed && !particle.spawning && !!particle.options.wobble?.enable;
14961
+ }
14962
+ loadOptions(options, ...sources) {
14963
+ loadOptionProperty(options, "wobble", Wobble, ...sources);
14964
+ }
14965
+ update(particle, delta) {
14966
+ if (!this.isEnabled(particle)) {
14967
+ return;
14968
+ }
14969
+ updateWobble(this.#container, particle, delta);
14970
+ }
14971
+ }
14972
+
14973
+ async function loadWobbleUpdater(engine) {
14974
+ engine.checkVersion("4.2.1");
14975
+ await engine.pluginManager.register(e => {
14976
+ e.pluginManager.addParticleUpdater("wobble", container => {
14977
+ return Promise.resolve(new WobbleUpdater(container));
14978
+ });
14979
+ });
14980
+ }
14981
+
14982
+ async function loadFull(engine) {
14983
+ engine.checkVersion("4.2.1");
14984
+ await engine.pluginManager.register(async (e) => {
14985
+ const loadEmittersPluginBundle = async (e) => {
14986
+ await loadEmittersPlugin(e);
14987
+ await Promise.all([
14988
+ loadEmittersShapeCircle(e),
14989
+ loadEmittersShapeSquare(e),
14990
+ ]);
14991
+ }, loadInteractivityForFull = async (e) => {
14992
+ await loadSlim(e);
14993
+ await Promise.all([
14994
+ loadExternalDragInteraction(e),
14995
+ loadExternalTrailInteraction(e),
14996
+ loadAbsorbersPlugin(e),
14997
+ loadEmittersPluginBundle(e),
14998
+ ]);
14999
+ };
15000
+ await Promise.all([
15001
+ loadInteractivityForFull(e),
15002
+ loadDestroyUpdater(e),
15003
+ loadRollUpdater(e),
15004
+ loadTiltUpdater(e),
15005
+ loadTwinkleUpdater(e),
15006
+ loadWobbleUpdater(e),
15007
+ loadTextShape(e),
15008
+ ]);
15009
+ });
15010
+ }
15011
+
15012
+ const InterlaceOffsets = [0, 4, 2, 1];
15013
+ const InterlaceSteps = [8, 8, 4, 2];
15014
+
15015
+ class ByteStream {
15016
+ data;
15017
+ pos;
15018
+ constructor(bytes) {
15019
+ this.pos = 0;
15020
+ this.data = new Uint8ClampedArray(bytes);
15021
+ }
15022
+ getString(count) {
15023
+ const slice = this.data.slice(this.pos, this.pos + count);
15024
+ this.pos += slice.length;
15025
+ return slice.reduce((acc, curr) => acc + String.fromCharCode(curr), "");
15026
+ }
15027
+ nextByte() {
15028
+ return this.data[this.pos++];
15029
+ }
15030
+ nextTwoBytes() {
15031
+ const increment = 2, previous = 1, shift = 8;
15032
+ this.pos += increment;
15033
+ return this.data[this.pos - increment] + (this.data[this.pos - previous] << shift);
15034
+ }
15035
+ readSubBlocks() {
15036
+ let blockString = "", size;
15037
+ const minCount = 0, emptySize = 0;
15038
+ do {
15039
+ size = this.data[this.pos++];
15040
+ for (let count = size; --count >= minCount; blockString += String.fromCharCode(this.data[this.pos++])) {
15041
+ }
15042
+ } while (size !== emptySize);
15043
+ return blockString;
15044
+ }
15045
+ readSubBlocksBin() {
15046
+ let size = this.data[this.pos], len = 0;
15047
+ const emptySize = 0, increment = 1;
15048
+ for (let offset = 0; size !== emptySize; offset += size + increment, size = this.data[this.pos + offset]) {
15049
+ len += size;
15050
+ }
15051
+ const blockData = new Uint8Array(len);
15052
+ size = this.data[this.pos++];
15053
+ for (let i = 0; size !== emptySize; size = this.data[this.pos++]) {
15054
+ for (let count = size; --count >= emptySize; blockData[i++] = this.data[this.pos++]) {
15055
+ }
15056
+ }
15057
+ return blockData;
15058
+ }
15059
+ skipSubBlocks() {
15060
+ for (const increment = 1, noData = 0; this.data[this.pos] !== noData; this.pos += this.data[this.pos] + increment) {
15061
+ }
15062
+ this.pos++;
15063
+ }
15064
+ }
15065
+
15066
+ var DisposalMethod;
15067
+ (function (DisposalMethod) {
15068
+ DisposalMethod[DisposalMethod["Replace"] = 0] = "Replace";
15069
+ DisposalMethod[DisposalMethod["Combine"] = 1] = "Combine";
15070
+ DisposalMethod[DisposalMethod["RestoreBackground"] = 2] = "RestoreBackground";
15071
+ DisposalMethod[DisposalMethod["RestorePrevious"] = 3] = "RestorePrevious";
15072
+ DisposalMethod[DisposalMethod["UndefinedA"] = 4] = "UndefinedA";
15073
+ DisposalMethod[DisposalMethod["UndefinedB"] = 5] = "UndefinedB";
15074
+ DisposalMethod[DisposalMethod["UndefinedC"] = 6] = "UndefinedC";
15075
+ DisposalMethod[DisposalMethod["UndefinedD"] = 7] = "UndefinedD";
15076
+ })(DisposalMethod || (DisposalMethod = {}));
15077
+
15078
+ var GIFDataHeaders;
15079
+ (function (GIFDataHeaders) {
15080
+ GIFDataHeaders[GIFDataHeaders["Extension"] = 33] = "Extension";
15081
+ GIFDataHeaders[GIFDataHeaders["ApplicationExtension"] = 255] = "ApplicationExtension";
15082
+ GIFDataHeaders[GIFDataHeaders["GraphicsControlExtension"] = 249] = "GraphicsControlExtension";
15083
+ GIFDataHeaders[GIFDataHeaders["PlainTextExtension"] = 1] = "PlainTextExtension";
15084
+ GIFDataHeaders[GIFDataHeaders["CommentExtension"] = 254] = "CommentExtension";
15085
+ GIFDataHeaders[GIFDataHeaders["Image"] = 44] = "Image";
15086
+ GIFDataHeaders[GIFDataHeaders["EndOfFile"] = 59] = "EndOfFile";
15087
+ })(GIFDataHeaders || (GIFDataHeaders = {}));
15088
+
15089
+ const defaultFrame = 0, initialTime = 0, firstIndex$1 = 0, defaultLoopCount = 0;
15090
+ function parseColorTable(byteStream, count) {
15091
+ const colors = [];
15092
+ for (let i = 0; i < count; i++) {
15093
+ colors.push({
15094
+ r: byteStream.data[byteStream.pos],
15095
+ g: byteStream.data[byteStream.pos + 1],
15096
+ b: byteStream.data[byteStream.pos + 2],
15097
+ });
15098
+ byteStream.pos += 3;
15099
+ }
15100
+ return colors;
15101
+ }
15102
+ function parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex) {
15103
+ switch (byteStream.nextByte()) {
15104
+ case GIFDataHeaders.GraphicsControlExtension: {
15105
+ const frame = gif.frames[getFrameIndex(false)];
15106
+ byteStream.pos++;
15107
+ const packedByte = byteStream.nextByte();
15108
+ frame.GCreserved = (packedByte & 0xe0) >>> 5;
15109
+ frame.disposalMethod = (packedByte & 0x1c) >>> 2;
15110
+ frame.userInputDelayFlag = (packedByte & 2) === 2;
15111
+ const transparencyFlag = (packedByte & 1) === 1;
15112
+ frame.delayTime = byteStream.nextTwoBytes() * 0xa;
15113
+ const transparencyIndex = byteStream.nextByte();
15114
+ if (transparencyFlag) {
15115
+ getTransparencyIndex(transparencyIndex);
15116
+ }
15117
+ byteStream.pos++;
15118
+ break;
15119
+ }
15120
+ case GIFDataHeaders.ApplicationExtension: {
15121
+ byteStream.pos++;
15122
+ const applicationExtension = {
15123
+ identifier: byteStream.getString(8),
15124
+ authenticationCode: byteStream.getString(3),
15125
+ data: byteStream.readSubBlocksBin(),
15126
+ };
15127
+ gif.applicationExtensions.push(applicationExtension);
15128
+ break;
15129
+ }
15130
+ case GIFDataHeaders.CommentExtension: {
15131
+ gif.comments.push([getFrameIndex(false), byteStream.readSubBlocks()]);
15132
+ break;
15133
+ }
15134
+ case GIFDataHeaders.PlainTextExtension: {
15135
+ if (gif.globalColorTable.length === 0) {
15136
+ throw new EvalError("plain text extension without global color table");
15137
+ }
15138
+ byteStream.pos++;
15139
+ gif.frames[getFrameIndex(false)].plainTextData = {
15140
+ left: byteStream.nextTwoBytes(),
15141
+ top: byteStream.nextTwoBytes(),
15142
+ width: byteStream.nextTwoBytes(),
15143
+ height: byteStream.nextTwoBytes(),
15144
+ charSize: {
15145
+ width: byteStream.nextTwoBytes(),
15146
+ height: byteStream.nextTwoBytes(),
15147
+ },
15148
+ foregroundColor: byteStream.nextByte(),
15149
+ backgroundColor: byteStream.nextByte(),
15150
+ text: byteStream.readSubBlocks(),
15151
+ };
15152
+ break;
15153
+ }
15154
+ default:
15155
+ byteStream.skipSubBlocks();
15156
+ break;
15157
+ }
15158
+ }
15159
+ function readBits(imageData, pos, len) {
15160
+ const bytePos = pos >>> 3, bitPos = pos & 7;
15161
+ return (((imageData[bytePos] + (imageData[bytePos + 1] << 8) + (imageData[bytePos + 2] << 16)) &
15162
+ (((1 << len) - 1) << bitPos)) >>>
15163
+ bitPos);
15164
+ }
15165
+ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, canvasSettings, progressCallback) {
15166
+ const frame = gif.frames[getFrameIndex(true)];
15167
+ frame.left = byteStream.nextTwoBytes();
15168
+ frame.top = byteStream.nextTwoBytes();
15169
+ frame.width = byteStream.nextTwoBytes();
15170
+ frame.height = byteStream.nextTwoBytes();
15171
+ const packedByte = byteStream.nextByte(), localColorTableFlag = (packedByte & 0x80) === 0x80, interlacedFlag = (packedByte & 0x40) === 0x40;
15172
+ frame.sortFlag = (packedByte & 0x20) === 0x20;
15173
+ frame.reserved = (packedByte & 0x18) >>> 3;
15174
+ const localColorCount = 1 << ((packedByte & 7) + 1);
15175
+ if (localColorTableFlag) {
15176
+ frame.localColorTable = parseColorTable(byteStream, localColorCount);
15177
+ }
15178
+ const getColor = (index) => {
15179
+ const { r, g, b } = (localColorTableFlag ? frame.localColorTable : gif.globalColorTable)[index];
15180
+ if (index !== getTransparencyIndex(null)) {
15181
+ return { r, g, b, a: 255 };
15182
+ }
15183
+ return { r, g, b, a: avgAlpha ? Math.trunc((r + g + b) / 3) : 0 };
15184
+ }, image = (() => {
15185
+ try {
15186
+ return new ImageData(frame.width, frame.height, canvasSettings);
15187
+ }
15188
+ catch (error) {
15189
+ if (error instanceof DOMException && error.name === "IndexSizeError") {
15190
+ return null;
15191
+ }
15192
+ throw error;
15193
+ }
15194
+ })();
15195
+ if (image == null) {
15196
+ throw new EvalError("GIF frame size is to large");
15197
+ }
15198
+ const minCodeSize = byteStream.nextByte(), imageData = byteStream.readSubBlocksBin(), clearCode = 1 << minCodeSize;
15199
+ if (interlacedFlag) {
15200
+ for (let code = 0, size = minCodeSize + 1, pos = 0, dic = [[0]], pass = 0; pass < 4; pass++) {
15201
+ if (InterlaceOffsets[pass] < frame.height) {
15202
+ let pixelPos = 0, lineIndex = 0, exit = false;
15203
+ while (!exit) {
15204
+ const last = code;
15205
+ code = readBits(imageData, pos, size);
15206
+ pos += size + 1;
15207
+ if (code === clearCode) {
15208
+ size = minCodeSize + 1;
15209
+ dic.length = clearCode + 2;
15210
+ for (let i = 0; i < dic.length; i++) {
15211
+ dic[i] = i < clearCode ? [i] : [];
15212
+ }
15213
+ }
15214
+ else {
15215
+ if (code >= dic.length) {
15216
+ dic.push(dic[last].concat(dic[last][0]));
15217
+ }
15218
+ else if (last !== clearCode) {
15219
+ dic.push(dic[last].concat(dic[code][0]));
15220
+ }
15221
+ for (const item of dic[code]) {
15222
+ const { r, g, b, a } = getColor(item);
15223
+ image.data.set([r, g, b, a], InterlaceOffsets[pass] * frame.width +
15224
+ InterlaceSteps[pass] * lineIndex +
15225
+ (pixelPos % (frame.width * 4)));
15226
+ pixelPos += 4;
15227
+ }
15228
+ if (dic.length === 1 << size && size < 0xc) {
15229
+ size++;
15230
+ }
15231
+ }
15232
+ if (pixelPos === frame.width * 4 * (lineIndex + 1)) {
15233
+ lineIndex++;
15234
+ if (InterlaceOffsets[pass] + InterlaceSteps[pass] * lineIndex >= frame.height) {
15235
+ exit = true;
15236
+ }
15237
+ }
15238
+ }
15239
+ }
15240
+ }
15241
+ frame.image = image;
15242
+ frame.bitmap = await createImageBitmap(image);
15243
+ }
15244
+ else {
15245
+ let code = 0, size = minCodeSize + 1, pos = 0, pixelPos = -4;
15246
+ const dic = [[0]];
15247
+ for (;;) {
15248
+ const last = code;
15249
+ code = readBits(imageData, pos, size);
15250
+ pos += size;
15251
+ if (code === clearCode) {
15252
+ size = minCodeSize + 1;
15253
+ dic.length = clearCode + 2;
15254
+ for (let i = 0; i < dic.length; i++) {
15255
+ dic[i] = i < clearCode ? [i] : [];
15256
+ }
15257
+ }
15258
+ else {
15259
+ if (code === clearCode + 1) {
15260
+ break;
15261
+ }
15262
+ if (code >= dic.length) {
15263
+ dic.push(dic[last].concat(dic[last][0]));
15264
+ }
15265
+ else if (last !== clearCode) {
15266
+ dic.push(dic[last].concat(dic[code][0]));
15267
+ }
15268
+ for (const item of dic[code]) {
15269
+ const { r, g, b, a } = getColor(item);
15270
+ pixelPos += 4;
15271
+ image.data.set([r, g, b, a], pixelPos);
15272
+ }
15273
+ if (dic.length >= 1 << size && size < 0xc) {
15274
+ size++;
15275
+ }
15276
+ }
15277
+ }
15278
+ frame.image = image;
15279
+ frame.bitmap = await createImageBitmap(image);
15280
+ }
15168
15281
  }
15169
-
15170
- class TwinkleLinksValues {
15171
- color;
15172
- enable = false;
15173
- frequency = 0.05;
15174
- opacity = 1;
15175
- load(data) {
15176
- if (isNull(data)) {
15177
- return;
15178
- }
15179
- if (data.color !== undefined) {
15180
- this.color = OptionsColor.create(this.color, data.color);
15181
- }
15182
- loadProperty(this, "enable", data.enable);
15183
- loadProperty(this, "frequency", data.frequency);
15184
- loadRangeProperty(this, "opacity", data.opacity);
15282
+ async function parseBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, canvasSettings, progressCallback) {
15283
+ switch (byteStream.nextByte()) {
15284
+ case GIFDataHeaders.EndOfFile:
15285
+ return true;
15286
+ case GIFDataHeaders.Image:
15287
+ await parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, canvasSettings);
15288
+ break;
15289
+ case GIFDataHeaders.Extension:
15290
+ parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex);
15291
+ break;
15292
+ default:
15293
+ throw new EvalError("undefined block found");
15185
15294
  }
15295
+ return false;
15186
15296
  }
15187
-
15188
- class TwinkleParticlesValues {
15189
- enable = false;
15190
- fillColor;
15191
- frequency = 0.05;
15192
- opacity = 1;
15193
- strokeColor;
15194
- load(data) {
15195
- if (isNull(data)) {
15196
- return;
15197
- }
15198
- if (data.fillColor !== undefined) {
15199
- this.fillColor = OptionsColor.create(this.fillColor, data.fillColor);
15200
- }
15201
- if (data.strokeColor !== undefined) {
15202
- this.strokeColor = OptionsColor.create(this.strokeColor, data.strokeColor);
15297
+ function getGIFLoopAmount(gif) {
15298
+ for (const extension of gif.applicationExtensions) {
15299
+ if (extension.identifier + extension.authenticationCode !== "NETSCAPE2.0") {
15300
+ continue;
15203
15301
  }
15204
- loadProperty(this, "enable", data.enable);
15205
- loadProperty(this, "frequency", data.frequency);
15206
- loadRangeProperty(this, "opacity", data.opacity);
15302
+ return extension.data[1] + (extension.data[2] << 8);
15207
15303
  }
15304
+ return Number.NaN;
15208
15305
  }
15209
-
15210
- class Twinkle {
15211
- links = new TwinkleLinksValues();
15212
- particles = new TwinkleParticlesValues();
15213
- load(data) {
15214
- if (isNull(data)) {
15215
- return;
15216
- }
15217
- this.links.load(data.links);
15218
- this.particles.load(data.particles);
15306
+ async function decodeGIF(gifURL, canvasSettings, progressCallback, avgAlpha) {
15307
+ avgAlpha ??= false;
15308
+ const res = await fetch(gifURL);
15309
+ if (!res.ok && res.status === 404) {
15310
+ throw new EvalError("file not found");
15219
15311
  }
15220
- }
15221
-
15222
- class TwinkleUpdater {
15223
- #container;
15224
- #pluginManager;
15225
- constructor(pluginManager, container) {
15226
- this.#pluginManager = pluginManager;
15227
- this.#container = container;
15312
+ const buffer = await res.arrayBuffer(), gif = {
15313
+ width: 0,
15314
+ height: 0,
15315
+ totalTime: 0,
15316
+ colorRes: 0,
15317
+ pixelAspectRatio: 0,
15318
+ frames: [],
15319
+ sortFlag: false,
15320
+ globalColorTable: [],
15321
+ backgroundImage: new ImageData(1, 1, canvasSettings),
15322
+ comments: [],
15323
+ applicationExtensions: [],
15324
+ }, byteStream = new ByteStream(new Uint8ClampedArray(buffer));
15325
+ if (byteStream.getString(6) !== "GIF89a") {
15326
+ throw new Error("not a supported GIF file");
15228
15327
  }
15229
- getColorStyles(particle, _context, _radius, opacity) {
15230
- const pOptions = particle.options, container = this.#container, twinkleOptions = pOptions["twinkle"];
15231
- if (!twinkleOptions) {
15232
- return {};
15233
- }
15234
- const twinkle = twinkleOptions.particles, twinkling = twinkle.enable && getRandom() < twinkle.frequency, zIndexOptions = particle.options.zIndex, zOffset = 1, zOpacityFactor = (zOffset - particle.zIndexFactor) ** zIndexOptions.opacityRate, twinklingOpacity = twinkling ? getRangeValue(twinkle.opacity) * zOpacityFactor : opacity, twinkleFillRgb = rangeColorToHsl(this.#pluginManager, twinkle.fillColor), twinkleStrokeRgb = rangeColorToHsl(this.#pluginManager, twinkle.strokeColor), getTwinkleFillStyle = () => {
15235
- if (!twinkleFillRgb) {
15236
- return undefined;
15237
- }
15238
- return getStyleFromHsl(twinkleFillRgb, container.hdr, twinklingOpacity);
15239
- }, getTwinkleStrokeStyle = () => {
15240
- if (!twinkleStrokeRgb) {
15241
- return undefined;
15242
- }
15243
- return getStyleFromHsl(twinkleStrokeRgb, container.hdr, twinklingOpacity);
15244
- }, twinkleFillStyle = getTwinkleFillStyle(), twinkleStrokeStyle = getTwinkleStrokeStyle(), res = {}, needsTwinkle = twinkling && (!!twinkleFillStyle || !!twinkleStrokeStyle);
15245
- res.fill = needsTwinkle ? twinkleFillStyle : undefined;
15246
- res.stroke = needsTwinkle ? twinkleStrokeStyle : undefined;
15247
- return res;
15328
+ gif.width = byteStream.nextTwoBytes();
15329
+ gif.height = byteStream.nextTwoBytes();
15330
+ const packedByte = byteStream.nextByte(), globalColorTableFlag = (packedByte & 0x80) === 0x80;
15331
+ gif.colorRes = (packedByte & 0x70) >>> 4;
15332
+ gif.sortFlag = (packedByte & 8) === 8;
15333
+ const globalColorCount = 1 << ((packedByte & 7) + 1), backgroundColorIndex = byteStream.nextByte();
15334
+ gif.pixelAspectRatio = byteStream.nextByte();
15335
+ if (gif.pixelAspectRatio !== 0) {
15336
+ gif.pixelAspectRatio = (gif.pixelAspectRatio + 0xf) / 0x40;
15248
15337
  }
15249
- init() {
15338
+ if (globalColorTableFlag) {
15339
+ gif.globalColorTable = parseColorTable(byteStream, globalColorCount);
15250
15340
  }
15251
- isEnabled(particle) {
15252
- const pOptions = particle.options, twinkleOptions = pOptions.twinkle;
15253
- if (!twinkleOptions) {
15254
- return false;
15341
+ const backgroundImage = (() => {
15342
+ try {
15343
+ return new ImageData(gif.width, gif.height, canvasSettings);
15255
15344
  }
15256
- return twinkleOptions.particles.enable;
15257
- }
15258
- loadOptions(options, ...sources) {
15259
- loadOptionProperty(options, "twinkle", Twinkle, ...sources);
15345
+ catch (error) {
15346
+ if (error instanceof DOMException && error.name === "IndexSizeError") {
15347
+ return null;
15348
+ }
15349
+ throw error;
15350
+ }
15351
+ })();
15352
+ if (backgroundImage == null) {
15353
+ throw new Error("GIF frame size is to large");
15260
15354
  }
15261
- update() {
15355
+ const { r, g, b } = gif.globalColorTable[backgroundColorIndex];
15356
+ backgroundImage.data.set(globalColorTableFlag ? [r, g, b, 255] : [0, 0, 0, 0]);
15357
+ for (let i = 4; i < backgroundImage.data.length; i *= 2) {
15358
+ backgroundImage.data.copyWithin(i, 0, i);
15262
15359
  }
15263
- }
15264
-
15265
- async function loadTwinkleUpdater(engine) {
15266
- engine.checkVersion("4.2.1");
15267
- await engine.pluginManager.register(e => {
15268
- e.pluginManager.addParticleUpdater("twinkle", container => {
15269
- return Promise.resolve(new TwinkleUpdater(e.pluginManager, container));
15270
- });
15271
- });
15272
- }
15273
-
15274
- class WobbleSpeed {
15275
- angle = 50;
15276
- move = 10;
15277
- load(data) {
15278
- if (isNull(data)) {
15279
- return;
15360
+ gif.backgroundImage = backgroundImage;
15361
+ let frameIndex = -1, incrementFrameIndex = true, transparencyIndex = -1;
15362
+ const getframeIndex = (increment) => {
15363
+ if (increment) {
15364
+ incrementFrameIndex = true;
15280
15365
  }
15281
- loadRangeProperty(this, "angle", data.angle);
15282
- loadRangeProperty(this, "move", data.move);
15283
- }
15284
- }
15285
-
15286
- class Wobble {
15287
- distance = 5;
15288
- enable = false;
15289
- speed = new WobbleSpeed();
15290
- load(data) {
15291
- if (isNull(data)) {
15292
- return;
15366
+ return frameIndex;
15367
+ }, getTransparencyIndex = (newValue) => {
15368
+ if (newValue != null) {
15369
+ transparencyIndex = newValue;
15293
15370
  }
15294
- loadRangeProperty(this, "distance", data.distance);
15295
- loadProperty(this, "enable", data.enable);
15296
- if (data.speed !== undefined) {
15297
- if (isNumber(data.speed)) {
15298
- this.speed.load({ angle: data.speed });
15371
+ return transparencyIndex;
15372
+ };
15373
+ try {
15374
+ do {
15375
+ if (incrementFrameIndex) {
15376
+ gif.frames.push({
15377
+ left: 0,
15378
+ top: 0,
15379
+ width: 0,
15380
+ height: 0,
15381
+ disposalMethod: DisposalMethod.Replace,
15382
+ image: new ImageData(1, 1, canvasSettings),
15383
+ plainTextData: null,
15384
+ userInputDelayFlag: false,
15385
+ delayTime: 0,
15386
+ sortFlag: false,
15387
+ localColorTable: [],
15388
+ reserved: 0,
15389
+ GCreserved: 0,
15390
+ });
15391
+ frameIndex++;
15392
+ transparencyIndex = -1;
15393
+ incrementFrameIndex = false;
15299
15394
  }
15300
- else {
15301
- const rangeSpeed = data.speed;
15302
- if ("min" in rangeSpeed) {
15303
- this.speed.load({ angle: rangeSpeed });
15304
- }
15305
- else {
15306
- this.speed.load(data.speed);
15307
- }
15395
+ } while (!(await parseBlock(byteStream, gif, avgAlpha, getframeIndex, getTransparencyIndex, canvasSettings, progressCallback)));
15396
+ gif.frames.length--;
15397
+ for (const frame of gif.frames) {
15398
+ if (frame.userInputDelayFlag && frame.delayTime === 0) {
15399
+ gif.totalTime = Infinity;
15400
+ break;
15308
15401
  }
15402
+ gif.totalTime += frame.delayTime;
15403
+ }
15404
+ return gif;
15405
+ }
15406
+ catch (error) {
15407
+ if (error instanceof EvalError) {
15408
+ throw new Error(`error while parsing frame ${frameIndex.toString()} "${error.message}"`, { cause: error });
15309
15409
  }
15410
+ throw error;
15310
15411
  }
15311
15412
  }
15312
-
15313
- const defaultDistance$1 = 0, distanceFactor = 60;
15314
- function updateWobble(container, particle, delta) {
15315
- const { wobble: wobbleOptions } = particle.options, { wobble } = particle;
15316
- if (!wobbleOptions?.enable || !wobble) {
15413
+ function drawGif(data, canvasSettings) {
15414
+ const { context, radius, particle, delta } = data, gifData = particle.gifData;
15415
+ if (!gifData) {
15317
15416
  return;
15318
15417
  }
15319
- const reduceFactor = container.retina.reduceFactor, angleSpeed = wobble.angleSpeed * delta.factor * reduceFactor, moveSpeed = wobble.moveSpeed * delta.factor * reduceFactor, distance = (moveSpeed * (particle.retina.wobbleDistance ?? defaultDistance$1)) / (millisecondsToSeconds$1 / distanceFactor), max = doublePI, { position } = particle;
15320
- wobble.angle += angleSpeed;
15321
- if (wobble.angle > max) {
15322
- wobble.angle -= max;
15418
+ const offscreenCanvas = new OffscreenCanvas(gifData.width, gifData.height), offscreenContext = offscreenCanvas.getContext("2d", canvasSettings);
15419
+ if (!offscreenContext) {
15420
+ throw new Error("could not create offscreen canvas context");
15323
15421
  }
15324
- position.x += distance * Math.cos(wobble.angle);
15325
- position.y += distance * Math.abs(Math.sin(wobble.angle));
15422
+ offscreenContext.imageSmoothingQuality = "low";
15423
+ offscreenContext.imageSmoothingEnabled = false;
15424
+ offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
15425
+ let frameIndex = particle.gifFrame ?? defaultFrame;
15426
+ const pos = { x: -gifData.width * half$2, y: -gifData.height * half$2 }, frame = gifData.frames[frameIndex];
15427
+ particle.gifTime ??= initialTime;
15428
+ if (!frame.bitmap) {
15429
+ return;
15430
+ }
15431
+ context.scale(radius / gifData.width, radius / gifData.height);
15432
+ switch (frame.disposalMethod) {
15433
+ case DisposalMethod.UndefinedA:
15434
+ case DisposalMethod.UndefinedB:
15435
+ case DisposalMethod.UndefinedC:
15436
+ case DisposalMethod.UndefinedD:
15437
+ case DisposalMethod.Replace:
15438
+ offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
15439
+ context.drawImage(offscreenCanvas, pos.x, pos.y);
15440
+ offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
15441
+ break;
15442
+ case DisposalMethod.Combine:
15443
+ offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
15444
+ context.drawImage(offscreenCanvas, pos.x, pos.y);
15445
+ break;
15446
+ case DisposalMethod.RestoreBackground:
15447
+ offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
15448
+ context.drawImage(offscreenCanvas, pos.x, pos.y);
15449
+ offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
15450
+ if (!gifData.globalColorTable.length) {
15451
+ offscreenContext.putImageData(gifData.frames[firstIndex$1].image, pos.x + frame.left, pos.y + frame.top);
15452
+ }
15453
+ else {
15454
+ offscreenContext.putImageData(gifData.backgroundImage, pos.x, pos.y);
15455
+ }
15456
+ break;
15457
+ case DisposalMethod.RestorePrevious:
15458
+ {
15459
+ const previousImageData = offscreenContext.getImageData(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
15460
+ offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
15461
+ context.drawImage(offscreenCanvas, pos.x, pos.y);
15462
+ offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
15463
+ offscreenContext.putImageData(previousImageData, originPoint.x, originPoint.y);
15464
+ }
15465
+ break;
15466
+ }
15467
+ particle.gifTime += delta.value;
15468
+ if (particle.gifTime > frame.delayTime) {
15469
+ particle.gifTime -= frame.delayTime;
15470
+ if (++frameIndex >= gifData.frames.length) {
15471
+ if (particle.gifLoopCount == null || --particle.gifLoopCount <= defaultLoopCount) {
15472
+ return;
15473
+ }
15474
+ frameIndex = firstIndex$1;
15475
+ offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
15476
+ }
15477
+ particle.gifFrame = frameIndex;
15478
+ }
15479
+ context.scale(gifData.width / radius, gifData.height / radius);
15326
15480
  }
15327
15481
 
15328
- const maxAngle$1 = 360, moveSpeedFactor$1 = 10, defaultDistance = 0;
15329
- class WobbleUpdater {
15482
+ class GifDrawer {
15330
15483
  #container;
15484
+ #gifCache;
15331
15485
  constructor(container) {
15332
15486
  this.#container = container;
15487
+ this.#gifCache = new Map();
15333
15488
  }
15334
- init(particle) {
15335
- const wobbleOpt = particle.options.wobble;
15336
- if (wobbleOpt?.enable) {
15337
- particle.wobble = {
15338
- angle: getRandom() * doublePI,
15339
- angleSpeed: getRangeValue(wobbleOpt.speed.angle) / maxAngle$1,
15340
- moveSpeed: getRangeValue(wobbleOpt.speed.move) / moveSpeedFactor$1,
15341
- };
15342
- }
15343
- else {
15344
- particle.wobble = {
15345
- angle: 0,
15346
- angleSpeed: 0,
15347
- moveSpeed: 0,
15348
- };
15349
- }
15350
- particle.retina.wobbleDistance =
15351
- getRangeValue(wobbleOpt?.distance ?? defaultDistance) * this.#container.retina.pixelRatio;
15489
+ destroy() {
15490
+ this.#gifCache.clear();
15352
15491
  }
15353
- isEnabled(particle) {
15354
- return !particle.destroyed && !particle.spawning && !!particle.options.wobble?.enable;
15492
+ draw(data) {
15493
+ const { context, opacity } = data;
15494
+ if (!data.particle.gifData)
15495
+ return;
15496
+ context.globalAlpha = opacity;
15497
+ drawGif(data, this.#container.canvas.render.settings);
15498
+ context.globalAlpha = 1;
15355
15499
  }
15356
- loadOptions(options, ...sources) {
15357
- loadOptionProperty(options, "wobble", Wobble, ...sources);
15500
+ loadShape(particle) {
15501
+ const shapeData = particle.shapeData;
15502
+ if (!shapeData?.src)
15503
+ return;
15504
+ if (this.#gifCache.has(shapeData.src))
15505
+ return;
15506
+ void decodeGIF(shapeData.src, { colorSpace: "srgb" }).then(gifData => {
15507
+ this.#gifCache.set(shapeData.src, gifData);
15508
+ });
15358
15509
  }
15359
- update(particle, delta) {
15360
- if (!this.isEnabled(particle)) {
15510
+ particleInit(container, particle) {
15511
+ const shapeData = particle.shapeData;
15512
+ if (!shapeData?.src)
15513
+ return;
15514
+ const gifData = this.#gifCache.get(shapeData.src);
15515
+ if (!gifData) {
15516
+ setTimeout(() => {
15517
+ this.particleInit(container, particle);
15518
+ });
15361
15519
  return;
15362
15520
  }
15363
- updateWobble(this.#container, particle, delta);
15521
+ particle.gifData = gifData;
15522
+ particle.gifFrame = 0;
15523
+ particle.gifTime = 0;
15524
+ particle.gifLoopCount = getGIFLoopAmount(gifData);
15525
+ if (particle.gifLoopCount === 0) {
15526
+ particle.gifLoopCount = Number.POSITIVE_INFINITY;
15527
+ }
15528
+ if (shapeData.loopCount !== undefined) {
15529
+ particle.gifLoopCount = shapeData.loopCount;
15530
+ }
15364
15531
  }
15365
15532
  }
15366
15533
 
15367
- async function loadWobbleUpdater(engine) {
15368
- engine.checkVersion("4.2.1");
15534
+ async function loadGifShape(engine) {
15535
+ engine.checkVersion("4.3.0");
15369
15536
  await engine.pluginManager.register(e => {
15370
- e.pluginManager.addParticleUpdater("wobble", container => {
15371
- return Promise.resolve(new WobbleUpdater(container));
15372
- });
15373
- });
15374
- }
15375
-
15376
- async function loadFull(engine) {
15377
- engine.checkVersion("4.2.1");
15378
- await engine.pluginManager.register(async (e) => {
15379
- const loadEmittersPluginBundle = async (e) => {
15380
- await loadEmittersPlugin(e);
15381
- await Promise.all([
15382
- loadEmittersShapeCircle(e),
15383
- loadEmittersShapeSquare(e),
15384
- ]);
15385
- }, loadInteractivityForFull = async (e) => {
15386
- await loadSlim(e);
15387
- await Promise.all([
15388
- loadExternalDragInteraction(e),
15389
- loadExternalTrailInteraction(e),
15390
- loadAbsorbersPlugin(e),
15391
- loadEmittersPluginBundle(e),
15392
- ]);
15393
- };
15394
- await Promise.all([
15395
- loadInteractivityForFull(e),
15396
- loadDestroyUpdater(e),
15397
- loadRollUpdater(e),
15398
- loadTiltUpdater(e),
15399
- loadTwinkleUpdater(e),
15400
- loadWobbleUpdater(e),
15401
- loadTextShape(e),
15402
- ]);
15537
+ e.pluginManager.addShape(["gif", "gifs"], container => Promise.resolve(new GifDrawer(container)));
15403
15538
  });
15404
15539
  }
15405
15540
 
@@ -15513,14 +15648,14 @@
15513
15648
  }
15514
15649
  }
15515
15650
 
15516
- function updateGradient(particle, delta) {
15651
+ function updateGradient(particle, delta, hdr) {
15517
15652
  const { gradient } = particle;
15518
15653
  if (!gradient) {
15519
15654
  return;
15520
15655
  }
15521
15656
  updateAnimation(particle, gradient.angle, false, exports.DestroyType.none, delta);
15522
15657
  for (const color of gradient.colors) {
15523
- updateColor(color.value, delta);
15658
+ updateColor(color.value, delta, hdr);
15524
15659
  if (color.opacity) {
15525
15660
  updateAnimation(particle, color.opacity, true, exports.DestroyType.none, delta);
15526
15661
  }
@@ -15652,7 +15787,7 @@
15652
15787
  }
15653
15788
  }
15654
15789
  update(particle, delta) {
15655
- updateGradient(particle, delta);
15790
+ updateGradient(particle, delta, this.#container.hdr);
15656
15791
  }
15657
15792
  }
15658
15793
 
@@ -20050,7 +20185,7 @@
20050
20185
  }
20051
20186
 
20052
20187
  async function loadAll(engine) {
20053
- engine.checkVersion("4.2.1");
20188
+ engine.checkVersion("4.3.0");
20054
20189
  await engine.pluginManager.register(async (e) => {
20055
20190
  const loadInteractionsForAll = async (e) => {
20056
20191
  await loadFull(e);
@@ -20126,6 +20261,7 @@
20126
20261
  loadArrowShape(e),
20127
20262
  loadCardsShape(e),
20128
20263
  loadCogShape(e),
20264
+ loadGifShape(e),
20129
20265
  loadHeartShape(e),
20130
20266
  loadInfinityShape(e),
20131
20267
  loadMatrixShape(e),
@@ -20151,10 +20287,10 @@
20151
20287
  }
20152
20288
  }
20153
20289
  class RenderManager {
20290
+ #backgroundElement;
20291
+ #backgroundWarnings;
20154
20292
  #canvasClearPlugins;
20155
20293
  #canvasManager;
20156
- #canvasPaintPlugins;
20157
- #clearDrawPlugins;
20158
20294
  #colorPlugins;
20159
20295
  #container;
20160
20296
  #context;
@@ -20162,9 +20298,7 @@
20162
20298
  #drawParticlePlugins;
20163
20299
  #drawParticlesCleanupPlugins;
20164
20300
  #drawParticlesSetupPlugins;
20165
- #drawPlugins;
20166
- #drawSettingsCleanupPlugins;
20167
- #drawSettingsSetupPlugins;
20301
+ #layers;
20168
20302
  #pluginManager;
20169
20303
  #postDrawUpdaters;
20170
20304
  #preDrawUpdaters;
@@ -20176,18 +20310,25 @@
20176
20310
  this.#container = container;
20177
20311
  this.#canvasManager = canvasManager;
20178
20312
  this.#context = null;
20313
+ this.#backgroundElement = null;
20314
+ this.#backgroundWarnings = new Set();
20179
20315
  this.#preDrawUpdaters = [];
20180
20316
  this.#postDrawUpdaters = [];
20181
- this.#colorPlugins = [];
20182
20317
  this.#canvasClearPlugins = [];
20183
- this.#canvasPaintPlugins = [];
20184
- this.#clearDrawPlugins = [];
20318
+ this.#colorPlugins = [];
20185
20319
  this.#drawParticlePlugins = [];
20186
20320
  this.#drawParticlesCleanupPlugins = [];
20187
20321
  this.#drawParticlesSetupPlugins = [];
20188
- this.#drawPlugins = [];
20189
- this.#drawSettingsSetupPlugins = [];
20190
- this.#drawSettingsCleanupPlugins = [];
20322
+ this.#layers = {
20323
+ 0: [],
20324
+ 1: [],
20325
+ 2: [],
20326
+ 3: [],
20327
+ 4: [],
20328
+ 5: [],
20329
+ 6: [],
20330
+ 7: [],
20331
+ };
20191
20332
  }
20192
20333
  get settings() {
20193
20334
  return this.#contextSettings;
@@ -20201,32 +20342,38 @@
20201
20342
  });
20202
20343
  }
20203
20344
  clear() {
20204
- let pluginHandled = false;
20205
20345
  for (const plugin of this.#canvasClearPlugins) {
20206
- pluginHandled = plugin.canvasClear?.() ?? false;
20207
- if (pluginHandled) {
20208
- break;
20346
+ if (plugin.canvasClear?.() ?? false) {
20347
+ return;
20209
20348
  }
20210
20349
  }
20211
- if (pluginHandled) {
20212
- return;
20350
+ for (const layer of Object.values(exports.DrawLayer)) {
20351
+ if (typeof layer === "number") {
20352
+ for (const plugin of this.#getLayerPlugins(layer)) {
20353
+ if (plugin.canvasClear?.() ?? false) {
20354
+ return;
20355
+ }
20356
+ }
20357
+ }
20213
20358
  }
20214
20359
  this.canvasClear();
20215
20360
  }
20216
20361
  destroy() {
20217
20362
  this.stop();
20363
+ this.#backgroundElement = null;
20364
+ this.#backgroundWarnings.clear();
20218
20365
  this.#preDrawUpdaters = [];
20219
20366
  this.#postDrawUpdaters = [];
20220
- this.#colorPlugins = [];
20221
20367
  this.#canvasClearPlugins = [];
20222
- this.#canvasPaintPlugins = [];
20223
- this.#clearDrawPlugins = [];
20368
+ this.#colorPlugins = [];
20224
20369
  this.#drawParticlePlugins = [];
20225
20370
  this.#drawParticlesCleanupPlugins = [];
20226
20371
  this.#drawParticlesSetupPlugins = [];
20227
- this.#drawPlugins = [];
20228
- this.#drawSettingsSetupPlugins = [];
20229
- this.#drawSettingsCleanupPlugins = [];
20372
+ for (const layer of Object.values(exports.DrawLayer)) {
20373
+ if (typeof layer === "number") {
20374
+ this.#layers[layer] = [];
20375
+ }
20376
+ }
20230
20377
  }
20231
20378
  draw(cb) {
20232
20379
  const ctx = this.#context;
@@ -20283,21 +20430,40 @@
20283
20430
  });
20284
20431
  }
20285
20432
  drawParticles(delta) {
20286
- const { particles } = this.#container;
20433
+ const { particles, actualOptions } = this.#container;
20287
20434
  this.clear();
20288
20435
  particles.update(delta);
20289
20436
  this.draw(ctx => {
20290
- for (const plugin of this.#drawSettingsSetupPlugins) {
20437
+ const width = this.#canvasManager.size.width, height = this.#canvasManager.size.height;
20438
+ if (this.#backgroundElement) {
20439
+ try {
20440
+ ctx.drawImage(this.#backgroundElement, originPoint.x, originPoint.y, width, height);
20441
+ }
20442
+ catch {
20443
+ this.#warnOnce("background-element-draw-error", "Error drawing background element onto canvas");
20444
+ }
20445
+ }
20446
+ const background = actualOptions.background;
20447
+ if (background.draw) {
20448
+ try {
20449
+ background.draw(ctx, delta);
20450
+ }
20451
+ catch {
20452
+ this.#warnOnce("background-draw-error", "Error in background.draw callback");
20453
+ }
20454
+ }
20455
+ for (const plugin of this.#getLayerPlugins(exports.DrawLayer.BackgroundMask)) {
20456
+ plugin.canvasPaint?.();
20457
+ }
20458
+ for (const plugin of this.#getLayerPlugins(exports.DrawLayer.CanvasSetup)) {
20291
20459
  plugin.drawSettingsSetup?.(ctx, delta);
20292
20460
  }
20293
- for (const plugin of this.#drawPlugins) {
20461
+ for (const plugin of this.#getLayerPlugins(exports.DrawLayer.PluginContent)) {
20294
20462
  plugin.draw?.(ctx, delta);
20295
20463
  }
20296
20464
  particles.drawParticles(delta);
20297
- for (const plugin of this.#clearDrawPlugins) {
20465
+ for (const plugin of this.#getLayerPlugins(exports.DrawLayer.CanvasCleanup)) {
20298
20466
  plugin.clearDraw?.(ctx, delta);
20299
- }
20300
- for (const plugin of this.#drawSettingsCleanupPlugins) {
20301
20467
  plugin.drawSettingsCleanup?.(ctx, delta);
20302
20468
  }
20303
20469
  });
@@ -20305,29 +20471,24 @@
20305
20471
  init() {
20306
20472
  this.initUpdaters();
20307
20473
  this.initPlugins();
20474
+ this.#resolveBackgroundElement();
20308
20475
  this.paint();
20309
20476
  }
20310
20477
  initPlugins() {
20311
- this.#colorPlugins = [];
20312
20478
  this.#canvasClearPlugins = [];
20313
- this.#canvasPaintPlugins = [];
20314
- this.#clearDrawPlugins = [];
20479
+ this.#colorPlugins = [];
20315
20480
  this.#drawParticlePlugins = [];
20316
20481
  this.#drawParticlesSetupPlugins = [];
20317
20482
  this.#drawParticlesCleanupPlugins = [];
20318
- this.#drawPlugins = [];
20319
- this.#drawSettingsSetupPlugins = [];
20320
- this.#drawSettingsCleanupPlugins = [];
20483
+ for (const layer of Object.values(exports.DrawLayer)) {
20484
+ if (typeof layer === "number") {
20485
+ this.#layers[layer] = [];
20486
+ }
20487
+ }
20321
20488
  for (const plugin of this.#container.plugins) {
20322
20489
  if (plugin.particleFillColor ?? plugin.particleStrokeColor) {
20323
20490
  this.#colorPlugins.push(plugin);
20324
20491
  }
20325
- if (plugin.canvasClear) {
20326
- this.#canvasClearPlugins.push(plugin);
20327
- }
20328
- if (plugin.canvasPaint) {
20329
- this.#canvasPaintPlugins.push(plugin);
20330
- }
20331
20492
  if (plugin.drawParticle) {
20332
20493
  this.#drawParticlePlugins.push(plugin);
20333
20494
  }
@@ -20337,17 +20498,20 @@
20337
20498
  if (plugin.drawParticleCleanup) {
20338
20499
  this.#drawParticlesCleanupPlugins.push(plugin);
20339
20500
  }
20340
- if (plugin.draw) {
20341
- this.#drawPlugins.push(plugin);
20501
+ if (plugin.canvasClear) {
20502
+ this.#canvasClearPlugins.push(plugin);
20503
+ }
20504
+ if (plugin.canvasPaint) {
20505
+ this.#getLayerPlugins(exports.DrawLayer.BackgroundMask).push(plugin);
20342
20506
  }
20343
20507
  if (plugin.drawSettingsSetup) {
20344
- this.#drawSettingsSetupPlugins.push(plugin);
20508
+ this.#getLayerPlugins(exports.DrawLayer.CanvasSetup).push(plugin);
20345
20509
  }
20346
- if (plugin.drawSettingsCleanup) {
20347
- this.#drawSettingsCleanupPlugins.push(plugin);
20510
+ if (plugin.draw) {
20511
+ this.#getLayerPlugins(exports.DrawLayer.PluginContent).push(plugin);
20348
20512
  }
20349
- if (plugin.clearDraw) {
20350
- this.#clearDrawPlugins.push(plugin);
20513
+ if (plugin.clearDraw ?? plugin.drawSettingsCleanup) {
20514
+ this.#getLayerPlugins(exports.DrawLayer.CanvasCleanup).push(plugin);
20351
20515
  }
20352
20516
  }
20353
20517
  }
@@ -20365,7 +20529,7 @@
20365
20529
  }
20366
20530
  paint() {
20367
20531
  let handled = false;
20368
- for (const plugin of this.#canvasPaintPlugins) {
20532
+ for (const plugin of this.#getLayerPlugins(exports.DrawLayer.BackgroundMask)) {
20369
20533
  handled = plugin.canvasPaint?.() ?? false;
20370
20534
  if (handled) {
20371
20535
  break;
@@ -20538,6 +20702,9 @@
20538
20702
  }
20539
20703
  drawer.beforeDraw(data);
20540
20704
  }
20705
+ #getLayerPlugins(layer) {
20706
+ return this.#layers[layer];
20707
+ }
20541
20708
  #getPluginParticleColors(particle) {
20542
20709
  let fColor, sColor;
20543
20710
  for (const plugin of this.#colorPlugins) {
@@ -20555,6 +20722,39 @@
20555
20722
  this.#reusablePluginColors[sColorIndex] = sColor;
20556
20723
  return this.#reusablePluginColors;
20557
20724
  }
20725
+ #resolveBackgroundElement() {
20726
+ const background = this.#container.actualOptions.background;
20727
+ this.#backgroundElement = null;
20728
+ if (!background.element) {
20729
+ return;
20730
+ }
20731
+ if (typeof background.element === "string") {
20732
+ if (typeof document !== "undefined") {
20733
+ const node = document.querySelector(background.element);
20734
+ if (node instanceof HTMLCanvasElement || node instanceof HTMLVideoElement || node instanceof HTMLImageElement) {
20735
+ this.#backgroundElement = node;
20736
+ }
20737
+ else if (node) {
20738
+ this.#warnOnce("background-element-not-supported", `Background element "${background.element}" is not a supported drawable element (canvas, video, or img)`);
20739
+ }
20740
+ else {
20741
+ this.#warnOnce("background-element-not-found", `Background element selector "${background.element}" not found`);
20742
+ }
20743
+ }
20744
+ }
20745
+ else if (background.element instanceof HTMLCanvasElement ||
20746
+ background.element instanceof OffscreenCanvas ||
20747
+ background.element instanceof HTMLVideoElement ||
20748
+ background.element instanceof HTMLImageElement) {
20749
+ this.#backgroundElement = background.element;
20750
+ }
20751
+ }
20752
+ #warnOnce(key, message) {
20753
+ if (!this.#backgroundWarnings.has(key)) {
20754
+ this.#backgroundWarnings.add(key);
20755
+ getLogger().warning(message);
20756
+ }
20757
+ }
20558
20758
  }
20559
20759
 
20560
20760
  const transferredCanvases = new WeakMap(), getTransferredCanvas = (canvas) => {
@@ -21131,7 +21331,6 @@
21131
21331
  }
21132
21332
  class Particle {
21133
21333
  backColor;
21134
- bubble;
21135
21334
  destroyed;
21136
21335
  direction;
21137
21336
  effect;
@@ -21165,7 +21364,6 @@
21165
21364
  shapeData;
21166
21365
  sides;
21167
21366
  size;
21168
- slow;
21169
21367
  spawning;
21170
21368
  strokeColor;
21171
21369
  strokeOpacity;
@@ -21187,18 +21385,25 @@
21187
21385
  d: 1,
21188
21386
  };
21189
21387
  #container;
21388
+ #modifiers = [];
21190
21389
  #pluginManager;
21191
21390
  constructor(pluginManager, container) {
21192
21391
  this.#pluginManager = pluginManager;
21193
21392
  this.#container = container;
21194
21393
  }
21394
+ addModifier(modifier) {
21395
+ this.#modifiers.push(modifier);
21396
+ this.#modifiers.sort((a, b) => a.priority - b.priority);
21397
+ }
21398
+ clearModifiers() {
21399
+ this.#modifiers.length = 0;
21400
+ }
21195
21401
  destroy(override) {
21196
21402
  if (this.unbreakable || this.destroyed) {
21197
21403
  return;
21198
21404
  }
21199
21405
  this.destroyed = true;
21200
- this.bubble.inRange = false;
21201
- this.slow.inRange = false;
21406
+ this.clearModifiers();
21202
21407
  const container = this.#container, shapeDrawer = this.shape ? container.shapeDrawers.get(this.shape) : undefined;
21203
21408
  shapeDrawer?.particleDestroy?.(this);
21204
21409
  for (const plugin of container.particleDestroyedPlugins) {
@@ -21220,13 +21425,16 @@
21220
21425
  return this.rotation + (this.pathRotation ? this.velocity.angle : defaultAngle$1);
21221
21426
  }
21222
21427
  getFillColor() {
21223
- return this.#getRollColor(this.bubble.color ?? getHslFromAnimation(this.fillColor));
21428
+ return this.#getRollColor(this.#applyModifiers(getHslFromAnimation(this.fillColor), m => m.fillColor));
21224
21429
  }
21225
21430
  getMass() {
21226
21431
  return this.getRadius() ** squareExp * Math.PI * half$2;
21227
21432
  }
21433
+ getModifier(id) {
21434
+ return this.#modifiers.find(m => m.id === id);
21435
+ }
21228
21436
  getOpacity() {
21229
- const zIndexOptions = this.options.zIndex, zIndexFactor = zIndexFactorOffset - this.zIndexFactor, zOpacityFactor = zIndexFactor ** zIndexOptions.opacityRate, opacity = this.bubble.opacity ?? getRangeValue(this.opacity?.value ?? defaultOpacity$3), fillOpacity = this.fillOpacity ?? defaultOpacity$3, strokeOpacity = this.strokeOpacity ?? defaultOpacity$3;
21437
+ const zIndexOptions = this.options.zIndex, zIndexFactor = zIndexFactorOffset - this.zIndexFactor, zOpacityFactor = zIndexFactor ** zIndexOptions.opacityRate, baseOpacity = getRangeValue(this.opacity?.value ?? defaultOpacity$3), modifierOpacity = this.#applyModifiers(undefined, m => m.opacity), opacity = modifierOpacity ?? baseOpacity, fillOpacity = this.fillOpacity ?? defaultOpacity$3, strokeOpacity = this.strokeOpacity ?? defaultOpacity$3;
21230
21438
  this.#cachedOpacityData.fillOpacity = opacity * fillOpacity * zOpacityFactor;
21231
21439
  this.#cachedOpacityData.opacity = opacity * zOpacityFactor;
21232
21440
  this.#cachedOpacityData.strokeOpacity = opacity * strokeOpacity * zOpacityFactor;
@@ -21239,7 +21447,7 @@
21239
21447
  return this.#cachedPosition;
21240
21448
  }
21241
21449
  getRadius() {
21242
- return this.bubble.radius ?? this.size.value;
21450
+ return this.#applyModifiers(this.size.value, m => m.radius);
21243
21451
  }
21244
21452
  getRotateData() {
21245
21453
  const angle = this.getAngle();
@@ -21248,7 +21456,7 @@
21248
21456
  return this.#cachedRotateData;
21249
21457
  }
21250
21458
  getStrokeColor() {
21251
- return this.#getRollColor(this.bubble.color ?? getHslFromAnimation(this.strokeColor));
21459
+ return this.#getRollColor(this.#applyModifiers(getHslFromAnimation(this.strokeColor), m => m.strokeColor));
21252
21460
  }
21253
21461
  getTransformData(externalTransform) {
21254
21462
  const rotateData = this.getRotateData(), rotating = this.isRotating;
@@ -21268,13 +21476,6 @@
21268
21476
  this.options = resolveParticleOptions(this, container, this.#pluginManager, overrideOptions);
21269
21477
  container.retina.initParticle(this);
21270
21478
  runUpdaterPreInit(container.particleUpdaters, this);
21271
- this.bubble = {
21272
- inRange: false,
21273
- };
21274
- this.slow = {
21275
- inRange: false,
21276
- factor: 1,
21277
- };
21278
21479
  this.#initPosition(position);
21279
21480
  this.initialVelocity = this.#calculateVelocity();
21280
21481
  this.velocity = this.initialVelocity.copy();
@@ -21314,11 +21515,29 @@
21314
21515
  isVisible() {
21315
21516
  return !this.destroyed && !this.spawning && this.isInsideCanvas();
21316
21517
  }
21518
+ removeModifier(id) {
21519
+ const idx = this.#modifiers.findIndex(m => m.id === id);
21520
+ if (idx >= defaultAngle$1) {
21521
+ this.#modifiers.splice(idx, identity$3);
21522
+ }
21523
+ }
21317
21524
  reset() {
21318
21525
  for (const updater of this.#container.particleUpdaters) {
21319
21526
  updater.reset?.(this);
21320
21527
  }
21321
21528
  }
21529
+ #applyModifiers(base, getter) {
21530
+ let value = base;
21531
+ for (const mod of this.#modifiers) {
21532
+ if (mod.enabled) {
21533
+ const override = getter(mod);
21534
+ if (override !== undefined) {
21535
+ value = override;
21536
+ }
21537
+ }
21538
+ }
21539
+ return value;
21540
+ }
21322
21541
  #calcPosition(position, zIndex) {
21323
21542
  let tryCount = defaultRetryCount, posVec = position ? Vector3d.create(position.x, position.y, zIndex) : undefined;
21324
21543
  const container = this.#container, plugins = container.particlePositionPlugins, outModes = this.options.move.outModes, radius = this.getRadius(), canvasSize = container.canvas.size;
@@ -22420,10 +22639,13 @@
22420
22639
  });
22421
22640
 
22422
22641
  class BackgroundMaskPluginInstance {
22642
+ layer = exports.DrawLayer.BackgroundMask;
22423
22643
  #container;
22424
22644
  #coverColorStyle;
22425
22645
  #coverImage;
22426
22646
  #defaultCompositeValue;
22647
+ #maskElement;
22648
+ #maskWarnings = new Set();
22427
22649
  #pluginManager;
22428
22650
  constructor(pluginManager, container) {
22429
22651
  this.#pluginManager = pluginManager;
@@ -22437,19 +22659,52 @@
22437
22659
  return this.canvasPaint();
22438
22660
  }
22439
22661
  canvasPaint() {
22440
- if (!this.#container.actualOptions.backgroundMask?.enable) {
22662
+ const { backgroundMask } = this.#container.actualOptions;
22663
+ if (!backgroundMask?.enable) {
22441
22664
  return false;
22442
22665
  }
22443
22666
  const canvas = this.#container.canvas;
22444
22667
  canvas.render.canvasClear();
22445
- if (this.#coverImage) {
22446
- canvas.render.paintImage(this.#coverImage.image, this.#coverImage.opacity);
22668
+ const cover = backgroundMask.cover;
22669
+ let dynamicUsed = false;
22670
+ const maskElement = this.#maskElement;
22671
+ if (maskElement) {
22672
+ dynamicUsed = true;
22673
+ canvas.render.draw(ctx => {
22674
+ try {
22675
+ ctx.drawImage(maskElement, originPoint.x, originPoint.y, canvas.size.width, canvas.size.height);
22676
+ }
22677
+ catch {
22678
+ this.#maskWarnOnce("mask-element-draw-error", "Error drawing background mask cover element onto canvas");
22679
+ }
22680
+ });
22447
22681
  }
22448
- else {
22449
- canvas.render.paintBase(this.#coverColorStyle);
22682
+ if (cover.draw) {
22683
+ dynamicUsed = true;
22684
+ const drawFn = cover.draw;
22685
+ canvas.render.draw(ctx => {
22686
+ try {
22687
+ drawFn(ctx, { value: 0, factor: 1 });
22688
+ }
22689
+ catch {
22690
+ this.#maskWarnOnce("mask-draw-error", "Error in mask cover.draw callback");
22691
+ }
22692
+ });
22693
+ }
22694
+ if (!dynamicUsed) {
22695
+ if (this.#coverImage) {
22696
+ canvas.render.paintImage(this.#coverImage.image, this.#coverImage.opacity);
22697
+ }
22698
+ else {
22699
+ canvas.render.paintBase(this.#coverColorStyle);
22700
+ }
22450
22701
  }
22451
22702
  return true;
22452
22703
  }
22704
+ destroy() {
22705
+ this.#maskElement = null;
22706
+ this.#maskWarnings.clear();
22707
+ }
22453
22708
  drawSettingsCleanup(context) {
22454
22709
  if (!this.#defaultCompositeValue) {
22455
22710
  return;
@@ -22462,7 +22717,9 @@
22462
22717
  context.globalCompositeOperation = backgroundMask?.enable ? backgroundMask.composite : previousComposite;
22463
22718
  }
22464
22719
  async init() {
22720
+ this.#maskWarnings.clear();
22465
22721
  await this.#initCover();
22722
+ this.#resolveMaskElement();
22466
22723
  }
22467
22724
  #initCover = async () => {
22468
22725
  const options = this.#container.actualOptions, cover = options.backgroundMask?.cover, color = cover?.color;
@@ -22476,11 +22733,9 @@
22476
22733
  this.#coverColorStyle = getStyleFromRgb(coverColor, this.#container.hdr, coverColor.a);
22477
22734
  }
22478
22735
  }
22479
- else {
22736
+ else if (cover?.image) {
22737
+ const coverImage = cover.image;
22480
22738
  await new Promise((resolve, reject) => {
22481
- if (!cover?.image) {
22482
- return;
22483
- }
22484
22739
  const img = safeDocument().createElement("img");
22485
22740
  img.addEventListener("load", () => {
22486
22741
  this.#coverImage = {
@@ -22492,10 +22747,44 @@
22492
22747
  img.addEventListener("error", () => {
22493
22748
  reject(new Error("Error loading image"));
22494
22749
  });
22495
- img.src = cover.image;
22750
+ img.src = coverImage;
22496
22751
  });
22497
22752
  }
22498
22753
  };
22754
+ #maskWarnOnce = (key, message) => {
22755
+ if (this.#maskWarnings.has(key)) {
22756
+ return;
22757
+ }
22758
+ this.#maskWarnings.add(key);
22759
+ getLogger().warning(`[tsParticles BackgroundMask] ${message}`);
22760
+ };
22761
+ #resolveMaskElement = () => {
22762
+ const cover = this.#container.actualOptions.backgroundMask?.cover;
22763
+ this.#maskElement = null;
22764
+ if (!cover?.element) {
22765
+ return;
22766
+ }
22767
+ if (typeof cover.element === "string") {
22768
+ if (typeof document !== "undefined") {
22769
+ const node = document.querySelector(cover.element);
22770
+ if (node instanceof HTMLCanvasElement || node instanceof HTMLVideoElement || node instanceof HTMLImageElement) {
22771
+ this.#maskElement = node;
22772
+ }
22773
+ else if (node) {
22774
+ this.#maskWarnOnce("mask-element-not-supported", `Mask cover element "${cover.element}" matched a non-drawable element (expected canvas, video, or img)`);
22775
+ }
22776
+ else {
22777
+ this.#maskWarnOnce("mask-element-not-found", `Mask cover element selector "${cover.element}" not found in the DOM`);
22778
+ }
22779
+ }
22780
+ }
22781
+ else if (cover.element instanceof HTMLCanvasElement ||
22782
+ cover.element instanceof OffscreenCanvas ||
22783
+ cover.element instanceof HTMLVideoElement ||
22784
+ cover.element instanceof HTMLImageElement) {
22785
+ this.#maskElement = cover.element;
22786
+ }
22787
+ };
22499
22788
  }
22500
22789
 
22501
22790
  var BackgroundMaskPluginInstance$1 = /*#__PURE__*/Object.freeze({
@@ -22611,7 +22900,8 @@
22611
22900
  particle.lastPathTime -= pathDelay;
22612
22901
  }
22613
22902
  function getProximitySpeedFactor(particle) {
22614
- return particle.slow.inRange ? particle.slow.factor : identity$3;
22903
+ const mod = particle.getModifier("slow");
22904
+ return mod?.enabled ? (mod.speedFactor ?? identity$3) : identity$3;
22615
22905
  }
22616
22906
  function initSpin(container, particle) {
22617
22907
  const options = particle.options, spinOptions = options.move.spin;
@@ -23775,6 +24065,7 @@
23775
24065
  });
23776
24066
 
23777
24067
  class BlendPluginInstance {
24068
+ layer = exports.DrawLayer.CanvasSetup;
23778
24069
  #container;
23779
24070
  #defaultCompositeValue;
23780
24071
  constructor(container) {
@@ -26880,6 +27171,7 @@
26880
27171
 
26881
27172
  const minimumLength = 0;
26882
27173
  class TrailPluginInstance {
27174
+ layer = exports.DrawLayer.PluginContent;
26883
27175
  #container;
26884
27176
  #pluginManager;
26885
27177
  #trailFill;
@@ -27133,6 +27425,7 @@
27133
27425
  }
27134
27426
 
27135
27427
  class ZoomPluginInstance {
27428
+ layer = exports.DrawLayer.CanvasSetup;
27136
27429
  #container;
27137
27430
  #listeners;
27138
27431
  constructor(container) {
@@ -28044,6 +28337,7 @@
28044
28337
  exports.hPhase = hPhase;
28045
28338
  exports.half = half$2;
28046
28339
  exports.hslToRgb = hslToRgb;
28340
+ exports.hslToRgbFloat = hslToRgbFloat;
28047
28341
  exports.hslaToRgba = hslaToRgba;
28048
28342
  exports.identity = identity$3;
28049
28343
  exports.inverseFactorNumerator = inverseFactorNumerator;