tsparticles
Version:
Full-featured tsParticles bundle — create stunning particle, confetti and fireworks animations with all official plugins and presets included. Ready to use components available for React, Vue.js (2.x and 3.x), Angular, Svelte, jQuery, Preact, Riot.js, Inf
13,372 lines • 545 kB
JavaScript
(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);
/* tsParticles v4.4.0 */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.__tsParticlesInternals = global.__tsParticlesInternals || {}, global.__tsParticlesInternals.bundles = global.__tsParticlesInternals.bundles || {}, global.__tsParticlesInternals.bundles.full = global.__tsParticlesInternals.bundles.full || {})));
})(this, (function (exports) { 'use strict';
const generatedAttribute = "generated", defaultCompositeValue = "source-over", resizeEvent = "resize", visibilityChangeEvent = "visibilitychange", percentDenominator = 100, half = 0.5, millisecondsToSeconds = 1000, originPoint = {
x: 0,
y: 0,
z: 0,
}, defaultTransform = {
a: 1,
b: 0,
c: 0,
d: 1,
}, randomColorValue = "random", midColorValue = "mid", double = 2, doublePI = Math.PI * double, defaultFps = 60, defaultAlpha$1 = 1, generatedTrue = "true", generatedFalse = "false", canvasTag = "canvas", defaultRetryCount = 0, squareExp = 2, spatialHashGridCellSize = 100, defaultRemoveQuantity = 1, defaultRatio$1 = 1, defaultReduceFactor = 1, inverseFactorNumerator = 1, rgbMax = 255, hMax = 360, sMax = 100, lMax = 100, hMin = 0, sMin = 0, hPhase = 60, empty = 0, quarter = 0.25, threeQuarter = half + quarter, minVelocity = 0, minDistance$6 = 0, minRadius$5 = 0, defaultTransformValue = 1, minimumSize = 0, zIndexFactorOffset = 1, defaultOpacity$3 = 1, removeDeleteCount = 1, removeMinIndex = 0, defaultFpsLimit = 120, minFpsLimit = 0, canvasFirstIndex = 0, loadRandomFactor = 10000, loadMinIndex = 0, one = 1, none = 0, decayOffset = 1, tryCountIncrement = 1, minZ = 0, minLimit = 0, countOffset = 1, minCount = 0, minIndex = 0, lengthOffset = 1, defaultDensityFactor = 1, deleteCount = 1, defaultAngle = 0, identity$2 = 1, minStrokeWidth = 0, lFactor = 1, lMin = 0, maxNits = 400, triple = 3, sextuple = 6, sNormalizedOffset = 1, phaseNumerator = 1, defaultRgbMin = 0, defaultVelocity = 0, defaultLoops = 0, defaultTime = 0, defaultZoom = 1;
exports.MoveDirection = void 0;
(function (MoveDirection) {
MoveDirection["bottom"] = "bottom";
MoveDirection["bottomLeft"] = "bottom-left";
MoveDirection["bottomRight"] = "bottom-right";
MoveDirection["left"] = "left";
MoveDirection["none"] = "none";
MoveDirection["right"] = "right";
MoveDirection["top"] = "top";
MoveDirection["topLeft"] = "top-left";
MoveDirection["topRight"] = "top-right";
MoveDirection["outside"] = "outside";
MoveDirection["inside"] = "inside";
})(exports.MoveDirection || (exports.MoveDirection = {}));
function getZ(source) {
return "z" in source ? source.z : originPoint.z;
}
class Vector3d {
x;
y;
z;
constructor(x = originPoint.x, y = originPoint.y, z = originPoint.z) {
this.x = x;
this.y = y;
this.z = z;
}
static get origin() {
return Vector3d.create(originPoint.x, originPoint.y, originPoint.z);
}
get angle() {
return Math.atan2(this.y, this.x);
}
set angle(angle) {
this.#updateFromAngle(angle, this.length);
}
get length() {
return Math.sqrt(this.getLengthSq());
}
set length(length) {
this.#updateFromAngle(this.angle, length);
}
static clone(source) {
return Vector3d.create(source.x, source.y, getZ(source));
}
static create(x, y, z) {
if (typeof x === "number") {
return new Vector3d(x, y ?? originPoint.y, z ?? originPoint.z);
}
return new Vector3d(x.x, x.y, getZ(x));
}
add(v) {
return Vector3d.create(this.x + v.x, this.y + v.y, this.z + getZ(v));
}
addTo(v) {
this.x += v.x;
this.y += v.y;
this.z += getZ(v);
}
copy() {
return Vector3d.clone(this);
}
div(n) {
return Vector3d.create(this.x / n, this.y / n, this.z / n);
}
getLengthSq() {
return this.x ** squareExp + this.y ** squareExp;
}
mult(n) {
return Vector3d.create(this.x * n, this.y * n, this.z * n);
}
multTo(n) {
this.x *= n;
this.y *= n;
this.z *= n;
}
normalize() {
const length = this.length;
if (length != none) {
this.multTo(inverseFactorNumerator / length);
}
}
rotate(angle) {
return Vector3d.create(this.x * Math.cos(angle) - this.y * Math.sin(angle), this.x * Math.sin(angle) + this.y * Math.cos(angle), originPoint.z);
}
setTo(c) {
this.x = c.x;
this.y = c.y;
this.z = getZ(c);
}
sub(v) {
return Vector3d.create(this.x - v.x, this.y - v.y, this.z - getZ(v));
}
subFrom(v) {
this.x -= v.x;
this.y -= v.y;
this.z -= getZ(v);
}
#updateFromAngle(angle, length) {
this.x = Math.cos(angle) * length;
this.y = Math.sin(angle) * length;
}
}
class Vector extends Vector3d {
constructor(x = originPoint.x, y = originPoint.y) {
super(x, y, originPoint.z);
}
static get origin() {
return Vector.create(originPoint.x, originPoint.y);
}
static clone(source) {
return Vector.create(source.x, source.y);
}
static create(x, y) {
if (typeof x === "number") {
return new Vector(x, y ?? originPoint.y);
}
return new Vector(x.x, x.y);
}
}
function isBoolean(arg) {
return typeof arg === "boolean";
}
function isString(arg) {
return typeof arg === "string";
}
function isNumber(arg) {
return typeof arg === "number";
}
function isFunction(arg) {
return typeof arg === "function";
}
function isObject(arg) {
return typeof arg === "object" && arg !== null;
}
function isArray(arg) {
return Array.isArray(arg);
}
function isNull(arg) {
return arg === null || arg === undefined;
}
const piDeg = 180, degToRadFactor = Math.PI / piDeg;
let _random = Math.random;
const _animationLoop = {
nextFrame: (cb) => requestAnimationFrame(cb),
cancel: (idx) => {
cancelAnimationFrame(idx);
},
};
function setRandom(rnd = Math.random) {
_random = rnd;
}
function getRandomFn() {
return _random;
}
function getRandom() {
const min = 0, max = 1;
return clamp(_random(), min, max - Number.EPSILON);
}
function getRandomInRange(min, max) {
return getRandom() * (max - min) + min;
}
function setAnimationFunctions(nextFrame, cancel) {
_animationLoop.nextFrame = nextFrame;
_animationLoop.cancel = cancel;
}
function animate(fn) {
return _animationLoop.nextFrame(fn);
}
function cancelAnimation(handle) {
_animationLoop.cancel(handle);
}
function clamp(num, min, max) {
return Math.min(Math.max(num, min), max);
}
function mix(comp1, comp2, weight1, weight2) {
return Math.floor((comp1 * weight1 + comp2 * weight2) / (weight1 + weight2));
}
function randomInRangeValue(r) {
const max = getRangeMax(r), minOffset = 0;
let min = getRangeMin(r);
if (max === min) {
min = minOffset;
}
return getRandomInRange(min, max);
}
function getRangeValue(value) {
return isNumber(value) ? value : randomInRangeValue(value);
}
function getRangeMin(value) {
return isNumber(value) ? value : value.min;
}
function getRangeMax(value) {
return isNumber(value) ? value : value.max;
}
function setRangeValue(source, value) {
if (source === value || (value === undefined && isNumber(source))) {
return source;
}
const min = getRangeMin(source), max = getRangeMax(source);
return value !== undefined
? {
min: Math.min(min, value),
max: Math.max(max, value),
}
: setRangeValue(min, max);
}
function getDistances(pointA, pointB) {
const dx = pointA.x - pointB.x, dy = pointA.y - pointB.y;
return { dx: dx, dy: dy, distance: Math.hypot(dx, dy) };
}
function getDistanceSq(pointA, pointB) {
const dx = pointA.x - pointB.x, dy = pointA.y - pointB.y;
return dx * dx + dy * dy;
}
function getDistance(pointA, pointB) {
return Math.sqrt(getDistanceSq(pointA, pointB));
}
function checkDistance(pointA, pointB, distance) {
return getDistanceSq(pointA, pointB) <= distance * distance;
}
function degToRad(degrees) {
return degrees * degToRadFactor;
}
function getParticleDirectionAngle(direction, position, center) {
if (isNumber(direction)) {
return degToRad(direction);
}
switch (direction) {
case exports.MoveDirection.top:
return -Math.PI * half;
case exports.MoveDirection.topRight:
return -Math.PI * quarter;
case exports.MoveDirection.right:
return empty;
case exports.MoveDirection.bottomRight:
return Math.PI * quarter;
case exports.MoveDirection.bottom:
return Math.PI * half;
case exports.MoveDirection.bottomLeft:
return Math.PI * threeQuarter;
case exports.MoveDirection.left:
return Math.PI;
case exports.MoveDirection.topLeft:
return -Math.PI * threeQuarter;
case exports.MoveDirection.inside:
return Math.atan2(center.y - position.y, center.x - position.x);
case exports.MoveDirection.outside:
return Math.atan2(position.y - center.y, position.x - center.x);
default:
return getRandom() * doublePI;
}
}
function getParticleBaseVelocity(direction) {
const baseVelocity = Vector.origin;
baseVelocity.length = 1;
baseVelocity.angle = direction;
return baseVelocity;
}
function collisionVelocity(v1, v2, m1, m2) {
return Vector.create((v1.x * (m1 - m2)) / (m1 + m2) + (v2.x * double * m2) / (m1 + m2), v1.y);
}
function calcPositionFromSize(data) {
return data.position?.x !== undefined && data.position.y !== undefined
? {
x: (data.position.x * data.size.width) / percentDenominator,
y: (data.position.y * data.size.height) / percentDenominator,
}
: undefined;
}
function calcPositionOrRandomFromSize(data) {
return {
x: ((data.position?.x ?? getRandom() * percentDenominator) * data.size.width) / percentDenominator,
y: ((data.position?.y ?? getRandom() * percentDenominator) * data.size.height) / percentDenominator,
};
}
function calcPositionOrRandomFromSizeRanged(data) {
const position = {
x: data.position?.x !== undefined ? getRangeValue(data.position.x) : undefined,
y: data.position?.y !== undefined ? getRangeValue(data.position.y) : undefined,
};
return calcPositionOrRandomFromSize({ size: data.size, position });
}
function calcExactPositionOrRandomFromSize(data) {
const { position, size } = data;
return {
x: position?.x ?? getRandom() * size.width,
y: position?.y ?? getRandom() * size.height,
};
}
function parseAlpha(input) {
const defaultAlpha = 1;
if (!input) {
return defaultAlpha;
}
return input.endsWith("%") ? parseFloat(input) / percentDenominator : parseFloat(input);
}
exports.OutModeDirection = void 0;
(function (OutModeDirection) {
OutModeDirection["bottom"] = "bottom";
OutModeDirection["left"] = "left";
OutModeDirection["right"] = "right";
OutModeDirection["top"] = "top";
})(exports.OutModeDirection || (exports.OutModeDirection = {}));
exports.PixelMode = void 0;
(function (PixelMode) {
PixelMode["precise"] = "precise";
PixelMode["percent"] = "percent";
})(exports.PixelMode || (exports.PixelMode = {}));
const minRadius$4 = 0;
function isForbiddenKey(key) {
return key === "__proto__" || key === "constructor" || key === "prototype";
}
function hasMatchMedia() {
return typeof matchMedia !== "undefined";
}
function safeDocument() {
return globalThis.document;
}
function safeMatchMedia(query) {
if (!hasMatchMedia()) {
return;
}
return matchMedia(query);
}
function safeMutationObserver(callback) {
if (typeof MutationObserver === "undefined") {
return;
}
return new MutationObserver(callback);
}
function isInArray(value, array) {
return value === array || (isArray(array) && array.includes(value));
}
function itemFromArray(array, index, useIndex = true) {
return array[index !== undefined && useIndex ? index % array.length : Math.floor(getRandom() * array.length)];
}
function isPointInside(point, size, offset, radius, direction) {
return areBoundsInside(calculateBounds(point, radius ?? minRadius$4), size, offset, direction);
}
function areBoundsInside(bounds, size, offset, direction) {
let inside = true;
if (!direction || direction === exports.OutModeDirection.bottom) {
inside = bounds.top < size.height + offset.x;
}
if (inside && (!direction || direction === exports.OutModeDirection.left)) {
inside = bounds.right > offset.x;
}
if (inside && (!direction || direction === exports.OutModeDirection.right)) {
inside = bounds.left < size.width + offset.y;
}
if (inside && (!direction || direction === exports.OutModeDirection.top)) {
inside = bounds.bottom > offset.y;
}
return inside;
}
function calculateBounds(point, radius) {
return {
bottom: point.y + radius,
left: point.x - radius,
right: point.x + radius,
top: point.y - radius,
};
}
function deepExtend(destination, ...sources) {
for (const source of sources) {
if (isNull(source)) {
continue;
}
if (!isObject(source)) {
destination = source;
continue;
}
if (Array.isArray(source)) {
if (!Array.isArray(destination)) {
destination = [];
}
}
else if (!isObject(destination) || Array.isArray(destination)) {
destination = Object.create(null);
}
const sourceKeys = Object.keys(source), hasNested = sourceKeys.some(k => {
const v = source[k];
return isObject(v) || Array.isArray(v);
});
if (!hasNested) {
const sourceDict = source, destDict = destination;
for (const key of sourceKeys) {
if (isForbiddenKey(key)) {
continue;
}
const v = sourceDict[key];
if (v !== undefined) {
destDict[key] = v;
}
}
continue;
}
for (const key of sourceKeys) {
if (isForbiddenKey(key)) {
continue;
}
const sourceDict = source, destDict = destination, value = sourceDict[key];
destDict[key] = Array.isArray(value)
? value.map(v => deepExtend(undefined, v))
: deepExtend(destDict[key], value);
}
}
return destination;
}
function circleBounceDataFromParticle(p) {
return {
position: p.getPosition(),
radius: p.getRadius(),
mass: p.getMass(),
velocity: p.velocity,
factor: Vector.create(getRangeValue(p.options.bounce.horizontal.value), getRangeValue(p.options.bounce.vertical.value)),
};
}
function circleBounce(p1, p2) {
const { x: xVelocityDiff, y: yVelocityDiff } = p1.velocity.sub(p2.velocity), [pos1, pos2] = [p1.position, p2.position], { dx: xDist, dy: yDist } = getDistances(pos2, pos1), minimumDistance = 0;
if (xVelocityDiff * xDist + yVelocityDiff * yDist < minimumDistance) {
return;
}
const angle = -Math.atan2(yDist, xDist), m1 = p1.mass, m2 = p2.mass, u1 = p1.velocity.rotate(angle), u2 = p2.velocity.rotate(angle), v1 = collisionVelocity(u1, u2, m1, m2), v2 = collisionVelocity(u2, u1, m1, m2), vFinal1 = v1.rotate(-angle), vFinal2 = v2.rotate(-angle);
p1.velocity.x = vFinal1.x * p1.factor.x;
p1.velocity.y = vFinal1.y * p1.factor.y;
p2.velocity.x = vFinal2.x * p2.factor.x;
p2.velocity.y = vFinal2.y * p2.factor.y;
}
function executeOnSingleOrMultiple(obj, callback) {
const defaultIndex = 0;
return isArray(obj) ? obj.map((item, index) => callback(item, index)) : callback(obj, defaultIndex);
}
function itemFromSingleOrMultiple(obj, index, useIndex) {
return isArray(obj) ? itemFromArray(obj, index, useIndex) : obj;
}
function getPositionOrSize(positionOrSize, canvasSize) {
const isPercent = positionOrSize.mode === exports.PixelMode.percent;
if (!isPercent) {
const { mode: _, ...rest } = positionOrSize;
return rest;
}
const isPosition = "x" in positionOrSize;
if (isPosition) {
return {
x: (positionOrSize.x / percentDenominator) * canvasSize.width,
y: (positionOrSize.y / percentDenominator) * canvasSize.height,
};
}
else {
return {
width: (positionOrSize.width / percentDenominator) * canvasSize.width,
height: (positionOrSize.height / percentDenominator) * canvasSize.height,
};
}
}
function getPosition(position, canvasSize) {
return getPositionOrSize(position, canvasSize);
}
function cloneStyle(style) {
const clonedStyle = safeDocument().createElement("div").style;
for (const key in style) {
const styleKey = style[key];
if (!(key in style) || isNull(styleKey)) {
continue;
}
const styleValue = style.getPropertyValue?.(styleKey);
if (!styleValue) {
continue;
}
const stylePriority = style.getPropertyPriority?.(styleKey);
if (stylePriority) {
clonedStyle.setProperty(styleKey, styleValue, stylePriority);
}
else {
clonedStyle.setProperty(styleKey, styleValue);
}
}
return clonedStyle;
}
let _cachedZIndex, _cachedStyle;
function getFullScreenStyle(zIndex) {
if (_cachedZIndex !== zIndex || !_cachedStyle) {
_cachedZIndex = zIndex;
const fullScreenStyle = safeDocument().createElement("div").style, radix = 10, style = {
width: "100%",
height: "100%",
margin: "0",
padding: "0",
borderWidth: "0",
position: "fixed",
zIndex: zIndex.toString(radix),
"z-index": zIndex.toString(radix),
top: "0",
left: "0",
"pointer-events": "none",
};
for (const key in style) {
const value = style[key];
if (value === undefined) {
continue;
}
fullScreenStyle.setProperty(key, value);
}
_cachedStyle = fullScreenStyle;
}
return _cachedStyle;
}
function manageListener(element, event, handler, add, options) {
if (add) {
let addOptions = { passive: true };
if (isBoolean(options)) {
addOptions.capture = options;
}
else if (options !== undefined) {
addOptions = options;
}
element.addEventListener(event, handler, addOptions);
}
else {
const removeOptions = options;
element.removeEventListener(event, handler, removeOptions);
}
}
async function getItemsFromInitializer(container, map, initializers, force = false) {
let res = map.get(container);
if (!res || force) {
res = await Promise.all([...initializers.values()].map(t => t(container)));
map.set(container, res);
}
return res;
}
async function getItemMapFromInitializer(container, map, initializers, force = false) {
let res = map.get(container);
if (!res || force) {
const entries = await Promise.all([...initializers.entries()].map(([key, initializer]) => initializer(container).then(item => [key, item])));
res = new Map(entries);
map.set(container, res);
}
return res;
}
class EventDispatcher {
#listeners;
constructor() {
this.#listeners = new Map();
}
addEventListener(type, listener) {
this.removeEventListener(type, listener);
let arr = this.#listeners.get(type);
if (!arr) {
arr = [];
this.#listeners.set(type, arr);
}
arr.push(listener);
}
dispatchEvent(type, args) {
const listeners = this.#listeners.get(type);
listeners?.forEach(handler => {
handler(args);
});
}
hasEventListener(type) {
return !!this.#listeners.get(type);
}
removeAllEventListeners(type) {
if (!type) {
this.#listeners = new Map();
}
else {
this.#listeners.delete(type);
}
}
removeEventListener(type, listener) {
const arr = this.#listeners.get(type);
if (!arr) {
return;
}
const length = arr.length, idx = arr.indexOf(listener);
if (idx < minIndex) {
return;
}
if (length === deleteCount) {
this.#listeners.delete(type);
}
else {
arr.splice(idx, deleteCount);
}
}
}
exports.EventType = void 0;
(function (EventType) {
EventType["configAdded"] = "configAdded";
EventType["containerInit"] = "containerInit";
EventType["particlesSetup"] = "particlesSetup";
EventType["containerStarted"] = "containerStarted";
EventType["containerStopped"] = "containerStopped";
EventType["containerDestroyed"] = "containerDestroyed";
EventType["containerPaused"] = "containerPaused";
EventType["containerPlay"] = "containerPlay";
EventType["containerBuilt"] = "containerBuilt";
EventType["particleAdded"] = "particleAdded";
EventType["particleDestroyed"] = "particleDestroyed";
EventType["particleRemoved"] = "particleRemoved";
})(exports.EventType || (exports.EventType = {}));
class PluginManager {
colorManagers = new Map();
easingFunctions = new Map();
effectDrawers = new Map();
initializers = {
effects: new Map(),
shapes: new Map(),
updaters: new Map(),
};
palettes = new Map();
plugins = [];
presets = new Map();
shapeDrawers = new Map();
updaters = new Map();
#allLoadersSet = new Set();
#configs = new Map();
#engine;
#executedSet = new Set();
#initialized = false;
#isRunningLoaders = false;
#loadPromises = new Set();
constructor(engine) {
this.#engine = engine;
}
get configs() {
const res = {};
for (const [name, config] of this.#configs) {
res[name] = config;
}
return res;
}
addColorManager(name, manager) {
this.colorManagers.set(name, manager);
}
addConfig(config) {
const key = config.key ?? config.name ?? "default";
this.#configs.set(key, config);
this.#engine.dispatchEvent(exports.EventType.configAdded, { data: { name: key, config } });
}
addEasing(name, easing) {
if (this.easingFunctions.get(name)) {
return;
}
this.easingFunctions.set(name, easing);
}
addEffect(effect, drawer) {
this.initializers.effects.set(effect, drawer);
}
addPalette(name, palette) {
this.palettes.set(name, palette);
}
addParticleUpdater(name, updaterInitializer) {
this.initializers.updaters.set(name, updaterInitializer);
}
addPlugin(plugin) {
if (this.getPlugin(plugin.id)) {
return;
}
this.plugins.push(plugin);
}
addPreset(preset, options, override = false) {
if (!(override || !this.getPreset(preset))) {
return;
}
this.presets.set(preset, options);
}
addShape(shapes, drawer) {
for (const shape of shapes) {
this.initializers.shapes.set(shape, drawer);
}
}
clearPlugins(container) {
this.effectDrawers.delete(container);
this.shapeDrawers.delete(container);
this.updaters.delete(container);
}
getEasing(name) {
return this.easingFunctions.get(name) ?? ((value) => value);
}
getEffectDrawers(container, force = false) {
return getItemMapFromInitializer(container, this.effectDrawers, this.initializers.effects, force);
}
getPalette(name) {
return this.palettes.get(name);
}
getPlugin(plugin) {
return this.plugins.find(t => t.id === plugin);
}
getPreset(preset) {
return this.presets.get(preset);
}
async getShapeDrawers(container, force = false) {
return getItemMapFromInitializer(container, this.shapeDrawers, this.initializers.shapes, force);
}
async getUpdaters(container, force = false) {
return getItemsFromInitializer(container, this.updaters, this.initializers.updaters, force);
}
async init() {
if (this.#initialized || this.#isRunningLoaders) {
return;
}
this.#isRunningLoaders = true;
this.#executedSet = new Set();
this.#allLoadersSet = new Set(this.#loadPromises);
try {
for (const loader of this.#allLoadersSet) {
await this.#runLoader(loader, this.#executedSet, this.#allLoadersSet);
}
}
finally {
this.#loadPromises.clear();
this.#isRunningLoaders = false;
this.#initialized = true;
}
}
loadParticlesOptions(container, options, ...sourceOptions) {
const updaters = this.updaters.get(container);
if (!updaters) {
return;
}
updaters.forEach(updater => updater.loadOptions?.(options, ...sourceOptions));
}
async register(...loaders) {
if (this.#initialized) {
throw new Error("Register plugins can only be done before calling tsParticles.load()");
}
for (const loader of loaders) {
if (this.#isRunningLoaders) {
await this.#runLoader(loader, this.#executedSet, this.#allLoadersSet);
}
else {
this.#loadPromises.add(loader);
}
}
}
async #runLoader(loader, executed, allLoaders) {
if (executed.has(loader))
return;
executed.add(loader);
allLoaders.add(loader);
await loader(this.#engine);
}
}
const errorPrefix = "tsParticles - Error";
const wrap = (fn) => (...args) => {
fn(...args);
}, _logger = {
debug: wrap(console.debug),
error: (message, ...optionalParams) => {
console.error(`${errorPrefix} - ${message}`, ...optionalParams);
},
info: wrap(console.info),
log: wrap(console.log),
trace: wrap(console.trace),
verbose: wrap(console.log),
warning: wrap(console.warn),
};
function setLogger(logger) {
if (logger.debug) {
_logger.debug = wrap(logger.debug);
}
if (logger.error) {
_logger.error = wrap(logger.error);
}
if (logger.info) {
_logger.info = wrap(logger.info);
}
if (logger.log) {
_logger.log = wrap(logger.log);
}
if (logger.trace) {
_logger.trace = wrap(logger.trace);
}
if (logger.verbose) {
_logger.verbose = wrap(logger.verbose);
}
if (logger.warning) {
_logger.warning = wrap(logger.warning);
}
}
function getLogger() {
return _logger;
}
const fullPercent = "100%";
async function getDataFromUrl(data) {
const url = itemFromSingleOrMultiple(data.url, data.index);
if (!url) {
return data.fallback;
}
const response = await fetch(url);
if (response.ok) {
return (await response.json());
}
getLogger().error(`${response.status.toString()} while retrieving config file`);
return data.fallback;
}
const getCanvasFromContainer = (domContainer) => {
const documentSafe = safeDocument();
let canvasEl;
const isCanvas = domContainer instanceof HTMLCanvasElement || domContainer.tagName.toLowerCase() === canvasTag;
if (isCanvas) {
canvasEl = domContainer;
canvasEl.dataset[generatedAttribute] ??= generatedFalse;
if (canvasEl.dataset[generatedAttribute] === generatedTrue) {
canvasEl.style.width ||= fullPercent;
canvasEl.style.height ||= fullPercent;
canvasEl.style.pointerEvents = "none";
canvasEl.style.setProperty("pointer-events", "none");
}
}
else {
const existingCanvases = domContainer.getElementsByTagName(canvasTag), foundCanvas = existingCanvases.item(canvasFirstIndex);
if (foundCanvas) {
canvasEl = foundCanvas;
canvasEl.dataset[generatedAttribute] = generatedFalse;
}
else {
canvasEl = documentSafe.createElement(canvasTag);
canvasEl.dataset[generatedAttribute] = generatedTrue;
domContainer.appendChild(canvasEl);
}
canvasEl.style.width ||= fullPercent;
canvasEl.style.height ||= fullPercent;
canvasEl.style.pointerEvents = "none";
canvasEl.style.setProperty("pointer-events", "none");
}
return canvasEl;
}, getDomContainer = (id, source) => {
const documentSafe = safeDocument();
let domContainer = source ?? documentSafe.getElementById(id);
if (domContainer) {
return domContainer;
}
domContainer = documentSafe.createElement("canvas");
domContainer.id = id;
domContainer.dataset[generatedAttribute] = generatedTrue;
documentSafe.body.append(domContainer);
return domContainer;
};
class Engine {
pluginManager = new PluginManager(this);
#domArray = [];
#eventDispatcher = new EventDispatcher();
#initialized = false;
get items() {
return this.#domArray;
}
get version() {
return "4.4.0";
}
addEventListener(type, listener) {
this.#eventDispatcher.addEventListener(type, listener);
}
checkVersion(pluginVersion) {
if (this.version === pluginVersion) {
return;
}
throw new Error(`The tsParticles version is different from the loaded plugins version. Engine version: ${this.version}. Plugin version: ${pluginVersion}`);
}
dispatchEvent(type, args) {
this.#eventDispatcher.dispatchEvent(type, args);
}
async init() {
if (this.#initialized) {
return;
}
await this.pluginManager.init();
this.#initialized = true;
}
item(index) {
const items = this.items, item = items[index];
if (item?.destroyed) {
items.splice(index, removeDeleteCount);
return;
}
return item;
}
async load(params) {
await this.init();
let domSourceElement;
if (typeof HTMLElement !== "undefined" && params.element instanceof HTMLElement) {
domSourceElement = params.element;
}
const { Container } = await Promise.resolve().then(function () { return Container$1; }), id = params.id ?? domSourceElement?.id ?? `tsparticles${Math.floor(getRandom() * loadRandomFactor).toString()}`, { index, url } = params, options = url ? await getDataFromUrl({ fallback: params.options, url, index }) : params.options, currentOptions = itemFromSingleOrMultiple(options, index), { items } = this, oldIndex = items.findIndex(v => v.id.description === id), newItem = new Container({
dispatchCallback: (eventType, args) => {
this.dispatchEvent(eventType, args);
},
id,
onDestroy: (remove) => {
if (!remove) {
return;
}
const mainArr = this.items, idx = mainArr.indexOf(newItem);
if (idx >= removeMinIndex) {
mainArr.splice(idx, removeDeleteCount);
}
},
pluginManager: this.pluginManager,
sourceOptions: currentOptions,
});
if (oldIndex >= loadMinIndex) {
const old = this.item(oldIndex), deleteCount = old ? one : none;
if (old && !old.destroyed) {
old.destroy(false);
}
items.splice(oldIndex, deleteCount, newItem);
}
else {
items.push(newItem);
}
const sourceCanvas = typeof OffscreenCanvas !== "undefined" && params.element instanceof OffscreenCanvas
? params.element
: getCanvasFromContainer(getDomContainer(id, domSourceElement));
newItem.canvas.loadCanvas(sourceCanvas);
await newItem.start();
return newItem;
}
async refresh(refresh = true) {
if (!refresh) {
return;
}
await Promise.all(this.items.map(t => t.refresh()));
}
removeEventListener(type, listener) {
this.#eventDispatcher.removeEventListener(type, listener);
}
}
function initEngine() {
const existing = globalThis.tsParticles;
if (existing?.pluginManager) {
return existing;
}
return new Engine();
}
var RangeType;
(function (RangeType) {
RangeType["circle"] = "circle";
RangeType["rectangle"] = "rectangle";
})(RangeType || (RangeType = {}));
class BaseRange {
position;
type;
constructor(x, y, type) {
this.position = {
x: x,
y: y,
};
this.type = type;
}
_resetPosition(x, y) {
this.position.x = x;
this.position.y = y;
}
}
class Circle extends BaseRange {
radius;
constructor(x, y, radius) {
super(x, y, RangeType.circle);
this.radius = radius;
}
contains(point) {
return checkDistance(point, this.position, this.radius);
}
intersects(range) {
const pos1 = this.position, pos2 = range.position, r = this.radius, dx = Math.abs(pos2.x - pos1.x), dy = Math.abs(pos2.y - pos1.y);
if (range instanceof Circle || range.type === RangeType.circle) {
const circleRange = range, rSum = r + circleRange.radius, dist = Math.hypot(dx, dy);
return rSum > dist;
}
else if (range instanceof Rectangle || range.type === RangeType.rectangle) {
const rectRange = range, { width, height } = rectRange.size, edges = Math.pow(dx - width, squareExp) + Math.pow(dy - height, squareExp);
return edges <= r ** squareExp || (dx <= r + width && dy <= r + height) || dx <= width || dy <= height;
}
return false;
}
reset(x, y, radius) {
this._resetPosition(x, y);
this.radius = radius;
return this;
}
}
class Rectangle extends BaseRange {
size;
constructor(x, y, width, height) {
super(x, y, RangeType.rectangle);
this.size = {
height: height,
width: width,
};
}
contains(point) {
const w = this.size.width, h = this.size.height, pos = this.position;
return point.x >= pos.x && point.x <= pos.x + w && point.y >= pos.y && point.y <= pos.y + h;
}
intersects(range) {
if (range instanceof Circle) {
return range.intersects(this);
}
if (!(range instanceof Rectangle)) {
return false;
}
const w = this.size.width, h = this.size.height, pos1 = this.position, pos2 = range.position, size2 = range.size, w2 = size2.width, h2 = size2.height;
return pos2.x < pos1.x + w && pos2.x + w2 > pos1.x && pos2.y < pos1.y + h && pos2.y + h2 > pos1.y;
}
reset(x, y, width, height) {
this._resetPosition(x, y);
this.size.width = width;
this.size.height = height;
return this;
}
}
exports.RotateDirection = void 0;
(function (RotateDirection) {
RotateDirection["clockwise"] = "clockwise";
RotateDirection["counterClockwise"] = "counter-clockwise";
RotateDirection["random"] = "random";
})(exports.RotateDirection || (exports.RotateDirection = {}));
exports.AnimationMode = void 0;
(function (AnimationMode) {
AnimationMode["auto"] = "auto";
AnimationMode["increase"] = "increase";
AnimationMode["decrease"] = "decrease";
AnimationMode["random"] = "random";
})(exports.AnimationMode || (exports.AnimationMode = {}));
exports.HdrMode = void 0;
(function (HdrMode) {
HdrMode["standard"] = "standard";
HdrMode["natural"] = "natural";
HdrMode["vivid"] = "vivid";
HdrMode["cinematic"] = "cinematic";
HdrMode["dynamic"] = "dynamic";
})(exports.HdrMode || (exports.HdrMode = {}));
exports.LimitMode = void 0;
(function (LimitMode) {
LimitMode["delete"] = "delete";
LimitMode["wait"] = "wait";
})(exports.LimitMode || (exports.LimitMode = {}));
exports.OutMode = void 0;
(function (OutMode) {
OutMode["bounce"] = "bounce";
OutMode["none"] = "none";
OutMode["out"] = "out";
OutMode["destroy"] = "destroy";
OutMode["split"] = "split";
})(exports.OutMode || (exports.OutMode = {}));
exports.AlterType = void 0;
(function (AlterType) {
AlterType["darken"] = "darken";
AlterType["enlighten"] = "enlighten";
})(exports.AlterType || (exports.AlterType = {}));
exports.DestroyType = void 0;
(function (DestroyType) {
DestroyType["none"] = "none";
DestroyType["max"] = "max";
DestroyType["min"] = "min";
})(exports.DestroyType || (exports.DestroyType = {}));
exports.GradientType = void 0;
(function (GradientType) {
GradientType["linear"] = "linear";
GradientType["radial"] = "radial";
GradientType["random"] = "random";
})(exports.GradientType || (exports.GradientType = {}));
exports.ParticleOutType = void 0;
(function (ParticleOutType) {
ParticleOutType["normal"] = "normal";
ParticleOutType["inside"] = "inside";
ParticleOutType["outside"] = "outside";
})(exports.ParticleOutType || (exports.ParticleOutType = {}));
exports.StartValueType = void 0;
(function (StartValueType) {
StartValueType["max"] = "max";
StartValueType["min"] = "min";
StartValueType["random"] = "random";
})(exports.StartValueType || (exports.StartValueType = {}));
exports.AnimationStatus = void 0;
(function (AnimationStatus) {
AnimationStatus["increasing"] = "increasing";
AnimationStatus["decreasing"] = "decreasing";
})(exports.AnimationStatus || (exports.AnimationStatus = {}));
exports.DrawLayer = void 0;
(function (DrawLayer) {
DrawLayer[DrawLayer["BackgroundElement"] = 0] = "BackgroundElement";
DrawLayer[DrawLayer["BackgroundDraw"] = 1] = "BackgroundDraw";
DrawLayer[DrawLayer["BackgroundMask"] = 2] = "BackgroundMask";
DrawLayer[DrawLayer["CanvasSetup"] = 3] = "CanvasSetup";
DrawLayer[DrawLayer["PluginContent"] = 4] = "PluginContent";
DrawLayer[DrawLayer["Particles"] = 5] = "Particles";
DrawLayer[DrawLayer["CanvasCleanup"] = 6] = "CanvasCleanup";
DrawLayer[DrawLayer["Foreground"] = 7] = "Foreground";
})(exports.DrawLayer || (exports.DrawLayer = {}));
class OptionLoader {
load(data) {
if (isNull(data)) {
return;
}
this.doLoad(data);
}
}
function loadOptions(options, ...sourceOptionsArr) {
for (const sourceOptions of sourceOptionsArr) {
options.load(sourceOptions);
}
}
function loadProperty(obj, key, value) {
if (value !== undefined) {
obj[key] = value;
}
}
function loadRangeProperty(obj, key, value) {
if (value !== undefined) {
obj[key] = setRangeValue(value);
}
}
function loadNestedProperty(obj, key, value) {
if (value !== undefined) {
obj[key].load(value);
}
}
function loadLazyProperty(obj, key, value, factory) {
if (value !== undefined) {
const objRecord = obj;
objRecord[key] ??= factory();
objRecord[key].load(value);
}
}
function loadExtendProperty(obj, key, value) {
if (value !== undefined) {
const keyStr = key;
if (keyStr === "__proto__" || keyStr === "constructor" || keyStr === "prototype") {
return;
}
const objRecord = obj, currentValue = Object.prototype.hasOwnProperty.call(objRecord, keyStr) ? objRecord[keyStr] : {};
objRecord[keyStr] = deepExtend(currentValue, value);
}
}
function loadOptionProperty(obj, key, optionClass, ...sources) {
const objRecord = obj;
objRecord[key] ??= new optionClass();
const target = objRecord[key];
for (const source of sources) {
target.load(source?.[key]);
}
}
class AnimationOptions extends OptionLoader {
count = 0;
decay = 0;
delay = 0;
enable = false;
speed = 1;
sync = false;
doLoad(data) {
loadRangeProperty(this, "count", data.count);
loadProperty(this, "enable", data.enable);
loadRangeProperty(this, "speed", data.speed);
loadRangeProperty(this, "decay", data.decay);
loadRangeProperty(this, "delay", data.delay);
loadProperty(this, "sync", data.sync);
}
}
class RangedAnimationOptions extends AnimationOptions {
mode = exports.AnimationMode.auto;
startValue = exports.StartValueType.random;
doLoad(data) {
super.doLoad(data);
loadProperty(this, "mode", data.mode);
loadProperty(this, "startValue", data.startValue);
}
}
class ColorAnimation extends AnimationOptions {
max;
min;
offset = 0;
sync = true;
constructor(min, max) {
super();
this.min = min;
this.max = max;
}
doLoad(data) {
super.doLoad(data);
loadProperty(this, "max", data.max);
loadProperty(this, "min", data.min);
loadRangeProperty(this, "offset", data.offset);
}
}
class HslAnimation extends OptionLoader {
h = new ColorAnimation(hMin, hMax);
l = new ColorAnimation(lMin, lMax);
s = new ColorAnimation(sMin, sMax);
doLoad(data) {
this.h.load(data.h);
this.s.load(data.s);
this.l.load(data.l);
}
}
class OptionsColor extends OptionLoader {
value = "";
static create(source, data) {
const color = new OptionsColor();
color.load(source);
if (data !== undefined) {
if (isString(data) || isArray(data)) {
color.load({ value: data });
}
else {
color.load(data);
}
}
return color;
}
doLoad(data) {
if (!isNull(data.value)) {
this.value = data.value;
}
}
}
class AnimatableColor extends OptionsColor {
animation = new HslAnimation();
static create(source, data) {
const color = new AnimatableColor();
color.load(source);
if (data !== undefined) {
if (isString(data) || isArray(data)) {
color.load({ value: data });
}
else {
color.load(data);
}
}
return color;
}
doLoad(data) {
super.doLoad(data);
const colorAnimation = data.animation;
if (colorAnimation !== undefined) {
if (colorAnimation.enable === undefined) {
this.animation.load(data.animation);
}
else {
this.animation.h.load(colorAnimation);
}
}
}
}
class Background extends OptionLoader {
color;
draw;
element;
image = "";
opacity = 1;
position = "";
repeat = "";
size = "";
constructor() {
super();
this.color = new OptionsColor();
this.color.value = "";
}
doLoad(data) {
if (data.color !== undefined) {
this.color = OptionsColor.create(this.color, data.color);
}
loadProperty(this, "element", data.element);
loadProperty(this, "draw", data.draw);
loadProperty(this, "image", data.image);
loadProperty(this, "position", data.position);
loadProperty(this, "repeat", data.repeat);
loadProperty(this, "size", data.size);
loadProperty(this, "opacity", data.opacity);
}
}
class FullScreen extends OptionLoader {
enable = true;
zIndex = 0;
doLoad(data) {
loadProperty(this, "enable", data.enable);
loadProperty(this, "zIndex", data.zIndex);
}
}
const defaultPeakNits = 400, minPeakNits = 0, toStringValue = (value) => String(value);
class HDROptions extends OptionLoader {
enable = true;
mode = exports.HdrMode.standard;
peakNits = defaultPeakNits;
doLoad(data) {
if (data.enable !== undefined && !isBoolean(data.enable)) {
throw new Error(`Invalid HDR "enable" value: expected a boolean, got "${String(data.enable)}"`);
}
if (data.mode !== undefined &&
(typeof data.mode !== "string" || !Object.values(exports.HdrMode).includes(data.mode))) {
throw new Error(`Invalid HDR "mode" value: expected one of ${Object.values(exports.HdrMode).join(", ")}, got "${toStringValue(data.mode)}"`);
}
if (data.peakNits !== undefined &&
(!isNumber(data.peakNits) || !Number.isFinite(data.peakNits) || data.peakNits <= minPeakNits)) {
throw new Error(`Invalid HDR "peakNits" value: expected a positive number, got "${String(data.peakNits)}"`);
}
loadProperty(this, "enable", data.enable);
loadProperty(this, "mode", data.mode);
loadProperty(this, "peakNits", data.peakNits);
}
}
class ResizeEvent extends OptionLoader {
delay = 0.5;
enable = true;
doLoad(data) {
loadProperty(this, "delay", data.delay);
loadProperty(this, "enable", data.enable);
}
}
class Effect extends OptionLoader {
close = true;
options = {};
type = [];
doLoad(data) {
const options = data.options;
if (options !== undefined) {
for (const effect in options) {
const item = options[effect];
if (item) {
this.options[effect] = deepExtend(this.options[effect] ?? {}, item);
}
}
}
loadProperty(this, "close", data.close);
loadProperty(this, "type", data.type);
}
}
class Fill extends OptionLoader {
color;
enable = true;
opacity = 1;
doLoad(data) {
if (data.color !== undefined) {
this.color = AnimatableColor.create(this.color, data.color);
}
loadProperty(this, "enable", data.enable);
loadRangeProperty(this, "opacity", data.opacity);
}
}
class MoveAngle extends OptionLoader {
offset = 0;
value = 90;
doLoad(data) {
loadRangeProperty(this, "offset", data.offset);
loadRangeProperty(this, "value", data.value);
}
}
class MoveCenter extends OptionLoader {
mode = exports.PixelMode.percent;
radius = 0;
x = 50;
y = 50;
doLoad(data) {
loadProperty(this, "x", data.x);
loadProperty(this, "y", data.y);
loadProperty(this, "mode", data.mode);
loadProperty(this, "radius", data.radius);
}
}
class MoveGravity extends OptionLoader {
acceleration = 9.81;
enable = false;
inverse = false;
maxSpeed = 50;
doLoad(data) {
loadRangeProperty(this, "acceleration", data.acceleration);
loadProperty(this, "enable", data.enable);
loadProperty(this, "inverse", data.inverse);
loadRangeProperty(this, "maxSpeed", data.maxSpeed);
}
}
class ValueWithRandom extends OptionLoader {
value = 0;
doLoad(data) {
if (!isNull(data.value)) {
this.value = setRangeValue(data.value);
}
}
}
class AnimationValueWithRandom extends ValueWithRandom {
animation = new AnimationOptions();
doLoad(data) {
super.doLoad(data);
loadNestedProperty(this, "animation", data.animation);
}
}
class RangedAnimationValueWithRandom extends AnimationValueWithRandom {
animation = new RangedAnimationOptions();
}
class MovePath extends OptionLoader {
clamp = true;
delay = new ValueWithRandom();
enable = false;
generator;
options = {};
doLoad(data) {
loadProperty(this, "clamp", data.clamp);
this.delay.load(data.delay);
loadProperty(this, "enable", data.enable);
this.generator = data.generator;
if (data.options) {
this.options = deepExtend(this.options, data.options);
}
}
}
class OutModes extends OptionLoader {
bottom;
default = exports.OutMode.out;
left;
right;
top;
doLoad(data) {
if (data.default !== undefined) {
this.default = data.default;
}
this.bottom = data.bottom ?? data.default;
this.left = data.left ?? data.default;
this.right = data.right ?? data.default;
this.top = data.top ?? data.default;
}
}
class Spin extends OptionLoader {
acceleration = 0;
enable = false;
position;
doLoad(data) {
loadRangeProperty(this, "acceleration", data.acceleration);
loadProperty(this, "enable", data.enable);
if (data.position) {
this.position = deepExtend({}, data.position);
}
}
}
class Move extends OptionLoader {
angle = new MoveAngle();
center = new MoveCenter();
decay = 0;
direction = exports.MoveDirection.none;
distance = {};
drift = 0;
enable = false;
gravity = new MoveGravity();
outModes = new OutModes();
path = new MovePath();
random = false;
size = false;
speed = 2;
spin = new Spin();
straight = false;
vibrate = false;
warp = false;
doLoad(data) {
this.angle.load(isNumber(data.angle) ? { value: data.angle } : data.angle);
this.center.load(data.center);
loadRangeProperty(this, "decay", data.decay);
loadProperty(this, "direction", data.direction);
if (data.distance !== undefined) {
this.distance = isNumber(data.distance)
? {
horizontal: data.distance,
vertical: data.distance,
}
: { ...data.distance };
}
loadRangeProperty(this, "drift", data.drift);
loadProperty(this, "enable", data.enable);
this.gravity.load(data.gravity);
const outModes = data.outModes;
if (outModes !== undefined) {
if (isObject(outModes)) {
this.outModes.load(outModes);
}
else {
this.outModes.load({
default: outModes,
});
}
}
this.path.load(data.path);
loadProperty(this, "random", data.random);
loadProperty(this, "size", data.size);
loadRangeProperty(this, "speed", data.speed);
this.spin.load(data.spin);
loadProperty(this, "straight", data.straight);
loadProperty(this, "vibrate", data.vibrate);
loadProperty(this, "warp", data.warp);
}
}
class Stroke extends OptionLoader {
color;
opacity;
width = 0;
doLoad(data) {
if (data.color !== undefined) {
this.color = AnimatableColor.create(this.color, data.color);
}
loadRangeProperty(this, "width", data.width);
loadRangeProperty(this, "opacity", data.opacity);
}
}
class Paint extends OptionLoader {
color;
fill;
stroke;
doLoad(data) {
if (data.color !== undefined) {
this.color = AnimatableColor.create(this.color, data.color);
}
loadLazyProperty(this, "fill", data.fill, () => new Fill());
loadLazyProperty(this, "stroke", data.stroke, () => new Stroke());
}
}
class ParticlesBounceFactor extends ValueWithRandom {
value = 1;
}
class ParticlesBounce extends OptionLoader {
horizontal = new ParticlesBounceFactor();
vertical = new ParticlesBounceFactor();
doLoad(data) {
this.horizontal.load(data.horizontal);
this.vertical.load(data.vertical);
}
}
class ParticlesDensity extends OptionLoader {
enable = false;
height = 1080;
width = 1920;
doLoad(data) {
loadProperty(this, "enable", data.enable);
loadProperty(this, "width", data.width);
loadProperty(this, "height", data.height);
}
}
class ParticlesNumberLimit extends OptionLoader {
mode = exports.LimitMode.delete;
value = 0;
doLoad(data) {
loadProperty(this, "mode", data.mode);
loadProperty(this, "value", data.value);
}
}
class ParticlesNumber extends OptionLoader {
density = new ParticlesDensity();
limit = new ParticlesNumberLimit();
value = 0;
doLoad(data) {
this.density.load(data.density);
this.limit.load(data.limit);
loadProperty(this, "value", data.value);
}
}
class Shape extends OptionLoader {
close = true;
options = {};
type = "circle";
doLoad(data) {
const options = data.options;
if (options !== undefined) {
for (const shape in options) {
const item = options[shape];
if (item) {
this.options[shape] = deepExtend(this.options[shape] ?? {}, item);
}
}
}
loadProperty(this, "close", data.close);
loadProperty(this, "type", data.type);
}
}
class ZIndex extends ValueWithRandom {
opacityRate = 1;
sizeRate = 1;
velocityRate = 1;
doLoad(data) {
super.doLoad(data);
loadProperty(this, "opacityRate", data.opacityRate);
loadProperty(this, "sizeRate", data.sizeRate);
loadProperty(this, "velocityRate", data.velocityRate);
}
}
class ParticlesOptions extends OptionLoader {
bounce = new ParticlesBounce();
effect = new Effect();
groups = {};
move = new Move();
number = new ParticlesNumber();
paint;
palette;
reduceDuplicates = false;
shape = new Shape();
zIndex = new ZIndex();
#container;
#pluginManager;
constructor(pluginManager, container) {
super();
this.#pluginManager = pluginManager;
this.#container = container;
this.paint = new Paint();
this.paint.color = new AnimatableColor();
this.paint.color.value = "#fff";
this.paint.fill = new Fill();
this.paint.fill.enable = true;
}
doLoad(data) {
if (data.palette) {
this.palette = data.palette;
this.#importPalette(this.palette);
}
if (data.groups !== undefined) {
for (const group of Object.keys(data.groups)) {
if (!(group in data.groups)) {
continue;
}
const item = data.groups[group];
if (item !== undefined) {
this.groups[group] = deepExtend(this.groups[group] ?? {}, item);
}
}
}
if (data.reduceDuplicates !== undefined) {
this.reduceDuplicates = data.reduceDuplicates;
}
this.bounce.load(data.bounce);
this.effect.load(data.effect);
this.move.load(data.move);
this.number.load(data.number);
const paintToLoad = data.paint;
if (paintToLoad) {
if (isArray(paintToLoad)) {
this.paint = executeOnSingleOrMultiple(paintToLoad, t => {
const tmp = new Paint();
tmp.load(t);
return tmp;
});
}
else if (isArray(this.paint)) {
this.paint = new Paint();
this.paint.load(paintToLoad);
}
else {
this.paint.load(paintToLoad);
}
}
this.shape.load(data.shape);
this.zIndex.load(data.zIndex);
if (this.#container) {
for (const plugin of this.#pluginManager.plugins) {
if (plugin.loadParticlesOptions) {
plugin.loadParticlesOptions(this.#container, this, data);
}
}
const updaters = this.#pluginManager.updaters.get(this.#container);
if (updaters) {
for (const updater of updaters) {
if (updater.loadOptions) {
updater.loadOptions(this, data);
}
}
}
}
}
#importPalette(palette) {
const paletteData = this.#pluginManager.getPalette(palette);
if (!paletteData) {
return;
}
const paletteColors = paletteData.colors, defaultPaintStrokeWidth = 0, defaultPaintVariantsLength = 1, firstPaintVariantIndex = 0, defaultPalettePaintVariant = {}, colorVariants = isArray(paletteColors) ? paletteColors : [paletteColors], palettePaintVariants = colorVariants.flatMap(variant => {
const paletteFill = variant.fill, paletteStroke = variant.stroke, fillPart = paletteFill
? {
color: {
value: paletteFill.value,
},
enable: paletteFill.enable,
opacity: paletteFill.opacity,
}
: undefined;
if (!paletteStroke) {
return [
{
fill: fillPart,
},
];
}
return [
{
fill: fillPart,
stroke: {
color: {
value: paletteStroke.value,
},
opacity: paletteStroke.opacity,
width: paletteStroke.width || defaultPaintStrokeWidth,
},
},
];
}), palettePaint = palettePaintVariants.length > defaultPaintVariantsLength
? palettePaintVariants
: (palettePaintVariants[firstPaintVariantIndex] ?? defaultPalettePaintVariant);
this.load({
paint: palettePaint,
blend: {
enable: true,
mode: paletteData.blendMode,
},
});
}
}
function loadParticlesOptions(pluginManager, container, ...sourceOptionsArr) {
const options = new ParticlesOptions(pluginManager, container);
loadOptions(options, ...sourceOptionsArr);
return options;
}
class Options extends OptionLoader {
autoPlay = true;
background;
clear = true;
defaultThemes = {};
delay = 0;
detectRetina = true;
duration = 0;
fpsLimit = 120;
fullScreen;
hdr;
key;
name;
palette;
particles;
pauseOnBlur = true;
pauseOnOutsideViewport = true;
preset;
resize;
smooth = false;
style = {};
zLayers = 100;
#container;
#pluginManager;
constructor(pluginManager, container) {
super();
this.#pluginManager = pluginManager;
this.#container = container;
this.background = new Background();
this.fullScreen = new FullScreen();
this.hdr = new HDROptions();
this.particles = loadParticlesOptions(this.#pluginManager, this.#container);
this.resize = new ResizeEvent();
}
doLoad(data) {
if (data.preset !== undefined) {
this.preset = data.preset;
executeOnSingleOrMultiple(this.preset, preset => {
this.#importPreset(preset);
});
}
if (data.palette !== undefined) {
this.palette = data.palette;
this.#importPalette(this.palette);
}
loadProperty(this, "autoPlay", data.autoPlay);
loadProperty(this, "clear", data.clear);
loadProperty(this, "key", data.key);
loadProperty(this, "name", data.name);
loadRangeProperty(this, "delay", data.delay);
loadProperty(this, "detectRetina", data.detectRetina);
loadRangeProperty(this, "duration", data.duration);
loadProperty(this, "fpsLimit", data.fpsLimit);
const hdrData = data.hdr;
if (isBoolean(hdrData)) {
this.hdr.enable = hdrData;
this.hdr.mode = exports.HdrMode.standard;
this.hdr.peakNits = 400;
}
else {
this.hdr.load(hdrData);
}
loadProperty(this, "pauseOnBlur", data.pauseOnBlur);
loadProperty(this, "pauseOnOutsideViewport", data.pauseOnOutsideViewport);
loadProperty(this, "zLayers", data.zLayers);
this.background.load(data.background);
const fullScreen = data.fullScreen;
if (isBoolean(fullScreen)) {
this.fullScreen.enable = fullScreen;
}
else {
this.fullScreen.load(fullScreen);
}
this.particles.load(data.particles);
this.resize.load(data.resize);
this.style = deepExtend(this.style, data.style);
loadProperty(this, "smooth", data.smooth);
this.#pluginManager.plugins.forEach(plugin => {
plugin.loadOptions(this.#container, this, data);
});
}
#importPalette(palette) {
const paletteData = this.#pluginManager.getPalette(palette);
if (!paletteData) {
return;
}
this.load({
background: {
color: paletteData.background,
},
blend: {
enable: true,
mode: paletteData.blendMode,
},
particles: {
palette,
},
});
}
#importPreset(preset) {
this.load(this.#pluginManager.getPreset(preset));
}
}
const styleCache = new Map(), maxStyleCacheSize = 2000, rgbFixedPrecision = 2, hslFixedPrecision = 2, hdrRgbFixedPrecision = 4, hdrHslFixedPrecision = 4, sdrReferenceWhiteNits = 203, acesA = 2.51, acesB = 0.03, acesC = 2.43, acesD = 0.59, acesE = 0.14, saturationBoost = 1.15, lightnessBoost = 1.05, temperatureR = 1.05, temperatureB = 0.95, contrastFactor = 1.1, luminanceR = 0.2126, luminanceG = 0.7152, luminanceB = 0.0722, dynamicLuminanceFactor = 2, channelCount = 3, srgbThreshold = 0.04045, srgbDivisor = 12.92, srgbOffset = 0.055, srgbScale = 1.055, srgbExponent = 2.4, linearThreshold = 0.0031308, linearScale = 12.92;
function getCachedStyle(key, generator) {
let cached = styleCache.get(key);
if (!cached) {
cached = generator();
if (styleCache.size > maxStyleCacheSize) {
styleCache.clear();
}
styleCache.set(key, cached);
}
return cached;
}
function acesFilmic(x) {
return clamp((x * (acesA * x + acesB)) / (x * (acesC * x + acesD) + acesE), none, one);
}
function srgbToLinear(c) {
return c <= srgbThreshold ? c / srgbDivisor : Math.pow((c + srgbOffset) / srgbScale, srgbExponent);
}
function linearToSrgb(c) {
return c <= linearThreshold ? c * linearScale : srgbScale * Math.pow(c, one / srgbExponent) - srgbOffset;
}
function applyHdrModeAdjustments(r, g, b, mode, maxChannel) {
switch (mode) {
case exports.HdrMode.vivid: {
const avg = (r + g + b) / channelCount;
return {
b: clamp(clamp(avg + (b - avg) * saturationBoost, none, maxChannel) * lightnessBoost, none, maxChannel),
g: clamp(clamp(avg + (g - avg) * saturationBoost, none, maxChannel) * lightnessBoost, none, maxChannel),
r: clamp(clamp(avg + (r - avg) * saturationBoost, none, maxChannel) * lightnessBoost, none, maxChannel),
};
}
case exports.HdrMode.cinematic: {
const tempR = r * temperatureR, tempB = b * temperatureB, avg = (tempR + g + tempB) / channelCount;
return {
b: clamp(avg + (tempB - avg) * contrastFactor, none, maxChannel),
g: clamp(avg + (g - avg) * contrastFactor, none, maxChannel),
r: clamp(avg + (tempR - avg) * contrastFactor, none, maxChannel),
};
}
case exports.HdrMode.dynamic: {
const luminance = luminanceR * r + luminanceG * g + luminanceB * b, vividWeight = Math.min(one, luminance * dynamicLuminanceFactor), naturalWeight = one - vividWeight, avg = (r + g + b) / channelCount, vividR = clamp(avg + (r - avg) * saturationBoost, none, maxChannel) * lightnessBoost, vividG = clamp(avg + (g - avg) * saturationBoost, none, maxChannel) * lightnessBoost, vividB = clamp(avg + (b - avg) * saturationBoost, none, maxChannel) * lightnessBoost;
return {
b: clamp(b * naturalWeight + vividB * vividWeight, none, maxChannel),
g: clamp(g * naturalWeight + vividG * vividWeight, none, maxChannel),
r: clamp(r * naturalWeight + vividR * vividWeight, none, maxChannel),
};
}
case exports.HdrMode.standard:
return { b, g, r };
case exports.HdrMode.natural:
default:
return { b, g, r };
}
}
function stringToRgba(pluginManager, input) {
if (!input) {
return;
}
for (const manager of pluginManager.colorManagers.values()) {
if (manager.accepts(input)) {
return manager.parseString(input);
}
}
return undefined;
}
function rangeColorToRgb(pluginManager, input, index, useIndex = true) {
if (!input) {
return;
}
const color = isString(input) ? { value: input } : input;
if (isString(color.value)) {
return colorToRgb(pluginManager, color.value, index, useIndex);
}
if (isArray(color.value)) {
const value = itemFromArray(color.value, index, useIndex);
if (!value) {
return;
}
return rangeColorToRgb(pluginManager, {
value,
});
}
for (const manager of pluginManager.colorManagers.values()) {
const res = manager.handleRangeColor(color);
if (res) {
return res;
}
}
return undefined;
}
function colorToRgb(pluginManager, input, index, useIndex = true) {
if (!input) {
return;
}
const color = isString(input) ? { value: input } : input;
if (isString(color.value)) {
return color.value === randomColorValue ? getRandomRgbColor() : stringToRgb(pluginManager, color.value);
}
if (isArray(color.value)) {
const value = itemFromArray(color.value, index, useIndex);
if (!value) {
return;
}
return colorToRgb(pluginManager, {
value,
});
}
for (const manager of pluginManager.colorManagers.values()) {
const res = manager.handleColor(color);
if (res) {
return res;
}
}
return undefined;
}
function rangeColorToHsl(pluginManager, color, index, useIndex = true) {
const rgb = rangeColorToRgb(pluginManager, color, index, useIndex);
return rgb ? rgbToHsl(rgb) : undefined;
}
function rgbToHsl(color) {
const r1 = color.r / rgbMax, g1 = color.g / rgbMax, b1 = color.b / rgbMax, max = Math.max(r1, g1, b1), min = Math.min(r1, g1, b1), res = {
h: hMin,
l: (max + min) * half,
s: sMin,
};
if (max !== min) {
res.s = res.l < half ? (max - min) / (max + min) : (max - min) / (double - max - min);
if (r1 === max) {
res.h = (g1 - b1) / (max - min);
}
else if (g1 === max) {
res.h = double + (b1 - r1) / (max - min);
}
else {
res.h = double * double + (r1 - g1) / (max - min);
}
}
res.l *= lMax;
res.s *= sMax;
res.h *= hPhase;
if (res.h < hMin) {
res.h += hMax;
}
if (res.h >= hMax) {
res.h -= hMax;
}
return res;
}
function stringToAlpha(pluginManager, input) {
return stringToRgba(pluginManager, input)?.a;
}
function stringToRgb(pluginManager, input) {
return stringToRgba(pluginManager, input);
}
function hslChannel(temp1, temp2, temp3) {
const temp3Min = 0, temp3Max = 1;
if (temp3 < temp3Min) {
temp3++;
}
if (temp3 > temp3Max) {
temp3--;
}
if (temp3 * sextuple < temp3Max) {
return temp1 + (temp2 - temp1) * sextuple * temp3;
}
if (temp3 * double < temp3Max) {
return temp2;
}
if (temp3 * triple < temp3Max * double) {
const temp3Offset = double / triple;
return temp1 + (temp2 - temp1) * (temp3Offset - temp3) * sextuple;
}
return temp1;
}
function hslToRgb(hsl) {
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;
if (s === sMin) {
const grayscaleValue = Math.round(lNormalized * rgbMax);
return { r: grayscaleValue, g: grayscaleValue, b: grayscaleValue };
}
const temp1 = lNormalized < half
? lNormalized * (sNormalizedOffset + sNormalized)
: 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));
return { r: Math.round(red), g: Math.round(green), b: Math.round(blue) };
}
function hslToRgbFloat(hsl) {
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;
if (s === sMin) {
const grayscaleValue = lNormalized * rgbMax;
return { r: grayscaleValue, g: grayscaleValue, b: grayscaleValue };
}
const temp1 = lNormalized < half
? lNormalized * (sNormalizedOffset + sNormalized)
: 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));
return { r: red, g: green, b: blue };
}
function hslaToRgba(hsla) {
const rgbResult = hslToRgb(hsla);
return {
a: hsla.a,
b: rgbResult.b,
g: rgbResult.g,
r: rgbResult.r,
};
}
function getRandomRgbColor(min, hdr) {
const fixedMin = min ?? defaultRgbMin;
if (hdr) {
return {
r: getRandomInRange(fixedMin, rgbMax),
g: getRandomInRange(fixedMin, rgbMax),
b: getRandomInRange(fixedMin, rgbMax),
};
}
const fixedMax = rgbMax + identity$2, getRgbInRangeValue = () => Math.floor(getRandomInRange(fixedMin, fixedMax));
return {
b: getRgbInRangeValue(),
g: getRgbInRangeValue(),
r: getRgbInRangeValue(),
};
}
function getStyleFromRgb(color, hdr, opacity, peakNits, mode) {
const rgbPrecision = hdr ? hdrRgbFixedPrecision : rgbFixedPrecision, op = opacity ?? defaultOpacity$3, key = hdr
? `rgb-${color.r.toFixed(rgbPrecision)}-${color.g.toFixed(rgbPrecision)}-${color.b.toFixed(rgbPrecision)}-hdr-${op.toString()}-${(peakNits ?? maxNits).toString()}-${mode ?? exports.HdrMode.standard}`
: `rgb-${color.r.toFixed(rgbPrecision)}-${color.g.toFixed(rgbPrecision)}-${color.b.toFixed(rgbPrecision)}-sdr-${op.toString()}`;
return getCachedStyle(key, () => hdr ? getHdrStyleFromRgb(color, opacity, peakNits, mode) : getSdrStyleFromRgb(color, opacity));
}
function getHdrStyleFromRgb(color, opacity, peakNits = maxNits, mode = exports.HdrMode.standard) {
const headroom = peakNits / sdrReferenceWhiteNits, middleGray = 0.18, mapped = hdrToneMapColor(color, headroom, middleGray, mode);
return `color(display-p3 ${mapped.r.toString()} ${mapped.g.toString()} ${mapped.b.toString()} / ${(opacity ?? defaultOpacity$3).toString()})`;
}
function hdrToneMapColor(color, headroom, middleGray, mode) {
const rNorm = color.r / rgbMax, gNorm = color.g / rgbMax, bNorm = color.b / rgbMax;
if (mode !== exports.HdrMode.natural && mode !== exports.HdrMode.vivid && mode !== exports.HdrMode.cinematic && mode !== exports.HdrMode.dynamic) {
return { b: bNorm, g: gNorm, r: rNorm };
}
const maxChannel = Math.max(one, headroom), luminance = luminanceR * rNorm + luminanceG * gNorm + luminanceB * bNorm, mappedLuminance = hdrToneMap(luminance, headroom, middleGray), scale = luminance > none ? mappedLuminance / luminance : one;
return applyHdrModeAdjustments(clamp(rNorm * scale, none, maxChannel), clamp(gNorm * scale, none, maxChannel), clamp(bNorm * scale, none, maxChannel), mode, maxChannel);
}
function hdrToneMap(normalized, headroom, middleGray) {
if (normalized <= middleGray) {
return acesFilmic(normalized);
}
const expanded = normalized + (normalized - middleGray) * (headroom - one);
return acesFilmic(expanded);
}
function getSdrStyleFromRgb(color, opacity) {
return `rgba(${color.r.toString()}, ${color.g.toString()}, ${color.b.toString()}, ${(opacity ?? defaultOpacity$3).toString()})`;
}
function getStyleFromHsl(color, hdr, opacity, peakNits, mode) {
const hslPrecision = hdr ? hdrHslFixedPrecision : hslFixedPrecision, op = opacity ?? defaultOpacity$3, key = hdr
? `hsl-${color.h.toFixed(hslPrecision)}-${color.s.toFixed(hslPrecision)}-${color.l.toFixed(hslPrecision)}-hdr-${op.toString()}-${(peakNits ?? maxNits).toString()}-${mode ?? exports.HdrMode.standard}`
: `hsl-${color.h.toFixed(hslPrecision)}-${color.s.toFixed(hslPrecision)}-${color.l.toFixed(hslPrecision)}-sdr-${op.toString()}`;
return getCachedStyle(key, () => hdr
? getStyleFromRgb(hslToRgbFloat(color), true, opacity, peakNits, mode)
: `hsla(${color.h.toString()}, ${color.s.toString()}%, ${color.l.toString()}%, ${op.toString()})`);
}
function colorMix(color1, color2, size1, size2, hdr) {
let rgb1 = color1, rgb2 = color2;
if (!("r" in rgb1)) {
rgb1 = hdr ? hslToRgbFloat(color1) : hslToRgb(color1);
}
if (!("r" in rgb2)) {
rgb2 = hdr ? hslToRgbFloat(color2) : hslToRgb(color2);
}
if (hdr) {
const lin1 = {
b: srgbToLinear(rgb1.b / rgbMax),
g: srgbToLinear(rgb1.g / rgbMax),
r: srgbToLinear(rgb1.r / rgbMax),
}, lin2 = {
b: srgbToLinear(rgb2.b / rgbMax),
g: srgbToLinear(rgb2.g / rgbMax),
r: srgbToLinear(rgb2.r / rgbMax),
}, totalWeight = size1 + size2;
return {
b: linearToSrgb((lin1.b * size1 + lin2.b * size2) / totalWeight) * rgbMax,
g: linearToSrgb((lin1.g * size1 + lin2.g * size2) / totalWeight) * rgbMax,
r: linearToSrgb((lin1.r * size1 + lin2.r * size2) / totalWeight) * rgbMax,
};
}
return {
b: mix(rgb1.b, rgb2.b, size1, size2),
g: mix(rgb1.g, rgb2.g, size1, size2),
r: mix(rgb1.r, rgb2.r, size1, size2),
};
}
function getLinkColor(p1, p2, linkColor, hdr) {
if (linkColor === randomColorValue) {
return getRandomRgbColor();
}
else if (linkColor === midColorValue) {
const sourceColor = p1.getFillColor() ?? p1.getStrokeColor(), destColor = p2?.getFillColor() ?? p2?.getStrokeColor();
if (sourceColor && destColor && p2) {
return colorMix(sourceColor, destColor, p1.getRadius(), p2.getRadius(), hdr);
}
else {
const hslColor = sourceColor ?? destColor;
if (hslColor) {
return hslToRgb(hslColor);
}
}
}
else {
return linkColor;
}
return undefined;
}
function getLinkRandomColor(pluginManager, optColor, blink, consent) {
const color = isString(optColor) ? optColor : optColor.value;
if (color === randomColorValue) {
if (consent) {
return rangeColorToRgb(pluginManager, {
value: color,
});
}
if (blink) {
return randomColorValue;
}
return midColorValue;
}
else if (color === midColorValue) {
return midColorValue;
}
else {
return rangeColorToRgb(pluginManager, {
value: color,
});
}
}
function getHslFromAnimation(animation) {
return animation === undefined
? undefined
: {
h: animation.h.value,
s: animation.s.value,
l: animation.l.value,
};
}
function getHslAnimationFromHsl(hsl, animationOptions, reduceFactor) {
const resColor = {
h: {
enable: false,
value: hsl.h,
min: hMin,
max: hMax,
},
s: {
enable: false,
value: hsl.s,
min: sMin,
max: sMax,
},
l: {
enable: false,
value: hsl.l,
min: lMin,
max: lMax,
},
};
if (animationOptions) {
setColorAnimation(resColor.h, animationOptions.h, reduceFactor);
setColorAnimation(resColor.s, animationOptions.s, reduceFactor);
setColorAnimation(resColor.l, animationOptions.l, reduceFactor);
}
return resColor;
}
function setColorAnimation(colorValue, colorAnimation, reduceFactor) {
colorValue.enable = colorAnimation.enable;
colorValue.min = colorAnimation.min;
colorValue.max = colorAnimation.max;
if (colorValue.enable) {
colorValue.velocity = (getRangeValue(colorAnimation.speed) / percentDenominator) * reduceFactor;
colorValue.decay = decayOffset - getRangeValue(colorAnimation.decay);
colorValue.status = exports.AnimationStatus.increasing;
colorValue.loops = defaultLoops;
colorValue.maxLoops = getRangeValue(colorAnimation.count);
colorValue.time = defaultTime;
colorValue.delayTime = getRangeValue(colorAnimation.delay) * millisecondsToSeconds;
if (!colorAnimation.sync) {
colorValue.velocity *= getRandom();
colorValue.value *= getRandom();
}
colorValue.initialValue = colorValue.value;
colorValue.offset = setRangeValue(colorAnimation.offset);
}
else {
colorValue.velocity = defaultVelocity;
}
}
function updateColorValue(data, decrease, delta, _hdr) {
const minLoops = 0, minDelay = 0, identity = 1, minVelocity = 0, minOffset = 0, velocityFactor = 3.6;
if (!data.enable ||
((data.maxLoops ?? minLoops) > minLoops && (data.loops ?? minLoops) > (data.maxLoops ?? minLoops))) {
return;
}
data.time ??= 0;
if ((data.delayTime ?? minDelay) > minDelay && data.time < (data.delayTime ?? minDelay)) {
data.time += delta.value;
}
if ((data.delayTime ?? minDelay) > minDelay && data.time < (data.delayTime ?? minDelay)) {
return;
}
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;
if (!decrease || data.status === exports.AnimationStatus.increasing) {
data.value += velocity;
if (data.value > max) {
data.loops ??= 0;
data.loops++;
if (decrease) {
data.status = exports.AnimationStatus.decreasing;
}
else {
data.value -= max;
}
}
}
else {
data.value -= velocity;
if (data.value < min) {
data.loops ??= 0;
data.loops++;
data.status = exports.AnimationStatus.increasing;
}
}
if (data.velocity && decay !== identity) {
data.velocity *= decay;
}
data.value = clamp(data.value, min, max);
}
function updateColor(color, delta, hdr) {
if (!color) {
return;
}
const { h, s, l } = color;
updateColorValue(h, false, delta);
updateColorValue(s, true, delta);
updateColorValue(l, true, delta);
}
function alterHsl(color, type, value) {
return {
h: color.h,
s: color.s,
l: color.l + (type === exports.AlterType.darken ? -lFactor : lFactor) * value,
};
}
const tsParticles = initEngine();
class ClickEvent {
enable = false;
mode = [];
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "enable", data.enable);
loadProperty(this, "mode", data.mode);
}
}
var DivType;
(function (DivType) {
DivType["circle"] = "circle";
DivType["rectangle"] = "rectangle";
})(DivType || (DivType = {}));
class DivEvent {
enable = false;
mode = [];
selectors = [];
type = DivType.circle;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "selectors", data.selectors);
loadProperty(this, "enable", data.enable);
loadProperty(this, "mode", data.mode);
loadProperty(this, "type", data.type);
}
}
class HoverEvent {
enable = false;
mode = [];
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "enable", data.enable);
loadProperty(this, "mode", data.mode);
}
}
class Events {
onClick = new ClickEvent();
onDiv = new DivEvent();
onHover = new HoverEvent();
load(data) {
if (isNull(data)) {
return;
}
this.onClick.load(data.onClick);
const onDiv = data.onDiv;
if (onDiv !== undefined) {
this.onDiv = executeOnSingleOrMultiple(onDiv, t => {
const tmp = new DivEvent();
tmp.load(t);
return tmp;
});
}
this.onHover.load(data.onHover);
}
}
var InteractivityDetect;
(function (InteractivityDetect) {
InteractivityDetect["canvas"] = "canvas";
InteractivityDetect["parent"] = "parent";
InteractivityDetect["window"] = "window";
})(InteractivityDetect || (InteractivityDetect = {}));
class Modes {
#container;
#pluginManager;
constructor(pluginManager, container) {
this.#pluginManager = pluginManager;
this.#container = container;
}
load(data) {
if (isNull(data)) {
return;
}
if (!this.#container) {
return;
}
const interactors = this.#pluginManager.interactors?.get(this.#container);
if (!interactors) {
return;
}
for (const interactor of interactors) {
if (!interactor.loadModeOptions) {
continue;
}
interactor.loadModeOptions(this, data);
}
}
}
class Interactivity {
detectsOn = InteractivityDetect.window;
events = new Events();
modes;
constructor(pluginManager, container) {
this.modes = new Modes(pluginManager, container);
}
load(data) {
if (isNull(data)) {
return;
}
const detectsOn = data.detectsOn;
if (detectsOn !== undefined) {
this.detectsOn = detectsOn;
}
this.events.load(data.events);
this.modes.load(data.modes);
}
}
class InteractivityPlugin {
id = "interactivity";
#pluginManager;
constructor(pluginManager) {
this.#pluginManager = pluginManager;
}
async getPlugin(container) {
const { InteractivityPluginInstance } = await Promise.resolve().then(function () { return InteractivityPluginInstance$1; });
return new InteractivityPluginInstance(this.#pluginManager, container);
}
loadOptions(container, options, source) {
if (!this.needsPlugin()) {
return;
}
let interactivityOptions = options.interactivity;
if (!interactivityOptions?.load) {
options.interactivity = interactivityOptions = new Interactivity(this.#pluginManager, container);
}
interactivityOptions.load(source?.interactivity);
const interactors = this.#pluginManager.interactors?.get(container);
if (!interactors) {
return;
}
for (const interactor of interactors) {
if (interactor.loadOptions) {
interactor.loadOptions(options, source);
}
}
}
loadParticlesOptions(container, options, source) {
if (source?.interactivity) {
options.interactivity = deepExtend({}, source.interactivity);
}
const interactors = this.#pluginManager.interactors?.get(container);
if (!interactors) {
return;
}
for (const interactor of interactors) {
interactor.loadParticlesOptions?.(options, source);
}
}
needsPlugin() {
return true;
}
}
var InteractorType;
(function (InteractorType) {
InteractorType["external"] = "external";
InteractorType["particles"] = "particles";
})(InteractorType || (InteractorType = {}));
class ExternalInteractorBase {
type = InteractorType.external;
container;
constructor(container) {
this.container = container;
}
}
class ParticlesInteractorBase {
type = InteractorType.particles;
container;
constructor(container) {
this.container = container;
}
}
const clickEvent = "click", mouseDownEvent = "pointerdown", mouseUpEvent = "pointerup", mouseLeaveEvent = "pointerleave", mouseMoveEvent = "pointermove", touchStartEvent = "touchstart", touchEndEvent = "touchend", touchMoveEvent = "touchmove", touchCancelEvent = "touchcancel";
function findItemFromSingleOrMultiple(obj, callback) {
if (isArray(obj)) {
return obj.find((t, index) => callback(t, index));
}
const defaultIndex = 0;
return callback(obj, defaultIndex) ? obj : undefined;
}
function checkSelector(element, selectors) {
const res = executeOnSingleOrMultiple(selectors, selector => {
return element.matches(selector);
});
return isArray(res) ? res.some(t => t) : res;
}
function isDivModeEnabled(mode, divs) {
return !!findItemFromSingleOrMultiple(divs, t => t.enable && isInArray(mode, t.mode));
}
function divModeExecute(mode, divs, callback) {
executeOnSingleOrMultiple(divs, div => {
const divMode = div.mode, divEnabled = div.enable;
if (divEnabled && isInArray(mode, divMode)) {
singleDivModeExecute(div, callback);
}
});
}
function singleDivModeExecute(div, callback) {
const selectors = div.selectors;
executeOnSingleOrMultiple(selectors, selector => {
callback(selector, div);
});
}
function divMode(divs, element) {
if (!element || !divs) {
return;
}
return findItemFromSingleOrMultiple(divs, div => {
return checkSelector(element, div.selectors);
});
}
async function loadInteractivityPlugin(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
const interactivityEngine = e, interactivityPluginManager = interactivityEngine.pluginManager;
interactivityPluginManager.addPlugin(new InteractivityPlugin(interactivityPluginManager));
interactivityPluginManager.initializers.interactors ??= new Map();
interactivityPluginManager.interactors ??= new Map();
interactivityPluginManager.addInteractor = (name, interactorInitializer) => {
interactivityPluginManager.initializers.interactors ??= new Map();
interactivityPluginManager.initializers.interactors.set(name, interactorInitializer);
};
interactivityPluginManager.getInteractors = async (container, force = false) => {
interactivityPluginManager.interactors ??= new Map();
interactivityPluginManager.initializers.interactors ??= new Map();
return getItemsFromInitializer(container, interactivityPluginManager.interactors, interactivityPluginManager.initializers.interactors, force);
};
interactivityPluginManager.setOnClickHandler = (callback) => {
const { items } = interactivityEngine;
if (!items.length) {
throw new Error("Click handlers can only be set after calling tsParticles.load()");
}
items.forEach(item => {
const interactivityContainer = item;
interactivityContainer.addClickHandler?.(callback);
});
};
});
}
function ensureInteractivityPluginLoaded(e) {
if (!e.pluginManager.addInteractor) {
throw new Error("tsParticles Interactivity Plugin is not loaded");
}
}
class AbsorberLife {
count;
delay;
duration;
wait = false;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "count", data.count);
loadRangeProperty(this, "delay", data.delay);
loadRangeProperty(this, "duration", data.duration);
loadProperty(this, "wait", data.wait);
}
}
class AbsorberSizeLimit {
mass = 0;
radius = 0;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "mass", data.mass);
loadProperty(this, "radius", data.radius);
}
}
class AbsorberSize extends ValueWithRandom {
density = 5;
limit = new AbsorberSizeLimit();
value = 50;
load(data) {
if (isNull(data)) {
return;
}
super.load(data);
loadProperty(this, "density", data.density);
if (isNumber(data.limit)) {
this.limit.radius = data.limit;
}
else {
this.limit.load(data.limit);
}
}
}
class Absorber {
color;
destroy;
draggable;
life;
name;
opacity;
orbits;
position;
size;
constructor() {
this.color = new OptionsColor();
this.color.value = "#000000";
this.draggable = false;
this.opacity = 1;
this.destroy = true;
this.orbits = false;
this.life = new AbsorberLife();
this.size = new AbsorberSize();
}
load(data) {
if (isNull(data)) {
return;
}
if (data.color !== undefined) {
this.color = OptionsColor.create(this.color, data.color);
}
loadProperty(this, "draggable", data.draggable);
if (data.life !== undefined) {
this.life.load(data.life);
}
this.name = data.name;
loadProperty(this, "opacity", data.opacity);
if (data.position !== undefined) {
this.position = {};
if (data.position.x !== undefined) {
this.position.x = setRangeValue(data.position.x);
}
if (data.position.y !== undefined) {
this.position.y = setRangeValue(data.position.y);
}
}
if (data.size !== undefined) {
this.size.load(data.size);
}
loadProperty(this, "destroy", data.destroy);
loadProperty(this, "orbits", data.orbits);
}
}
const absorbersMode = "absorbers";
class AbsorbersInteractor extends ExternalInteractorBase {
handleClickMode;
maxDistance;
#dragging = false;
#draggingAbsorber;
#instancesManager;
constructor(container, instancesManager) {
super(container);
this.maxDistance = 0;
this.#instancesManager = instancesManager;
this.#instancesManager.initContainer(container);
this.handleClickMode = (mode, interactivityData) => {
const container = this.container, options = container.actualOptions, absorbers = options.interactivity.modes.absorbers;
if (!absorbers || mode !== absorbersMode) {
return;
}
const { clickPosition } = interactivityData.mouse;
if (clickPosition) {
const existingAbsorber = instancesManager
.getArray(this.container)
.some(t => getDistance(t.position, clickPosition) < t.size);
if (existingAbsorber) {
return;
}
}
const absorbersModeOptions = itemFromArray(absorbers) ?? new Absorber();
void this.#instancesManager.addAbsorber(container, absorbersModeOptions, clickPosition);
};
}
clear() {
}
init() {
}
interact(interactivityData, delta) {
for (const particle of this.container.particles.filter(p => this.isEnabled(interactivityData, p))) {
for (const absorber of this.#instancesManager.getArray(this.container)) {
if (absorber.options.draggable) {
const mouse = interactivityData.mouse;
if (mouse.clicking && mouse.downPosition) {
const mouseDist = getDistance(absorber.position, mouse.downPosition);
if (mouseDist <= absorber.size) {
this.#dragging = true;
this.#draggingAbsorber = absorber;
}
}
else {
this.#dragging = false;
this.#draggingAbsorber = undefined;
}
if (this.#dragging && this.#draggingAbsorber == absorber && mouse.position) {
absorber.position.x = mouse.position.x;
absorber.position.y = mouse.position.y;
}
}
absorber.attract(particle, delta);
if (particle.destroyed) {
break;
}
}
}
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity).events;
if (!mouse.clickPosition || !events.onClick.enable) {
return false;
}
return isInArray(absorbersMode, events.onClick.mode);
}
loadModeOptions(options, ...sources) {
options.absorbers ??= [];
for (const source of sources) {
if (!source) {
continue;
}
if (isArray(source.absorbers)) {
for (const absorber of source.absorbers) {
const tmp = new Absorber();
tmp.load(absorber);
options.absorbers.push(tmp);
}
}
else {
const tmp = new Absorber();
tmp.load(source.absorbers);
options.absorbers.push(tmp);
}
}
}
reset() {
}
}
const instancesManagers$1 = new WeakMap();
function getAbsorbersInstancesManager(e) {
const pluginManager = e.pluginManager;
let manager = instancesManagers$1.get(pluginManager);
if (!manager) {
manager = Promise.resolve().then(function () { return AbsorbersInstancesManager$1; })
.then(({ AbsorbersInstancesManager }) => new AbsorbersInstancesManager(pluginManager))
.catch((error) => {
instancesManagers$1.delete(pluginManager);
throw error;
});
instancesManagers$1.set(pluginManager, manager);
}
return manager;
}
async function loadAbsorbersInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(async (e) => {
const pluginManager = e.pluginManager, instancesManager = await getAbsorbersInstancesManager(e);
ensureInteractivityPluginLoaded(e);
pluginManager.addInteractor?.("externalAbsorbers", container => {
return Promise.resolve(new AbsorbersInteractor(container, instancesManager));
});
});
}
class AbsorbersPlugin {
id = "absorbers";
#instancesManager;
constructor(instancesManager) {
this.#instancesManager = instancesManager;
}
async getPlugin(container) {
const { AbsorbersPluginInstance } = await Promise.resolve().then(function () { return AbsorbersPluginInstance$1; });
return new AbsorbersPluginInstance(container, this.#instancesManager);
}
loadOptions(_container, options, source) {
if (!this.needsPlugin(options) && !this.needsPlugin(source)) {
return;
}
if (source?.absorbers) {
options.absorbers = executeOnSingleOrMultiple(source.absorbers, absorber => {
const tmp = new Absorber();
tmp.load(absorber);
return tmp;
});
}
}
needsPlugin(options) {
if (!options) {
return false;
}
const absorbers = options.absorbers;
if (isArray(absorbers)) {
return !!absorbers.length;
}
else if (absorbers) {
return true;
}
return false;
}
}
async function loadAbsorbersPluginSimple(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(async (e) => {
const pluginManager = e.pluginManager, instancesManager = await getAbsorbersInstancesManager(e);
pluginManager.addPlugin(new AbsorbersPlugin(instancesManager));
});
}
async function loadAbsorbersPlugin(engine) {
await loadAbsorbersPluginSimple(engine);
await loadAbsorbersInteraction(engine);
}
class DestroyBounds {
bottom;
left;
right;
top;
load(data) {
if (isNull(data)) {
return;
}
loadRangeProperty(this, "bottom", data.bottom);
loadRangeProperty(this, "left", data.left);
loadRangeProperty(this, "right", data.right);
loadRangeProperty(this, "top", data.top);
}
}
var DestroyMode;
(function (DestroyMode) {
DestroyMode["explode"] = "explode";
DestroyMode["none"] = "none";
DestroyMode["split"] = "split";
})(DestroyMode || (DestroyMode = {}));
class Explode {
maxSizeFactor = 3;
speed = 2;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "maxSizeFactor", data.maxSizeFactor);
loadProperty(this, "speed", data.speed);
}
}
class SplitFactor extends ValueWithRandom {
value = 3;
}
class SplitRate extends ValueWithRandom {
value = { min: 4, max: 9 };
}
class Split {
count = 1;
factor = new SplitFactor();
fillColor;
fillColorOffset;
particles;
rate = new SplitRate();
sizeOffset = true;
strokeColor;
strokeColorOffset;
load(data) {
if (isNull(data)) {
return;
}
if (data.fillColor !== undefined) {
this.fillColor = OptionsColor.create(this.fillColor, data.fillColor);
}
if (data.strokeColor !== undefined) {
this.strokeColor = OptionsColor.create(this.strokeColor, data.strokeColor);
}
loadProperty(this, "count", data.count);
this.factor.load(data.factor);
this.rate.load(data.rate);
this.particles = executeOnSingleOrMultiple(data.particles, particles => {
return deepExtend({}, particles);
});
loadProperty(this, "sizeOffset", data.sizeOffset);
if (data.fillColorOffset) {
this.fillColorOffset = this.fillColorOffset ?? {};
if (data.fillColorOffset.h !== undefined) {
this.fillColorOffset.h = data.fillColorOffset.h;
}
if (data.fillColorOffset.s !== undefined) {
this.fillColorOffset.s = data.fillColorOffset.s;
}
if (data.fillColorOffset.l !== undefined) {
this.fillColorOffset.l = data.fillColorOffset.l;
}
}
if (data.strokeColorOffset) {
this.strokeColorOffset = this.strokeColorOffset ?? {};
if (data.strokeColorOffset.h !== undefined) {
this.strokeColorOffset.h = data.strokeColorOffset.h;
}
if (data.strokeColorOffset.s !== undefined) {
this.strokeColorOffset.s = data.strokeColorOffset.s;
}
if (data.strokeColorOffset.l !== undefined) {
this.strokeColorOffset.l = data.strokeColorOffset.l;
}
}
}
}
let Destroy$1 = class Destroy {
bounds = new DestroyBounds();
explode = new Explode();
mode = DestroyMode.none;
split = new Split();
load(data) {
if (isNull(data)) {
return;
}
if (data.mode) {
this.mode = data.mode;
}
if (data.bounds) {
this.bounds.load(data.bounds);
}
this.explode.load(data.explode);
this.split.load(data.split);
}
};
const defaultOffset = 0, minDestroySize = 0.5, defaultSplitCount = 0, increment = 1, unbreakableTime = 500, minSplitCount = 0, minHue = 0, hueRange = 360, minSaturation = 0, maxSaturation = 100, minLightness = 0, maxLightness = 100;
function createParentColor(parentColor) {
return AnimatableColor.create(undefined, {
value: {
hsl: parentColor,
},
});
}
function createOffsetColor(parentColor, offset) {
const offsetH = getRangeValue(offset.h ?? defaultOffset), offsetS = getRangeValue(offset.s ?? defaultOffset), offsetL = getRangeValue(offset.l ?? defaultOffset), h = (parentColor.h + offsetH) % hueRange, s = Math.max(minSaturation, Math.min(maxSaturation, parentColor.s + offsetS)), l = Math.max(minLightness, Math.min(maxLightness, parentColor.l + offsetL));
return AnimatableColor.create(undefined, {
value: {
hsl: {
h: h < minHue ? h + hueRange : h,
s,
l,
},
},
});
}
function resolveSplitColor(offset, splitColor, splitParticlesColor, parentColor) {
if (offset && parentColor) {
return createOffsetColor(parentColor, offset);
}
if (splitColor) {
return AnimatableColor.create(undefined, splitColor);
}
if (splitParticlesColor) {
return AnimatableColor.create(undefined, splitParticlesColor);
}
return parentColor ? createParentColor(parentColor) : undefined;
}
function addSplitParticle(pluginManager, container, parent, splitParticlesOptions) {
const destroyOptions = parent.options.destroy;
if (!destroyOptions) {
return;
}
const splitOptions = destroyOptions.split, splitParticleOptions = loadParticlesOptions(pluginManager, container, parent.options), splitFillColor = itemFromSingleOrMultiple(splitOptions.fillColor), splitStrokeColor = itemFromSingleOrMultiple(splitOptions.strokeColor), parentFillColor = parent.getFillColor(), parentStrokeColor = parent.getStrokeColor();
splitParticleOptions.move.load({
center: {
x: parent.position.x,
y: parent.position.y,
mode: exports.PixelMode.precise,
},
});
const factor = identity$2 / getRangeValue(splitOptions.factor.value), sizeOptions = splitParticleOptions["size"];
if (sizeOptions) {
if (isNumber(sizeOptions.value)) {
sizeOptions.value *= factor;
}
else {
sizeOptions.value.min *= factor;
sizeOptions.value.max *= factor;
}
}
splitParticleOptions.load(splitParticlesOptions);
const splitParticlePaintOptions = itemFromSingleOrMultiple(splitParticleOptions.paint), splitParticleFillOptions = splitParticlePaintOptions?.fill, splitParticleStrokeOptions = splitParticlePaintOptions?.stroke, fillColor = resolveSplitColor(splitOptions.fillColorOffset, splitFillColor, splitParticleFillOptions?.color, parentFillColor), strokeColor = resolveSplitColor(splitOptions.strokeColorOffset, splitStrokeColor, splitParticleStrokeOptions?.color, parentStrokeColor);
if (fillColor && splitParticleFillOptions) {
splitParticleFillOptions.color = fillColor;
}
if (strokeColor && splitParticleStrokeOptions) {
splitParticleStrokeOptions.color = strokeColor;
}
const offset = splitOptions.sizeOffset ? setRangeValue(-parent.size.value, parent.size.value) : defaultOffset, position = {
x: parent.position.x + randomInRangeValue(offset),
y: parent.position.y + randomInRangeValue(offset),
};
return container.particles.addParticle(position, splitParticleOptions, parent.group, (particle) => {
if (particle.size.value < minDestroySize) {
return false;
}
particle.velocity.length = randomInRangeValue(setRangeValue(parent.velocity.length, particle.velocity.length));
particle.splitCount = (parent.splitCount ?? defaultSplitCount) + increment;
particle.unbreakable = true;
particle.unbreakableUntil = performance.now() + unbreakableTime;
return true;
});
}
function split(pluginManager, container, particle) {
const destroyOptions = particle.options.destroy;
if (!destroyOptions) {
return;
}
const splitOptions = destroyOptions.split;
if (splitOptions.count >= minSplitCount) {
if (particle.splitCount === undefined || particle.splitCount > splitOptions.count) {
return;
}
particle.splitCount++;
}
const rate = getRangeValue(splitOptions.rate.value), particlesSplitOptions = itemFromSingleOrMultiple(splitOptions.particles);
for (let i = 0; i < rate; i++) {
addSplitParticle(pluginManager, container, particle, particlesSplitOptions);
}
}
const defaultDeltaFactor$1 = 1, fpsFactor = 60, minExplodeSpeed = 0.01, maxExplodeProgress = 1;
class DestroyUpdater {
#container;
#pluginManager;
constructor(pluginManager, container) {
this.#container = container;
this.#pluginManager = pluginManager;
}
init(particle) {
const container = this.#container, particlesOptions = particle.options, destroyOptions = particlesOptions.destroy;
if (!destroyOptions) {
return;
}
particle.exploding = undefined;
particle.splitCount = 0;
const destroyBoundsOptions = destroyOptions.bounds;
particle.destroyBounds ??= {};
const { bottom, left, right, top } = destroyBoundsOptions, { destroyBounds } = particle, canvasSize = container.canvas.size;
if (bottom) {
destroyBounds.bottom = (getRangeValue(bottom) * canvasSize.height) / percentDenominator;
}
if (left) {
destroyBounds.left = (getRangeValue(left) * canvasSize.width) / percentDenominator;
}
if (right) {
destroyBounds.right = (getRangeValue(right) * canvasSize.width) / percentDenominator;
}
if (top) {
destroyBounds.top = (getRangeValue(top) * canvasSize.height) / percentDenominator;
}
}
isEnabled(particle) {
const destroyParticle = particle;
return !destroyParticle.destroyed || !!destroyParticle.exploding;
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "destroy", Destroy$1, ...sources);
}
particleDestroyed(particle, override) {
if (override) {
return;
}
const destroyOptions = particle.options.destroy;
switch (destroyOptions?.mode) {
case DestroyMode.split:
split(this.#pluginManager, this.#container, particle);
break;
case DestroyMode.explode: {
if (particle.exploding) {
particle.destroyed = false;
break;
}
const { explode } = destroyOptions, initialSize = particle.size.value, maxSize = initialSize * explode.maxSizeFactor, opacity = particle.getOpacity();
particle.exploding = {
initialFillOpacity: opacity.fillOpacity,
initialSize,
initialStrokeOpacity: opacity.strokeOpacity,
maxSize,
progress: 0,
speed: Math.max(explode.speed, minExplodeSpeed),
};
particle.fillOpacity = opacity.fillOpacity;
particle.strokeOpacity = opacity.strokeOpacity;
particle.destroyed = false;
break;
}
}
}
update(particle, delta) {
if (particle.exploding) {
const explosionState = particle.exploding, deltaFactor = delta.factor || defaultDeltaFactor$1;
explosionState.progress = Math.min(maxExplodeProgress, explosionState.progress + (explosionState.speed * deltaFactor) / fpsFactor);
const progress = explosionState.progress;
particle.size.value =
explosionState.initialSize + (explosionState.maxSize - explosionState.initialSize) * progress;
particle.fillOpacity = explosionState.initialFillOpacity * (maxExplodeProgress - progress);
particle.strokeOpacity = explosionState.initialStrokeOpacity * (maxExplodeProgress - progress);
if (progress >= maxExplodeProgress) {
particle.exploding = undefined;
particle.destroy(true);
}
return;
}
if (particle.unbreakableUntil !== undefined && performance.now() >= particle.unbreakableUntil) {
particle.unbreakable = false;
particle.unbreakableUntil = undefined;
}
const position = particle.getPosition(), bounds = particle.destroyBounds;
if (!bounds) {
return;
}
if ((bounds.bottom !== undefined && position.y >= bounds.bottom) ||
(bounds.left !== undefined && position.x <= bounds.left) ||
(bounds.right !== undefined && position.x >= bounds.right) ||
(bounds.top !== undefined && position.y <= bounds.top)) {
particle.destroy();
}
}
}
async function loadDestroyUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("destroy", container => {
return Promise.resolve(new DestroyUpdater(e.pluginManager, container));
});
});
}
class EmitterLife {
count;
delay;
duration;
wait = false;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "count", data.count);
loadRangeProperty(this, "delay", data.delay);
loadRangeProperty(this, "duration", data.duration);
loadProperty(this, "wait", data.wait);
}
}
class EmitterRate {
delay = 0.1;
quantity = 1;
load(data) {
if (isNull(data)) {
return;
}
loadRangeProperty(this, "quantity", data.quantity);
loadRangeProperty(this, "delay", data.delay);
}
}
class EmitterShapeReplace {
color = false;
opacity = false;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "color", data.color);
loadProperty(this, "opacity", data.opacity);
}
}
class EmitterShape {
options = {};
replace = new EmitterShapeReplace();
type = "square";
load(data) {
if (isNull(data)) {
return;
}
if (data.options !== undefined) {
this.options = deepExtend({}, data.options ?? {});
}
this.replace.load(data.replace);
loadProperty(this, "type", data.type);
}
}
class EmitterSize {
height = 0;
mode = exports.PixelMode.percent;
width = 0;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "mode", data.mode);
loadProperty(this, "height", data.height);
loadProperty(this, "width", data.width);
}
}
class EmitterSpawn {
fill;
stroke;
load(data) {
if (isNull(data)) {
return;
}
if (data.fill) {
this.fill ??= new Fill();
this.fill.load(data.fill);
}
if (data.stroke) {
this.stroke ??= new Stroke();
this.stroke.load(data.stroke);
}
}
}
class Emitter {
autoPlay = true;
direction;
domId;
fill = true;
life = new EmitterLife();
name;
particles;
position;
rate = new EmitterRate();
shape = new EmitterShape();
size;
spawn = new EmitterSpawn();
spawnFillColor;
spawnStrokeColor;
startCount = 0;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "autoPlay", data.autoPlay);
if (data.size !== undefined) {
this.size ??= new EmitterSize();
this.size.load(data.size);
}
loadProperty(this, "direction", data.direction);
loadProperty(this, "domId", data.domId);
loadProperty(this, "fill", data.fill);
this.life.load(data.life);
loadProperty(this, "name", data.name);
this.particles = executeOnSingleOrMultiple(data.particles, particles => {
return deepExtend({}, particles);
});
this.rate.load(data.rate);
this.shape.load(data.shape);
this.spawn.load(data.spawn);
if (data.position !== undefined) {
this.position = {};
if (data.position.x !== undefined) {
this.position.x = setRangeValue(data.position.x);
}
if (data.position.y !== undefined) {
this.position.y = setRangeValue(data.position.y);
}
}
if (data.spawnFillColor !== undefined) {
this.spawnFillColor ??= new AnimatableColor();
this.spawnFillColor.load(data.spawnFillColor);
}
if (data.spawnStrokeColor !== undefined) {
this.spawnStrokeColor ??= new AnimatableColor();
this.spawnStrokeColor.load(data.spawnStrokeColor);
}
loadProperty(this, "startCount", data.startCount);
}
}
const defaultRandomOptions = {
count: 1,
enable: true,
};
const emittersMode = "emitters";
class EmittersInteractor extends ExternalInteractorBase {
handleClickMode;
maxDistance;
#instancesManager;
constructor(instancesManager, container) {
super(container);
this.#instancesManager = instancesManager;
this.maxDistance = 0;
this.handleClickMode = (mode, interactivityData) => {
const container = this.container, options = container.actualOptions, modeEmitters = options.interactivity.modes.emitters;
if (!modeEmitters || mode !== emittersMode) {
return;
}
let emittersModeOptions;
if (isArray(modeEmitters.value)) {
const minLength = 0, modeEmittersCount = modeEmitters.value.length;
if (modeEmittersCount > minLength && modeEmitters.random.enable) {
emittersModeOptions = [];
const usedIndexes = new Set();
for (let i = 0; i < modeEmitters.random.count; i++) {
const idx = Math.floor(getRandom() * modeEmitters.value.length);
if (usedIndexes.has(idx) && usedIndexes.size < modeEmittersCount) {
i--;
continue;
}
usedIndexes.add(idx);
const selectedOptions = itemFromArray(modeEmitters.value, idx);
if (!selectedOptions) {
continue;
}
emittersModeOptions.push(selectedOptions);
}
}
else {
emittersModeOptions = modeEmitters.value;
}
}
else {
emittersModeOptions = modeEmitters.value;
}
const emittersOptions = emittersModeOptions, ePosition = interactivityData.mouse.clickPosition;
void executeOnSingleOrMultiple(emittersOptions, async (emitter) => {
await this.#instancesManager.addEmitter(this.container, emitter, ePosition);
});
};
}
clear() {
}
init() {
}
interact(_interactivityData, delta) {
for (const emitter of this.#instancesManager.getArray(this.container)) {
emitter.update(delta);
}
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity).events;
if (!mouse.clickPosition || !events.onClick.enable) {
return false;
}
return isInArray(emittersMode, events.onClick.mode);
}
loadModeOptions(options, ...sources) {
options.emitters = {
random: defaultRandomOptions,
value: [],
};
for (const source of sources) {
if (!source?.emitters) {
continue;
}
if (isArray(source.emitters)) {
for (const emitter of source.emitters) {
const tmp = new Emitter();
tmp.load(emitter);
options.emitters.value.push(tmp);
}
}
else if ("value" in source.emitters) {
const emitterModeOptions = source.emitters;
options.emitters.random.enable = emitterModeOptions.random?.enable ?? options.emitters.random.enable;
options.emitters.random.count = emitterModeOptions.random?.count ?? options.emitters.random.count;
if (isArray(emitterModeOptions.value)) {
for (const emitter of emitterModeOptions.value) {
const tmp = new Emitter();
tmp.load(emitter);
options.emitters.value.push(tmp);
}
}
else {
const tmp = new Emitter();
tmp.load(emitterModeOptions.value);
options.emitters.value.push(tmp);
}
}
else {
const tmp = new Emitter();
tmp.load(source.emitters);
options.emitters.value.push(tmp);
}
}
}
removeEmitter(emitter) {
const index = this.#instancesManager.getArray(this.container).indexOf(emitter), minIndex = 0, deleteCount = 1;
if (index >= minIndex) {
this.#instancesManager.getArray(this.container).splice(index, deleteCount);
}
}
reset() {
}
}
async function addEmittersShapesManager(e) {
const { ShapeManager } = await Promise.resolve().then(function () { return ShapeManager$1; }), pluginManager = e.pluginManager;
pluginManager.emitterShapeManager ??= new ShapeManager();
pluginManager.addEmitterShapeGenerator ??= (name, generator) => {
pluginManager.emitterShapeManager?.addShapeGenerator(name, generator);
};
}
const instancesManagers = new WeakMap();
function getEmittersInstancesManager(e) {
const pluginManager = e.pluginManager;
let manager = instancesManagers.get(pluginManager);
if (!manager) {
manager = Promise.resolve().then(function () { return EmittersInstancesManager$1; })
.then(({ EmittersInstancesManager }) => new EmittersInstancesManager(pluginManager))
.catch((error) => {
instancesManagers.delete(pluginManager);
throw error;
});
instancesManagers.set(pluginManager, manager);
}
return manager;
}
class EmitterShapeBase {
fill;
options;
position;
size;
constructor(position, size, fill, options) {
this.position = position;
this.size = size;
this.fill = fill;
this.options = options;
}
resize(position, size) {
this.position = position;
this.size = size;
}
}
var EmitterClickMode;
(function (EmitterClickMode) {
EmitterClickMode["emitter"] = "emitter";
})(EmitterClickMode || (EmitterClickMode = {}));
async function loadEmittersInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(async (e) => {
const instancesManager = await getEmittersInstancesManager(e);
ensureInteractivityPluginLoaded(e);
await addEmittersShapesManager(e);
e.pluginManager.addInteractor?.("externalEmitters", container => {
return Promise.resolve(new EmittersInteractor(instancesManager, container));
});
});
}
class EmittersPlugin {
id = "emitters";
#instancesManager;
constructor(instancesManager) {
this.#instancesManager = instancesManager;
}
async getPlugin(container) {
const { EmittersPluginInstance } = await Promise.resolve().then(function () { return EmittersPluginInstance$1; });
return new EmittersPluginInstance(this.#instancesManager, container);
}
loadOptions(_container, options, source) {
if (!this.needsPlugin(options) && !this.needsPlugin(source)) {
return;
}
if (source?.emitters) {
options.emitters = executeOnSingleOrMultiple(source.emitters, emitter => {
const tmp = new Emitter();
tmp.load(emitter);
return tmp;
});
}
}
needsPlugin(options) {
if (!options) {
return false;
}
const emitters = options.emitters;
return (isArray(emitters) && !!emitters.length) || emitters !== undefined;
}
}
async function loadEmittersPluginSimple(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(async (e) => {
const instancesManager = await getEmittersInstancesManager(e);
await addEmittersShapesManager(e);
e.pluginManager.addPlugin(new EmittersPlugin(instancesManager));
});
}
function ensureEmittersPluginLoaded(e) {
if (!e.pluginManager.addEmitterShapeGenerator) {
throw new Error("tsParticles Emitters Plugin is not loaded");
}
}
async function loadEmittersPlugin(engine) {
await loadEmittersPluginSimple(engine);
await loadEmittersInteraction(engine);
}
class EmittersCircleShape extends EmitterShapeBase {
constructor(position, size, fill, options) {
super(position, size, fill, options);
}
async init() {
}
randomPosition() {
const size = this.size, fill = this.fill, position = this.position, generateTheta = (x, y) => {
const u = getRandom() * quarter, theta = Math.atan((y / x) * Math.tan(doublePI * u)), v = getRandom();
if (v < quarter) {
return theta;
}
else if (v < double * quarter) {
return Math.PI - theta;
}
else if (v < double * quarter + quarter) {
return Math.PI + theta;
}
else {
return -theta;
}
}, radius = (x, y, theta) => (x * y) / Math.sqrt((y * Math.cos(theta)) ** squareExp + (x * Math.sin(theta)) ** squareExp), [a, b] = [size.width * half, size.height * half], randomTheta = generateTheta(a, b), maxRadius = radius(a, b, randomTheta), randomRadius = fill ? maxRadius * Math.sqrt(getRandom()) : maxRadius;
return {
position: {
x: position.x + randomRadius * Math.cos(randomTheta),
y: position.y + randomRadius * Math.sin(randomTheta),
},
};
}
}
class EmittersCircleShapeGenerator {
generate(_container, position, size, fill, options) {
return new EmittersCircleShape(position, size, fill, options);
}
}
async function loadEmittersShapeCircle(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureEmittersPluginLoaded(e);
e.pluginManager.addEmitterShapeGenerator?.("circle", new EmittersCircleShapeGenerator());
});
}
const sides$5 = 4;
var Sides;
(function (Sides) {
Sides[Sides["TopLeft"] = 0] = "TopLeft";
Sides[Sides["TopRight"] = 1] = "TopRight";
Sides[Sides["BottomRight"] = 2] = "BottomRight";
Sides[Sides["BottomLeft"] = 3] = "BottomLeft";
})(Sides || (Sides = {}));
function randomSquareCoordinate(position, offset) {
return position + offset * (getRandom() - half);
}
function getRandomSize(sides) {
return Math.floor(getRandom() * sides);
}
class EmittersSquareShape extends EmitterShapeBase {
constructor(position, size, fill, options) {
super(position, size, fill, options);
}
async init() {
}
randomPosition() {
const fill = this.fill, position = this.position, size = this.size;
if (fill) {
return {
position: {
x: randomSquareCoordinate(position.x, size.width),
y: randomSquareCoordinate(position.y, size.height),
},
};
}
else {
const halfW = size.width * half, halfH = size.height * half, side = getRandomSize(sides$5), v = (getRandom() - half) * double;
switch (side) {
case Sides.TopLeft:
return {
position: {
x: position.x + v * halfW,
y: position.y - halfH,
},
};
case Sides.TopRight:
return {
position: {
x: position.x - halfW,
y: position.y + v * halfH,
},
};
case Sides.BottomRight:
return {
position: {
x: position.x + v * halfW,
y: position.y + halfH,
},
};
case Sides.BottomLeft:
default:
return {
position: {
x: position.x + halfW,
y: position.y + v * halfH,
},
};
}
}
}
}
class EmittersSquareShapeGenerator {
generate(_container, position, size, fill, options) {
return new EmittersSquareShape(position, size, fill, options);
}
}
async function loadEmittersShapeSquare(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureEmittersPluginLoaded(e);
e.pluginManager.addEmitterShapeGenerator?.("square", new EmittersSquareShapeGenerator());
});
}
const defaultMomentumFactor$1 = 0.03, defaultPreserveMomentum = false;
class Drag {
momentumFactor = defaultMomentumFactor$1;
preserveMomentum = defaultPreserveMomentum;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "momentumFactor", data.momentumFactor);
loadProperty(this, "preserveMomentum", data.preserveMomentum);
}
}
const clickReleaseDelayMs = 50, defaultMomentumFactor = 0.03, dragMode = "drag", firstSampleIndex = 0, lastSampleOffset = 1, maxMomentumSamples = 5, minSamplesForMomentum = 2, momentumToVelocityFactor = millisecondsToSeconds / defaultFps, zeroValue = 0;
class Dragger extends ExternalInteractorBase {
maxDistance = 0;
#dragStartClickTime;
#dragStartClickingFalseTime;
#draggedParticle;
#grabOffset;
#lastMousePosition;
#momentumSamples = [];
#mouseDownHandled = false;
#savedVelocity;
constructor(container) {
super(container);
}
clear(_particle, _delta) {
}
init() {
}
interact(interactivityData, _delta) {
const mouse = interactivityData.mouse, mousePos = mouse.position;
if (mousePos) {
this.#lastMousePosition = mousePos;
}
if (this.#draggedParticle) {
const now = performance.now();
if (!mouse.clicking) {
this.#dragStartClickingFalseTime ??= now;
}
else {
this.#dragStartClickingFalseTime = undefined;
}
const hasStableClickEndWithoutClickTime = this.#dragStartClickTime === undefined &&
this.#dragStartClickingFalseTime !== undefined &&
now - this.#dragStartClickingFalseTime >= clickReleaseDelayMs;
if (!mouse.inside || this.#hasClickEnded(mouse) || hasStableClickEndWithoutClickTime) {
this.#releaseDragged();
this.#mouseDownHandled = false;
return;
}
const pointerPos = mousePos ?? this.#lastMousePosition;
if (!pointerPos) {
return;
}
this.#momentumSamples.push({ x: pointerPos.x, y: pointerPos.y, t: now });
if (this.#momentumSamples.length > maxMomentumSamples) {
this.#momentumSamples.shift();
}
const targetX = pointerPos.x + (this.#grabOffset?.x ?? zeroValue), targetY = pointerPos.y + (this.#grabOffset?.y ?? zeroValue);
this.#draggedParticle.position.x = targetX - this.#draggedParticle.offset.x;
this.#draggedParticle.position.y = targetY - this.#draggedParticle.offset.y;
this.#draggedParticle.velocity.x = zeroValue;
this.#draggedParticle.velocity.y = zeroValue;
this.#draggedParticle.initialPosition.x = this.#draggedParticle.position.x;
this.#draggedParticle.initialPosition.y = this.#draggedParticle.position.y;
this.#draggedParticle.misplaced = false;
return;
}
if (!mouse.clicking) {
this.#releaseDragged();
this.#mouseDownHandled = false;
return;
}
if (this.#mouseDownHandled) {
return;
}
this.#mouseDownHandled = true;
const downPos = mouse.downPosition ?? mousePos ?? this.#lastMousePosition, closest = this.#findParticleUnderCursor(interactivityData, downPos);
if (!closest || !mousePos) {
if (!closest) {
this.#mouseDownHandled = false;
}
return;
}
const renderedPos = closest.getPosition();
this.#draggedParticle = closest;
this.#dragStartClickTime = mouse.clickTime;
this.#grabOffset = {
x: renderedPos.x - mousePos.x,
y: renderedPos.y - mousePos.y,
};
this.#savedVelocity = { x: closest.velocity.x, y: closest.velocity.y };
this.#dragStartClickingFalseTime = undefined;
this.#momentumSamples = [{ x: mousePos.x, y: mousePos.y, t: performance.now() }];
closest.velocity.x = zeroValue;
closest.velocity.y = zeroValue;
closest.position.x = renderedPos.x - closest.offset.x;
closest.position.y = renderedPos.y - closest.offset.y;
closest.initialPosition.x = closest.position.x;
closest.initialPosition.y = closest.position.y;
closest.misplaced = false;
}
isEnabled(interactivityData, particle) {
const container = this.container, mouse = interactivityData.mouse, events = (particle?.interactivity ?? container.actualOptions.interactivity)?.events;
if (this.#draggedParticle || this.#mouseDownHandled) {
return true;
}
return !!events?.onClick.enable && mouse.clicking && !!mouse.position && isInArray(dragMode, events.onClick.mode);
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "drag", Drag, ...sources);
}
reset(_interactivityData, _particle) {
}
#findParticleUnderCursor(interactivityData, mousePos) {
if (!mousePos) {
return;
}
const candidates = this.container.particles.filter(p => this.isEnabled(interactivityData, p));
let closest, closestDist = Infinity;
for (const candidate of candidates) {
const dist = getDistance(candidate.getPosition(), mousePos), radius = candidate.getRadius();
if (!Number.isFinite(dist) || !Number.isFinite(radius) || dist > radius || dist >= closestDist) {
continue;
}
closest = candidate;
closestDist = dist;
}
return closest;
}
#hasClickEnded(mouse) {
return (this.#dragStartClickTime !== undefined &&
mouse.clickTime !== undefined &&
mouse.clickTime !== this.#dragStartClickTime);
}
#releaseDragged() {
if (this.#draggedParticle) {
const dragOptions = this.container.actualOptions.interactivity?.modes.drag, preserveMomentum = dragOptions?.preserveMomentum ?? false;
if (preserveMomentum && this.#momentumSamples.length >= minSamplesForMomentum) {
const first = this.#momentumSamples[firstSampleIndex], last = this.#momentumSamples[this.#momentumSamples.length - lastSampleOffset];
if (first && last) {
const dt = last.t - first.t;
if (dt > zeroValue) {
const factor = dragOptions?.momentumFactor ?? defaultMomentumFactor;
this.#draggedParticle.velocity.x = ((last.x - first.x) / dt) * momentumToVelocityFactor * factor;
this.#draggedParticle.velocity.y = ((last.y - first.y) / dt) * momentumToVelocityFactor * factor;
}
else if (this.#savedVelocity) {
this.#draggedParticle.velocity.x = this.#savedVelocity.x;
this.#draggedParticle.velocity.y = this.#savedVelocity.y;
}
}
else if (this.#savedVelocity) {
this.#draggedParticle.velocity.x = this.#savedVelocity.x;
this.#draggedParticle.velocity.y = this.#savedVelocity.y;
}
}
else if (this.#savedVelocity) {
this.#draggedParticle.velocity.x = this.#savedVelocity.x;
this.#draggedParticle.velocity.y = this.#savedVelocity.y;
}
}
this.#draggedParticle = undefined;
this.#dragStartClickTime = undefined;
this.#dragStartClickingFalseTime = undefined;
this.#grabOffset = undefined;
this.#lastMousePosition = undefined;
this.#momentumSamples = [];
this.#savedVelocity = undefined;
}
}
async function loadExternalDragInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalDrag", container => {
return Promise.resolve(new Dragger(container));
});
});
}
class TrailColorWeight {
x = 0;
y = 0;
load(data) {
if (!data) {
return;
}
loadProperty(this, "x", data.x);
loadProperty(this, "y", data.y);
}
}
class TrailColorComponent {
value;
weights = new TrailColorWeight();
load(data) {
if (!data) {
return;
}
if (data.weights) {
this.weights ??= new TrailColorWeight();
this.weights.load(data.weights);
}
if (!isNull(data.value)) {
this.value = setRangeValue(data.value);
}
}
}
class TrailColorCoords {
h = new TrailColorComponent();
l = new TrailColorComponent();
s = new TrailColorComponent();
load(data) {
if (!data) {
return;
}
if (data.h) {
this.h ??= new TrailColorComponent();
this.h.load(data.h);
}
if (data.s) {
this.s ??= new TrailColorComponent();
this.s.load(data.s);
}
if (data.l) {
this.l ??= new TrailColorComponent();
this.l.load(data.l);
}
}
}
class Trail {
colorCoords = new TrailColorCoords();
delay = 1;
particles;
pauseOnStop = false;
quantity = 1;
load(data) {
if (isNull(data))
return;
loadProperty(this, "delay", data.delay);
loadProperty(this, "quantity", data.quantity);
loadProperty(this, "pauseOnStop", data.pauseOnStop);
if (data.particles !== undefined) {
this.particles = deepExtend({}, data.particles);
}
if (data.colorCoords) {
this.colorCoords ??= new TrailColorCoords();
this.colorCoords.load(data.colorCoords);
}
}
}
const trailMode = "trail", defaultMin = 0, defaultWeight = 0;
class TrailMaker extends ExternalInteractorBase {
maxDistance = 0;
#delay;
#lastPosition;
#pluginManager;
constructor(pluginManager, container) {
super(container);
this.#pluginManager = pluginManager;
this.#delay = 0;
}
clear() {
}
init() {
}
interact(interactivityData, delta) {
const container = this.container;
if (!container.retina.reduceFactor)
return;
const options = container.actualOptions, trailOptions = options.interactivity?.modes.trail;
if (!trailOptions)
return;
const optDelay = (trailOptions.delay * millisecondsToSeconds) / this.container.retina.reduceFactor;
if (this.#delay < optDelay) {
this.#delay += delta.value;
}
if (this.#delay < optDelay)
return;
const mousePos = interactivityData.mouse.position, canEmit = !(trailOptions.pauseOnStop &&
(mousePos === this.#lastPosition ||
(mousePos?.x === this.#lastPosition?.x && mousePos?.y === this.#lastPosition?.y)));
if (mousePos) {
this.#lastPosition = { ...mousePos };
}
else {
this.#lastPosition = undefined;
}
if (canEmit && mousePos) {
let particleOptions = trailOptions.particles;
const colorCoords = trailOptions.colorCoords;
if (colorCoords) {
const { width, height } = container.canvas.size, norm = {
x: mousePos.x / width,
y: mousePos.y / height,
}, hasWeights = (comp) => {
return !!(comp?.weights?.x ?? comp?.weights?.y);
}, calculateValue = (comp, originalValue, defaultMax) => {
if (!hasWeights(comp))
return undefined;
const w = comp?.weights, factor = norm.x * (w?.x ?? defaultWeight) + norm.y * (w?.y ?? defaultWeight), rangeSource = comp?.value ?? originalValue, min = getRangeMin(rangeSource ?? defaultMin), max = getRangeMax(rangeSource ?? defaultMax), result = min + factor * (max - min);
return Math.min(max, Math.max(min, result));
}, trailPaintOptions = trailOptions.particles?.paint
? itemFromSingleOrMultiple(trailOptions.particles.paint)
: undefined, fillData = trailPaintOptions?.fill, baseHsl = fillData
? rangeColorToHsl(this.#pluginManager, AnimatableColor.create(undefined, fillData.color))
: undefined, h = calculateValue(colorCoords.h, baseHsl?.h, hMax), s = calculateValue(colorCoords.s, baseHsl?.s, sMax), l = calculateValue(colorCoords.l, baseHsl?.l, lMax);
if (h !== undefined || s !== undefined || l !== undefined) {
particleOptions = deepExtend({}, trailOptions.particles, {
paint: {
fill: {
color: {
value: {
h: h ?? baseHsl?.h,
s: s ?? baseHsl?.s,
l: l ?? baseHsl?.l,
},
},
},
},
});
}
}
container.particles.push(trailOptions.quantity, mousePos, particleOptions);
}
this.#delay -= optDelay;
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
return (!!events &&
((mouse.clicking && mouse.inside && !!mouse.position && isInArray(trailMode, events.onClick.mode)) ||
(mouse.inside && !!mouse.position && isInArray(trailMode, events.onHover.mode))));
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "trail", Trail, ...sources);
}
reset() {
}
}
async function loadExternalTrailInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalTrail", container => {
return Promise.resolve(new TrailMaker(e.pluginManager, container));
});
});
}
var RollMode;
(function (RollMode) {
RollMode["both"] = "both";
RollMode["horizontal"] = "horizontal";
RollMode["vertical"] = "vertical";
})(RollMode || (RollMode = {}));
const maxAngle$4 = 360;
function initParticle(pluginManager, particle) {
const rollOpt = particle.options.roll;
if (!rollOpt?.enable) {
particle.roll = {
enable: false,
horizontal: false,
vertical: false,
angle: 0,
speed: 0,
};
return;
}
particle.roll = {
enable: rollOpt.enable,
horizontal: rollOpt.mode === RollMode.horizontal || rollOpt.mode === RollMode.both,
vertical: rollOpt.mode === RollMode.vertical || rollOpt.mode === RollMode.both,
angle: getRandom() * doublePI,
speed: getRangeValue(rollOpt.speed) / maxAngle$4,
};
if (rollOpt.backColor) {
particle.backColor = rangeColorToHsl(pluginManager, rollOpt.backColor);
}
else if (rollOpt.darken.enable && rollOpt.enlighten.enable) {
const alterType = getRandom() >= half ? exports.AlterType.darken : exports.AlterType.enlighten;
particle.roll.alter = {
type: alterType,
value: getRangeValue(alterType === exports.AlterType.darken ? rollOpt.darken.value : rollOpt.enlighten.value),
};
}
else if (rollOpt.darken.enable) {
particle.roll.alter = {
type: exports.AlterType.darken,
value: getRangeValue(rollOpt.darken.value),
};
}
else if (rollOpt.enlighten.enable) {
particle.roll.alter = {
type: exports.AlterType.enlighten,
value: getRangeValue(rollOpt.enlighten.value),
};
}
}
function updateRoll(particle, delta) {
const roll = particle.options.roll, data = particle.roll;
if (!data || !roll?.enable) {
return;
}
const speed = data.speed * delta.factor, max = doublePI;
data.angle += speed;
if (data.angle > max) {
data.angle -= max;
}
}
class RollLight {
enable = false;
value = 0;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "enable", data.enable);
loadRangeProperty(this, "value", data.value);
}
}
class Roll {
backColor;
darken = new RollLight();
enable = false;
enlighten = new RollLight();
mode = RollMode.vertical;
speed = 25;
load(data) {
if (isNull(data)) {
return;
}
if (data.backColor !== undefined) {
this.backColor = OptionsColor.create(this.backColor, data.backColor);
}
this.darken.load(data.darken);
loadProperty(this, "enable", data.enable);
this.enlighten.load(data.enlighten);
loadProperty(this, "mode", data.mode);
loadRangeProperty(this, "speed", data.speed);
}
}
class RollUpdater {
#pluginManager;
constructor(pluginManager) {
this.#pluginManager = pluginManager;
}
getTransformValues(particle) {
const roll = particle.roll?.enable && particle.roll, rollHorizontal = roll && roll.horizontal, rollVertical = roll && roll.vertical;
return {
a: rollHorizontal ? Math.cos(roll.angle) : undefined,
d: rollVertical ? Math.sin(roll.angle) : undefined,
};
}
init(particle) {
initParticle(this.#pluginManager, particle);
}
isEnabled(particle) {
const roll = particle.options.roll;
return !particle.destroyed && !particle.spawning && !!roll?.enable;
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "roll", Roll, ...sources);
}
update(particle, delta) {
if (!this.isEnabled(particle)) {
return;
}
updateRoll(particle, delta);
}
}
async function loadRollUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("roll", () => {
return Promise.resolve(new RollUpdater(e.pluginManager));
});
});
}
class Blend {
enable = false;
mode = "destination-out";
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "mode", data.mode);
loadProperty(this, "enable", data.enable);
}
}
class BlendPlugin {
id = "blend";
async getPlugin(container) {
const { BlendPluginInstance } = await Promise.resolve().then(function () { return BlendPluginInstance$1; });
return new BlendPluginInstance(container);
}
loadOptions(_container, options, source) {
if (!this.needsPlugin(options) && !this.needsPlugin(source)) {
return;
}
let blendOptions = options.blend;
if (!blendOptions?.load) {
options.blend = blendOptions = new Blend();
}
blendOptions.load(source?.blend);
}
loadParticlesOptions(_container, options, source) {
options.blend ??= new Blend();
options.blend.load(source?.blend);
}
needsPlugin(options) {
return !!options?.blend?.enable || !!options?.particles?.blend?.enable;
}
}
async function loadBlendPlugin(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addPlugin(new BlendPlugin());
});
}
const minAngle$2 = 0;
function drawCircle(data) {
const { context, particle, radius } = data;
particle.circleRange ??= { min: minAngle$2, max: doublePI };
const circleRange = particle.circleRange;
context.arc(originPoint.x, originPoint.y, radius, circleRange.min, circleRange.max, false);
}
const sides$4 = 12, maxAngle$3 = 360, minAngle$1 = 0;
class CircleDrawer {
draw(data) {
drawCircle(data);
}
getSidesCount() {
return sides$4;
}
particleInit(_container, particle) {
const shapeData = particle.shapeData, angle = shapeData?.angle ?? {
max: maxAngle$3,
min: minAngle$1,
};
particle.circleRange = isObject(angle)
? { min: degToRad(angle.min), max: degToRad(angle.max) }
: {
min: minAngle$1,
max: degToRad(angle),
};
}
}
async function loadCircleShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addShape(["circle"], () => {
return Promise.resolve(new CircleDrawer());
});
});
}
var RgbIndexes$1;
(function (RgbIndexes) {
RgbIndexes[RgbIndexes["r"] = 1] = "r";
RgbIndexes[RgbIndexes["g"] = 2] = "g";
RgbIndexes[RgbIndexes["b"] = 3] = "b";
RgbIndexes[RgbIndexes["a"] = 4] = "a";
})(RgbIndexes$1 || (RgbIndexes$1 = {}));
const shorthandHexRegex = /^#?([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i, hexRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i, hexRadix = 16, defaultAlpha = 1, alphaFactor = 0xff;
class HexColorManager {
accepts(input) {
return input.startsWith("#");
}
handleColor(color) {
return this.#parseString(color.value);
}
handleRangeColor(color) {
return this.#parseString(color.value);
}
parseString(input) {
return this.#parseString(input);
}
#parseString(hexColor) {
if (typeof hexColor !== "string" || !this.accepts(hexColor)) {
return;
}
const hexFixed = hexColor.replace(shorthandHexRegex, (_, r, g, b, a) => {
return r + r + g + g + b + b + (a === undefined ? "" : a + a);
}), result = hexRegex.exec(hexFixed);
return result
? {
a: result[RgbIndexes$1.a] ? Number.parseInt(result[RgbIndexes$1.a], hexRadix) / alphaFactor : defaultAlpha,
b: Number.parseInt(result[RgbIndexes$1.b] ?? "0", hexRadix),
g: Number.parseInt(result[RgbIndexes$1.g] ?? "0", hexRadix),
r: Number.parseInt(result[RgbIndexes$1.r] ?? "0", hexRadix),
}
: undefined;
}
}
async function loadHexColorPlugin(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addColorManager("hex", new HexColorManager());
});
}
var HslIndexes;
(function (HslIndexes) {
HslIndexes[HslIndexes["h"] = 1] = "h";
HslIndexes[HslIndexes["s"] = 2] = "s";
HslIndexes[HslIndexes["l"] = 3] = "l";
HslIndexes[HslIndexes["a"] = 5] = "a";
})(HslIndexes || (HslIndexes = {}));
const hslRegex = /hsla?\(\s*(\d+)\s*[\s,]\s*(\d+)%\s*[\s,]\s*(\d+)%\s*([\s,]\s*(0|1|0?\.\d+|(\d{1,3})%)\s*)?\)/i;
class HslColorManager {
accepts(input) {
return input.startsWith("hsl");
}
handleColor(color) {
const colorValue = color.value, hslColor = colorValue.hsl ?? color.value;
if (!("h" in hslColor) || !("s" in hslColor) || !("l" in hslColor)) {
return;
}
return hslToRgb(hslColor);
}
handleRangeColor(color) {
const colorValue = color.value, hslColor = colorValue.hsl ?? color.value;
if (!("h" in hslColor) || !("s" in hslColor) || !("l" in hslColor)) {
return;
}
return hslToRgb({
h: getRangeValue(hslColor.h),
l: getRangeValue(hslColor.l),
s: getRangeValue(hslColor.s),
});
}
parseString(input) {
if (!this.accepts(input)) {
return;
}
const result = hslRegex.exec(input), minLength = 4, defaultAlpha = 1, radix = 10;
return result
? hslaToRgba({
a: result.length > minLength ? parseAlpha(result[HslIndexes.a]) : defaultAlpha,
h: Number.parseInt(result[HslIndexes.h] ?? "0", radix),
l: Number.parseInt(result[HslIndexes.l] ?? "0", radix),
s: Number.parseInt(result[HslIndexes.s] ?? "0", radix),
})
: undefined;
}
}
async function loadHslColorPlugin(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addColorManager("hsl", new HslColorManager());
});
}
class MovePlugin {
id = "move";
#pluginManager;
constructor(pluginManager) {
this.#pluginManager = pluginManager;
}
async getPlugin(container) {
const { MovePluginInstance } = await Promise.resolve().then(function () { return MovePluginInstance$1; });
return new MovePluginInstance(this.#pluginManager, container);
}
loadOptions() {
}
needsPlugin() {
return true;
}
}
async function loadMovePlugin(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
const moveEngine = e, movePluginManager = moveEngine.pluginManager;
movePluginManager.initializers.pathGenerators ??= new Map();
movePluginManager.pathGenerators ??= new Map();
movePluginManager.addPathGenerator = (name, generator) => {
movePluginManager.initializers.pathGenerators ??= new Map();
movePluginManager.initializers.pathGenerators.set(name, generator);
};
movePluginManager.getPathGenerators = async (container, force = false) => {
movePluginManager.initializers.pathGenerators ??= new Map();
movePluginManager.pathGenerators ??= new Map();
return getItemMapFromInitializer(container, movePluginManager.pathGenerators, movePluginManager.initializers.pathGenerators, force);
};
e.pluginManager.addPlugin(new MovePlugin(e.pluginManager));
});
}
function checkDestroy(particle, destroyType, value, minValue, maxValue) {
switch (destroyType) {
case exports.DestroyType.max:
if (value >= maxValue) {
particle.destroy();
}
break;
case exports.DestroyType.min:
if (value <= minValue) {
particle.destroy();
}
break;
}
}
function initParticleNumericAnimationValue(options, pxRatio) {
const valueRange = options.value, animationOptions = options.animation, res = {
delayTime: getRangeValue(animationOptions.delay) * millisecondsToSeconds,
enable: animationOptions.enable,
value: getRangeValue(options.value) * pxRatio,
max: getRangeMax(valueRange) * pxRatio,
min: getRangeMin(valueRange) * pxRatio,
loops: 0,
maxLoops: getRangeValue(animationOptions.count),
time: 0,
}, decayOffset = 1;
if (animationOptions.enable) {
res.decay = decayOffset - getRangeValue(animationOptions.decay);
switch (animationOptions.mode) {
case exports.AnimationMode.increase:
res.status = exports.AnimationStatus.increasing;
break;
case exports.AnimationMode.decrease:
res.status = exports.AnimationStatus.decreasing;
break;
case exports.AnimationMode.random:
res.status = getRandom() >= half ? exports.AnimationStatus.increasing : exports.AnimationStatus.decreasing;
break;
}
const autoStatus = animationOptions.mode === exports.AnimationMode.auto;
switch (animationOptions.startValue) {
case exports.StartValueType.min:
res.value = res.min;
if (autoStatus) {
res.status = exports.AnimationStatus.increasing;
}
break;
case exports.StartValueType.max:
res.value = res.max;
if (autoStatus) {
res.status = exports.AnimationStatus.decreasing;
}
break;
case exports.StartValueType.random:
default:
res.value = randomInRangeValue(res);
if (autoStatus) {
res.status = getRandom() >= half ? exports.AnimationStatus.increasing : exports.AnimationStatus.decreasing;
}
break;
}
}
res.initialValue = res.value;
return res;
}
function updateAnimation(particle, data, changeDirection, destroyType, delta) {
const minLoops = 0, minDelay = 0, identity = 1, minVelocity = 0, minDecay = 1;
if (particle.destroyed ||
!data.enable ||
((data.maxLoops ?? minLoops) > minLoops && (data.loops ?? minLoops) > (data.maxLoops ?? minLoops))) {
return;
}
const velocity = (data.velocity ?? minVelocity) * delta.factor, minValue = data.min, maxValue = data.max, decay = data.decay ?? minDecay;
data.time ??= 0;
const delayTime = data.delayTime ?? minDelay;
if (delayTime > minDelay && data.time < delayTime) {
data.time += delta.value;
if (data.time < delayTime) {
return;
}
}
switch (data.status) {
case exports.AnimationStatus.increasing:
data.value += velocity;
break;
case exports.AnimationStatus.decreasing:
data.value -= velocity;
break;
}
if (data.velocity && decay !== identity) {
data.velocity *= decay;
}
switch (data.status) {
case exports.AnimationStatus.increasing:
if (data.value >= maxValue) {
if (changeDirection) {
data.status = exports.AnimationStatus.decreasing;
}
else {
data.value -= maxValue;
}
data.loops ??= minLoops;
data.loops++;
}
break;
case exports.AnimationStatus.decreasing:
if (data.value <= minValue) {
if (changeDirection) {
data.status = exports.AnimationStatus.increasing;
}
else {
data.value += maxValue;
}
data.loops ??= minLoops;
data.loops++;
}
break;
}
checkDestroy(particle, destroyType, data.value, minValue, maxValue);
if (!particle.destroyed) {
data.value = clamp(data.value, minValue, maxValue);
}
}
class OpacityAnimation extends RangedAnimationOptions {
destroy = exports.DestroyType.none;
load(data) {
super.load(data);
if (isNull(data)) {
return;
}
loadProperty(this, "destroy", data.destroy);
}
}
class Opacity extends RangedAnimationValueWithRandom {
animation = new OpacityAnimation();
value = 1;
load(data) {
if (isNull(data)) {
return;
}
super.load(data);
const animation = data.animation;
if (animation !== undefined) {
this.animation.load(animation);
}
}
}
class OpacityUpdater {
#container;
constructor(container) {
this.#container = container;
}
init(particle) {
const opacityOptions = particle.options.opacity, pxRatio = 1;
if (!opacityOptions) {
return;
}
particle.opacity = initParticleNumericAnimationValue(opacityOptions, pxRatio);
const opacityAnimation = opacityOptions.animation;
if (opacityAnimation.enable) {
particle.opacity.velocity =
(getRangeValue(opacityAnimation.speed) / percentDenominator) * this.#container.retina.reduceFactor;
if (!opacityAnimation.sync) {
particle.opacity.velocity *= getRandom();
}
}
}
isEnabled(particle) {
const none = 0;
return (!particle.destroyed &&
!particle.spawning &&
!!particle.opacity &&
particle.opacity.enable &&
((particle.opacity.maxLoops ?? none) <= none ||
((particle.opacity.maxLoops ?? none) > none &&
(particle.opacity.loops ?? none) < (particle.opacity.maxLoops ?? none))));
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "opacity", Opacity, ...sources);
}
reset(particle) {
if (!particle.opacity) {
return;
}
particle.opacity.time = 0;
particle.opacity.loops = 0;
}
update(particle, delta) {
if (!this.isEnabled(particle) || !particle.opacity || !particle.options.opacity) {
return;
}
updateAnimation(particle, particle.opacity, true, particle.options.opacity.animation.destroy, delta);
}
}
async function loadOpacityUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("opacity", container => {
return Promise.resolve(new OpacityUpdater(container));
});
});
}
const boundsMin = 0;
function bounceHorizontal(data) {
if ((data.outMode !== exports.OutMode.bounce && data.outMode !== exports.OutMode.split) ||
(data.direction !== exports.OutModeDirection.left && data.direction !== exports.OutModeDirection.right)) {
return;
}
if (data.bounds.right < boundsMin && data.direction === exports.OutModeDirection.left) {
data.particle.position.x = data.size + data.offset.x;
}
else if (data.bounds.left > data.canvasSize.width && data.direction === exports.OutModeDirection.right) {
data.particle.position.x = data.canvasSize.width - data.size - data.offset.x;
}
const velocity = data.particle.velocity.x;
let bounced = false;
if (data.outOfCanvas &&
((data.direction === exports.OutModeDirection.right && velocity > minVelocity) ||
(data.direction === exports.OutModeDirection.left && velocity < minVelocity))) {
const newVelocity = getRangeValue(data.particle.options.bounce.horizontal.value);
data.particle.velocity.x *= -newVelocity;
bounced = true;
}
if (!bounced) {
return;
}
const minPos = data.offset.x + data.size;
if (data.outOfCanvas && data.direction === exports.OutModeDirection.right) {
data.particle.position.x = data.canvasSize.width - minPos;
}
else if (data.outOfCanvas && data.direction === exports.OutModeDirection.left) {
data.particle.position.x = minPos;
}
if (data.outMode === exports.OutMode.split) {
data.particle.destroy();
}
}
function bounceVertical(data) {
if ((data.outMode !== exports.OutMode.bounce && data.outMode !== exports.OutMode.split) ||
(data.direction !== exports.OutModeDirection.bottom && data.direction !== exports.OutModeDirection.top)) {
return;
}
if (data.bounds.bottom < boundsMin && data.direction === exports.OutModeDirection.top) {
data.particle.position.y = data.size + data.offset.y;
}
else if (data.bounds.top > data.canvasSize.height && data.direction === exports.OutModeDirection.bottom) {
data.particle.position.y = data.canvasSize.height - data.size - data.offset.y;
}
const velocity = data.particle.velocity.y;
let bounced = false;
if (data.outOfCanvas &&
((data.direction === exports.OutModeDirection.bottom && velocity > minVelocity) ||
(data.direction === exports.OutModeDirection.top && velocity < minVelocity))) {
const newVelocity = getRangeValue(data.particle.options.bounce.vertical.value);
data.particle.velocity.y *= -newVelocity;
bounced = true;
}
if (!bounced) {
return;
}
const minPos = data.offset.y + data.size;
if (data.outOfCanvas && data.direction === exports.OutModeDirection.bottom) {
data.particle.position.y = data.canvasSize.height - minPos;
}
else if (data.outOfCanvas && data.direction === exports.OutModeDirection.top) {
data.particle.position.y = minPos;
}
if (data.outMode === exports.OutMode.split) {
data.particle.destroy();
}
}
class BounceOutMode {
modes;
#container;
#particleBouncePlugins;
constructor(container) {
this.#container = container;
this.modes = [
exports.OutMode.bounce,
exports.OutMode.split,
];
this.#particleBouncePlugins = container.plugins.filter(p => p.particleBounce !== undefined);
}
update(particle, direction, delta, outMode) {
if (!this.modes.includes(outMode)) {
return;
}
const container = this.#container;
let handled = false;
for (const plugin of this.#particleBouncePlugins) {
handled = plugin.particleBounce?.(particle, delta, direction) ?? false;
if (handled) {
break;
}
}
if (handled) {
return;
}
const pos = particle.getPosition(), offset = particle.offset, size = particle.getRadius(), bounds = calculateBounds(pos, size), canvasSize = container.canvas.size, outOfCanvas = !particle.isInsideCanvasForOutMode(outMode, direction);
bounceHorizontal({ particle, outMode, direction, bounds, canvasSize, offset, outOfCanvas, size });
bounceVertical({ particle, outMode, direction, bounds, canvasSize, offset, outOfCanvas, size });
}
}
class DestroyOutMode {
modes;
constructor(_container) {
this.modes = [exports.OutMode.destroy];
}
update(particle, direction, _delta, outMode) {
if (!this.modes.includes(outMode)) {
return;
}
switch (particle.outType) {
case exports.ParticleOutType.normal:
case exports.ParticleOutType.outside:
if (particle.isInsideCanvasForOutMode(outMode, direction)) {
return;
}
break;
case exports.ParticleOutType.inside: {
const { dx, dy } = getDistances(particle.position, particle.moveCenter), { x: vx, y: vy } = particle.velocity;
if ((vx < minVelocity && dx > particle.moveCenter.radius) ||
(vy < minVelocity && dy > particle.moveCenter.radius) ||
(vx >= minVelocity && dx < -particle.moveCenter.radius) ||
(vy >= minVelocity && dy < -particle.moveCenter.radius)) {
return;
}
break;
}
}
particle.destroy(true);
}
}
class NoneOutMode {
modes;
#container;
constructor(container) {
this.#container = container;
this.modes = [exports.OutMode.none];
}
update(particle, direction, _delta, outMode) {
if (!this.modes.includes(outMode)) {
return;
}
if ((particle.options.move.distance.horizontal &&
(direction === exports.OutModeDirection.left || direction === exports.OutModeDirection.right)) ??
(particle.options.move.distance.vertical &&
(direction === exports.OutModeDirection.top || direction === exports.OutModeDirection.bottom))) {
return;
}
const gravityOptions = particle.options.move.gravity, container = this.#container, canvasSize = container.canvas.size, pRadius = particle.getRadius();
if (!gravityOptions.enable) {
if ((particle.velocity.y > minVelocity && particle.position.y <= canvasSize.height + pRadius) ||
(particle.velocity.y < minVelocity && particle.position.y >= -pRadius) ||
(particle.velocity.x > minVelocity && particle.position.x <= canvasSize.width + pRadius) ||
(particle.velocity.x < minVelocity && particle.position.x >= -pRadius)) {
return;
}
if (!isPointInside(particle.position, container.canvas.size, originPoint, pRadius, direction)) {
particle.destroy();
}
}
else {
const position = particle.position;
if ((!gravityOptions.inverse &&
position.y > canvasSize.height + pRadius &&
direction === exports.OutModeDirection.bottom) ||
(gravityOptions.inverse && position.y < -pRadius && direction === exports.OutModeDirection.top)) {
particle.destroy();
}
}
}
}
const updateVector$1 = Vector.origin;
class OutOutMode {
modes;
#container;
constructor(container) {
this.#container = container;
this.modes = [exports.OutMode.out];
}
update(particle, direction, _delta, outMode) {
if (!this.modes.includes(outMode)) {
return;
}
const container = this.#container;
switch (particle.outType) {
case exports.ParticleOutType.inside: {
const { x: vx, y: vy } = particle.velocity;
updateVector$1.setTo(originPoint);
updateVector$1.length = particle.moveCenter.radius;
updateVector$1.angle = particle.velocity.angle + Math.PI;
updateVector$1.addTo(particle.moveCenter);
const { dx, dy } = getDistances(particle.position, updateVector$1);
if ((vx <= minVelocity && dx >= minDistance$6) ||
(vy <= minVelocity && dy >= minDistance$6) ||
(vx >= minVelocity && dx <= minDistance$6) ||
(vy >= minVelocity && dy <= minDistance$6)) {
return;
}
particle.position.x = Math.floor(randomInRangeValue({
min: 0,
max: container.canvas.size.width,
}));
particle.position.y = Math.floor(randomInRangeValue({
min: 0,
max: container.canvas.size.height,
}));
const { dx: newDx, dy: newDy } = getDistances(particle.position, particle.moveCenter);
particle.direction = Math.atan2(-newDy, -newDx);
particle.velocity.angle = particle.direction;
particle.justWarped = true;
break;
}
default: {
if (particle.isInsideCanvasForOutMode(outMode, direction)) {
return;
}
switch (particle.outType) {
case exports.ParticleOutType.outside: {
particle.position.x =
Math.floor(randomInRangeValue({
min: -particle.moveCenter.radius,
max: particle.moveCenter.radius,
})) + particle.moveCenter.x;
particle.position.y =
Math.floor(randomInRangeValue({
min: -particle.moveCenter.radius,
max: particle.moveCenter.radius,
})) + particle.moveCenter.y;
const { dx, dy } = getDistances(particle.position, particle.moveCenter);
if (particle.moveCenter.radius) {
particle.direction = Math.atan2(dy, dx);
particle.velocity.angle = particle.direction;
}
particle.justWarped = true;
break;
}
case exports.ParticleOutType.normal: {
const warp = particle.options.move.warp, canvasSize = container.canvas.size, newPos = {
bottom: canvasSize.height + particle.getRadius() + particle.offset.y,
left: -particle.getRadius() - particle.offset.x,
right: canvasSize.width + particle.getRadius() + particle.offset.x,
top: -particle.getRadius() - particle.offset.y,
}, sizeValue = particle.getRadius(), nextBounds = calculateBounds(particle.position, sizeValue);
if (direction === exports.OutModeDirection.right && nextBounds.left > canvasSize.width + particle.offset.x) {
particle.position.x = newPos.left;
particle.initialPosition.x = particle.position.x;
if (!warp) {
particle.position.y = getRandom() * canvasSize.height;
particle.initialPosition.y = particle.position.y;
}
particle.justWarped = true;
}
else if (direction === exports.OutModeDirection.left && nextBounds.right < -particle.offset.x) {
particle.position.x = newPos.right;
particle.initialPosition.x = particle.position.x;
if (!warp) {
particle.position.y = getRandom() * canvasSize.height;
particle.initialPosition.y = particle.position.y;
}
particle.justWarped = true;
}
if (direction === exports.OutModeDirection.bottom && nextBounds.top > canvasSize.height + particle.offset.y) {
if (!warp) {
particle.position.x = getRandom() * canvasSize.width;
particle.initialPosition.x = particle.position.x;
}
particle.position.y = newPos.top;
particle.initialPosition.y = particle.position.y;
particle.justWarped = true;
}
else if (direction === exports.OutModeDirection.top && nextBounds.bottom < -particle.offset.y) {
if (!warp) {
particle.position.x = getRandom() * canvasSize.width;
particle.initialPosition.x = particle.position.x;
}
particle.position.y = newPos.bottom;
particle.initialPosition.y = particle.position.y;
particle.justWarped = true;
}
break;
}
}
break;
}
}
}
}
const checkOutMode = (outModes, outMode) => {
return (outModes.default === outMode ||
outModes.bottom === outMode ||
outModes.left === outMode ||
outModes.right === outMode ||
outModes.top === outMode);
};
class OutOfCanvasUpdater {
updaters;
#container;
constructor(container) {
this.#container = container;
this.updaters = new Map();
}
init(particle) {
this.#addUpdaterIfMissing(particle, exports.OutMode.bounce, container => new BounceOutMode(container));
this.#addUpdaterIfMissing(particle, exports.OutMode.out, container => new OutOutMode(container));
this.#addUpdaterIfMissing(particle, exports.OutMode.destroy, container => new DestroyOutMode(container));
this.#addUpdaterIfMissing(particle, exports.OutMode.none, container => new NoneOutMode(container));
}
isEnabled(particle) {
return !particle.destroyed && !particle.spawning;
}
update(particle, delta) {
const outModes = particle.options.move.outModes;
particle.justWarped = false;
this.#updateOutMode(particle, delta, outModes.bottom ?? outModes.default, exports.OutModeDirection.bottom);
this.#updateOutMode(particle, delta, outModes.left ?? outModes.default, exports.OutModeDirection.left);
this.#updateOutMode(particle, delta, outModes.right ?? outModes.default, exports.OutModeDirection.right);
this.#updateOutMode(particle, delta, outModes.top ?? outModes.default, exports.OutModeDirection.top);
}
#addUpdaterIfMissing(particle, outMode, getUpdater) {
const outModes = particle.options.move.outModes;
if (!this.updaters.has(outMode) && checkOutMode(outModes, outMode)) {
this.updaters.set(outMode, getUpdater(this.#container));
}
}
#updateOutMode(particle, delta, outMode, direction) {
for (const updater of this.updaters.values()) {
updater.update(particle, direction, delta, outMode);
}
}
}
async function loadOutModesUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("outModes", container => {
return Promise.resolve(new OutOfCanvasUpdater(container));
});
});
}
const defaultOpacity$2 = 1;
class PaintUpdater {
#container;
#pluginManager;
constructor(pluginManager, container) {
this.#container = container;
this.#pluginManager = pluginManager;
}
init(particle) {
const container = this.#container, options = particle.options, paint = itemFromSingleOrMultiple(options.paint, particle.id, options.reduceDuplicates), color = paint?.color, paintColor = color ?? undefined, fill = paint?.fill, stroke = paint?.stroke;
if (fill) {
const fillColor = AnimatableColor.create(paintColor === undefined ? undefined : AnimatableColor.create(undefined, paintColor), fill.color);
particle.fillEnabled = fill.enable;
particle.fillOpacity = getRangeValue(fill.opacity);
particle.fillAnimation = fillColor.animation;
const fillHslColor = rangeColorToHsl(this.#pluginManager, fillColor);
if (fillHslColor) {
particle.fillColor = getHslAnimationFromHsl(fillHslColor, particle.fillAnimation, container.retina.reduceFactor);
}
}
else {
particle.fillEnabled = false;
particle.fillAnimation = undefined;
particle.fillColor = undefined;
particle.fillOpacity = defaultOpacity$2;
}
if (stroke) {
const strokeColor = AnimatableColor.create(paintColor === undefined ? undefined : AnimatableColor.create(undefined, paintColor), stroke.color);
particle.strokeWidth = getRangeValue(stroke.width) * container.retina.pixelRatio;
particle.strokeOpacity = getRangeValue(stroke.opacity ?? defaultOpacity$2);
particle.strokeAnimation = strokeColor.animation;
const strokeHslColor = rangeColorToHsl(this.#pluginManager, strokeColor) ?? particle.getFillColor();
if (strokeHslColor) {
particle.strokeColor = getHslAnimationFromHsl(strokeHslColor, particle.strokeAnimation, container.retina.reduceFactor);
}
}
else {
particle.strokeAnimation = undefined;
particle.strokeColor = undefined;
particle.strokeOpacity = defaultOpacity$2;
particle.strokeWidth = 0;
}
}
isEnabled(particle) {
const { fillAnimation, fillColor, strokeAnimation, strokeColor } = particle, fillEnabled = !!fillAnimation &&
((fillColor?.h.value !== undefined && fillColor.h.enable) ||
(fillColor?.s.value !== undefined && fillColor.s.enable) ||
(fillColor?.l.value !== undefined && fillColor.l.enable)), strokeEnabled = !!strokeAnimation &&
((strokeColor?.h.value !== undefined && strokeColor.h.enable) ||
(strokeColor?.s.value !== undefined && strokeColor.s.enable) ||
(strokeColor?.l.value !== undefined && strokeColor.l.enable));
return !particle.destroyed && !particle.spawning && (fillEnabled || strokeEnabled);
}
update(particle, delta) {
if (!this.isEnabled(particle)) {
return;
}
updateColor(particle.fillColor, delta, this.#container.hdr);
updateColor(particle.strokeColor, delta, this.#container.hdr);
}
}
async function loadPaintUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("paint", container => {
return Promise.resolve(new PaintUpdater(e.pluginManager, container));
});
});
}
var RgbIndexes;
(function (RgbIndexes) {
RgbIndexes[RgbIndexes["r"] = 1] = "r";
RgbIndexes[RgbIndexes["g"] = 2] = "g";
RgbIndexes[RgbIndexes["b"] = 3] = "b";
RgbIndexes[RgbIndexes["a"] = 5] = "a";
})(RgbIndexes || (RgbIndexes = {}));
const rgbRegex = /rgba?\(\s*(\d{1,3})\s*[\s,]\s*(\d{1,3})\s*[\s,]\s*(\d{1,3})\s*([\s,]\s*(0|1|0?\.\d+|(\d{1,3})%)\s*)?\)/i;
class RgbColorManager {
accepts(input) {
return input.startsWith("rgb");
}
handleColor(color) {
const colorValue = color.value, rgbColor = colorValue.rgb ?? color.value;
if (!("r" in rgbColor) || !("g" in rgbColor) || !("b" in rgbColor)) {
return;
}
return rgbColor;
}
handleRangeColor(color) {
const colorValue = color.value, rgbColor = colorValue.rgb ?? color.value;
if (!("r" in rgbColor) || !("g" in rgbColor) || !("b" in rgbColor)) {
return;
}
return {
r: getRangeValue(rgbColor.r),
g: getRangeValue(rgbColor.g),
b: getRangeValue(rgbColor.b),
};
}
parseString(input) {
if (!this.accepts(input)) {
return;
}
const result = rgbRegex.exec(input), radix = 10, minLength = 4, defaultAlpha = 1;
return result
? {
a: result.length > minLength ? parseAlpha(result[RgbIndexes.a]) : defaultAlpha,
b: parseInt(result[RgbIndexes.b] ?? "0", radix),
g: parseInt(result[RgbIndexes.g] ?? "0", radix),
r: parseInt(result[RgbIndexes.r] ?? "0", radix),
}
: undefined;
}
}
async function loadRgbColorPlugin(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addColorManager("rgb", new RgbColorManager());
});
}
class SizeAnimation extends RangedAnimationOptions {
destroy = exports.DestroyType.none;
load(data) {
super.load(data);
if (isNull(data)) {
return;
}
loadProperty(this, "destroy", data.destroy);
}
}
class Size extends RangedAnimationValueWithRandom {
animation = new SizeAnimation();
value = 3;
load(data) {
super.load(data);
if (isNull(data)) {
return;
}
const animation = data.animation;
if (animation !== undefined) {
this.animation.load(animation);
}
}
}
const minLoops = 0;
class SizeUpdater {
#container;
constructor(container) {
this.#container = container;
}
init(particle) {
const container = this.#container, sizeOptions = particle.options.size;
if (!sizeOptions) {
return;
}
const sizeAnimation = sizeOptions.animation;
if (!sizeAnimation.enable) {
return;
}
particle.size.velocity = (particle.retina.sizeAnimationSpeed / percentDenominator) * container.retina.reduceFactor;
if (sizeAnimation.sync) {
return;
}
particle.size.velocity *= getRandom();
}
isEnabled(particle) {
return (!particle.destroyed &&
!particle.spawning &&
particle.size.enable &&
((particle.size.maxLoops ?? minLoops) <= minLoops ||
((particle.size.maxLoops ?? minLoops) > minLoops &&
(particle.size.loops ?? minLoops) < (particle.size.maxLoops ?? minLoops))));
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "size", Size, ...sources);
}
preInit(particle) {
const pxRatio = this.#container.retina.pixelRatio, options = particle.options, sizeOptions = options.size;
if (!sizeOptions) {
return;
}
particle.size = initParticleNumericAnimationValue(sizeOptions, pxRatio);
particle.retina.sizeAnimationSpeed = getRangeValue(sizeOptions.animation.speed) * pxRatio;
}
reset(particle) {
particle.size.time = 0;
particle.size.loops = 0;
}
update(particle, delta) {
if (!this.isEnabled(particle) || !particle.options.size) {
return;
}
updateAnimation(particle, particle.size, true, particle.options.size.animation.destroy, delta);
}
}
async function loadSizeUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("size", container => {
return Promise.resolve(new SizeUpdater(container));
});
});
}
async function loadBasic(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(async (e) => {
await Promise.all([
loadBlendPlugin(e),
loadHexColorPlugin(e),
loadHslColorPlugin(e),
loadRgbColorPlugin(e),
loadMovePlugin(e),
loadCircleShape(e),
loadPaintUpdater(e),
loadOpacityUpdater(e),
loadOutModesUpdater(e),
loadSizeUpdater(e),
]);
});
}
const easingsFunctions = new Map();
easingsFunctions.set("ease-in-quad", value => value ** 2);
easingsFunctions.set("ease-out-quad", value => 1 - (1 - value) ** 2);
easingsFunctions.set("ease-in-out-quad", value => (value < 0.5 ? 2 * value ** 2 : 1 - (-2 * value + 2) ** 2 / 2));
async function loadEasingQuadPlugin(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
for (const [easing, easingFn] of easingsFunctions) {
e.pluginManager.addEasing(easing, easingFn);
}
});
}
const validTypes$1 = ["emoji"];
function drawEmoji(data, image) {
const { context, opacity } = data, previousAlpha = context.globalAlpha, diameter = image.width, radius = diameter * half;
context.globalAlpha = opacity;
context.drawImage(image, -radius, -radius, diameter, diameter);
context.globalAlpha = previousAlpha;
}
function drawLine$1(context, begin, end) {
context.beginPath();
context.moveTo(begin.x, begin.y);
context.lineTo(end.x, end.y);
context.closePath();
}
async function loadFont(font, weight) {
try {
await safeDocument().fonts.load(`${weight ?? "400"} 36px '${font ?? "Verdana"}'`);
}
catch {
}
}
const defaultFont = '"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif', noPadding = 0, firstItem = 0;
class EmojiDrawer {
#emojiShapeDict = new Map();
destroy() {
for (const [key, data] of this.#emojiShapeDict) {
if (data instanceof ImageBitmap) {
data.close();
}
this.#emojiShapeDict.delete(key);
}
}
draw(data) {
const key = data.particle.emojiDataKey;
if (!key) {
return;
}
const image = this.#emojiShapeDict.get(key);
if (!image) {
return;
}
drawEmoji(data, image);
}
async init(container) {
const options = container.actualOptions, shapeData = options.particles.shape;
if (!validTypes$1.some(t => isInArray(t, shapeData.type))) {
return;
}
const promises = [loadFont(defaultFont)], shapeOptions = validTypes$1.map(t => shapeData.options[t])[firstItem];
executeOnSingleOrMultiple(shapeOptions, shape => {
if (shape.font) {
promises.push(loadFont(shape.font));
}
});
await Promise.all(promises);
}
particleDestroy(particle) {
particle.emojiDataKey = undefined;
}
particleInit(container, particle) {
const shapeData = particle.shapeData;
if (!shapeData.value) {
return;
}
const emoji = itemFromSingleOrMultiple(shapeData.value, particle.randomIndexData);
if (!emoji) {
return;
}
const emojiOptions = typeof emoji === "string"
? {
font: shapeData.font ?? defaultFont,
padding: shapeData.padding ?? noPadding,
value: emoji,
}
: {
font: defaultFont,
padding: noPadding,
...shapeData,
...emoji,
}, font = emojiOptions.font, value = emojiOptions.value, cacheKey = `${value}_${font}`;
if (this.#emojiShapeDict.has(cacheKey)) {
particle.emojiDataKey = cacheKey;
return;
}
const padding = emojiOptions.padding * double, maxSize = getRangeMax(particle.size.value), fullSize = maxSize + padding, canvasSize = fullSize * double, cacheCanvas = new OffscreenCanvas(canvasSize, canvasSize), context = cacheCanvas.getContext("2d", container.canvas.render.settings);
if (!context) {
return;
}
context.font = `400 ${(maxSize * double).toString()}px ${font}`;
context.textBaseline = "middle";
context.textAlign = "center";
context.fillText(value, fullSize, fullSize);
const image = cacheCanvas instanceof HTMLCanvasElement ? cacheCanvas : cacheCanvas.transferToImageBitmap();
this.#emojiShapeDict.set(cacheKey, image);
particle.emojiDataKey = cacheKey;
}
}
async function loadEmojiShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addShape(validTypes$1, () => Promise.resolve(new EmojiDrawer()));
});
}
const minFactor = 1, minRadius$3 = 0, updateVector = Vector.origin;
function processAttract(pluginManager, container, position, attractRadius, area, queryCb, onAttractParticle) {
const attractOptions = container.actualOptions.interactivity?.modes.attract;
if (!attractOptions) {
return;
}
const query = container.particles.grid.query(area, queryCb);
for (const particle of query) {
const { dx, dy, distance } = getDistances(particle.position, position), velocity = attractOptions.speed * attractOptions.factor, attractFactor = clamp(pluginManager.getEasing(attractOptions.easing)(identity$2 - distance / attractRadius) * velocity, minFactor, attractOptions.maxSpeed);
updateVector.x = !distance ? velocity : (dx / distance) * attractFactor;
updateVector.y = !distance ? velocity : (dy / distance) * attractFactor;
onAttractParticle?.(particle);
particle.position.subFrom(updateVector);
}
}
function clickAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
container.attract ??= { particles: [] };
const { attract } = container;
if (!attract.finish) {
attract.count ??= 0;
attract.count++;
if (attract.count === container.particles.count) {
attract.finish = true;
}
}
if (attract.clicking) {
const mousePos = interactivityData.mouse.clickPosition, attractRadius = container.retina.attractModeDistance;
if (!attractRadius || attractRadius < minRadius$3 || !mousePos) {
return;
}
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
}
else if (attract.clicking === false) {
attract.particles = [];
}
}
function hoverAttract(pluginManager, container, interactivityData, enabledCb, onAttractParticle) {
const mousePos = interactivityData.mouse.position, attractRadius = container.retina.attractModeDistance;
if (!attractRadius || attractRadius < minRadius$3 || !mousePos) {
return;
}
processAttract(pluginManager, container, mousePos, attractRadius, new Circle(mousePos.x, mousePos.y, attractRadius), (p) => enabledCb(p), onAttractParticle);
}
let Attract$1 = class Attract {
distance = 200;
duration = 0.4;
easing = "ease-out-quad";
factor = 1;
maxSpeed = 50;
restore;
speed = 1;
constructor() {
this.restore = {
enable: false,
delay: 0,
speed: 0.08,
follow: true,
};
}
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "distance", data.distance);
loadProperty(this, "duration", data.duration);
if (data.easing !== undefined) {
this.easing = data.easing;
}
loadProperty(this, "factor", data.factor);
if (data.maxSpeed !== undefined) {
this.maxSpeed = data.maxSpeed;
}
loadProperty(this, "speed", data.speed);
if (data.restore !== undefined) {
this.restore.enable = data.restore.enable ?? this.restore.enable;
this.restore.delay = data.restore.delay ?? this.restore.delay;
this.restore.speed = data.restore.speed ?? this.restore.speed;
this.restore.follow = data.restore.follow ?? this.restore.follow;
}
}
};
const attractMode = "attract", minVelocityLengthSq = 0, minRestoreSpeed$1 = 0.001, maxRestoreSpeed$1 = 1, restoreEpsilon$1 = 0.5;
let Attractor$1 = class Attractor extends ExternalInteractorBase {
handleClickMode;
#interactedThisFrame;
#maxDistance;
#pluginManager;
#restoreData;
constructor(pluginManager, container) {
super(container);
this.#pluginManager = pluginManager;
this.#maxDistance = 0;
this.#interactedThisFrame = new Set();
this.#restoreData = new Map();
container.attract ??= { particles: [] };
this.handleClickMode = (mode, interactivityData) => {
const options = this.container.actualOptions, attract = options.interactivity?.modes.attract;
if (!attract || mode !== attractMode) {
return;
}
container.attract ??= { particles: [] };
container.attract.clicking = true;
container.attract.count = 0;
for (const particle of container.attract.particles) {
if (!this.isEnabled(interactivityData, particle)) {
continue;
}
particle.velocity.setTo(particle.initialVelocity);
}
container.attract.particles = [];
container.attract.finish = false;
setTimeout(() => {
if (container.destroyed) {
return;
}
container.attract ??= { particles: [] };
container.attract.clicking = false;
}, attract.duration * millisecondsToSeconds);
};
}
get maxDistance() {
return this.#maxDistance;
}
clear() {
}
init() {
const container = this.container, attract = container.actualOptions.interactivity?.modes.attract;
if (!attract) {
return;
}
this.#maxDistance = attract.distance;
container.retina.attractModeDistance = attract.distance * container.retina.pixelRatio;
}
interact(interactivityData) {
this.#interactedThisFrame.clear();
const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
if (!events) {
return;
}
const { enable: hoverEnabled, mode: hoverMode } = events.onHover, { enable: clickEnabled, mode: clickMode } = events.onClick;
if (mouseMoveStatus && hoverEnabled && isInArray(attractMode, hoverMode)) {
hoverAttract(this.#pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
this.#trackInteractedParticle(p);
});
}
else if (clickEnabled && isInArray(attractMode, clickMode)) {
clickAttract(this.#pluginManager, this.container, interactivityData, p => this.isEnabled(interactivityData, p), p => {
this.#trackInteractedParticle(p);
});
}
this.#restoreParticles();
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
if ((!mouse.position || !events?.onHover.enable) && (!mouse.clickPosition || !events?.onClick.enable)) {
return false;
}
const hoverMode = events.onHover.mode, clickMode = events.onClick.mode;
return isInArray(attractMode, hoverMode) || isInArray(attractMode, clickMode);
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "attract", Attract$1, ...sources);
}
reset() {
}
#restoreParticles() {
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
if (!restore?.enable || !this.#restoreData.size) {
return;
}
const now = Date.now(), restoreDelay = restore.delay * millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed$1, Math.min(maxRestoreSpeed$1, restore.speed));
for (const [particle, restoreData] of this.#restoreData) {
if (this.#interactedThisFrame.has(particle)) {
continue;
}
if (particle.destroyed) {
this.#restoreData.delete(particle);
continue;
}
const target = restoreData.target;
if (now - restoreData.lastInteractionTime < restoreDelay) {
continue;
}
let dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
if (restore.follow && particle.options.move.enable) {
const { x: vx, y: vy, z: vz } = particle.velocity, velocityLengthSq = vx * vx + vy * vy + vz * vz;
if (velocityLengthSq > minVelocityLengthSq) {
const parallelScale = (dx * vx + dy * vy + dz * vz) / velocityLengthSq;
dx -= vx * parallelScale;
dy -= vy * parallelScale;
dz -= vz * parallelScale;
}
}
particle.position.x += dx * restoreSpeed;
particle.position.y += dy * restoreSpeed;
particle.position.z += dz * restoreSpeed;
if (Math.abs(dx) <= restoreEpsilon$1 && Math.abs(dy) <= restoreEpsilon$1) {
particle.position.x = target.x;
particle.position.y = target.y;
particle.position.z = target.z;
this.#restoreData.delete(particle);
continue;
}
}
}
#trackInteractedParticle(particle) {
this.#interactedThisFrame.add(particle);
const restore = this.container.actualOptions.interactivity?.modes.attract?.restore;
if (!restore?.enable) {
return;
}
const now = Date.now();
let restoreData = this.#restoreData.get(particle);
if (!restoreData) {
restoreData = {
target: particle.position.copy(),
lastInteractionTime: now,
};
this.#restoreData.set(particle, restoreData);
}
restoreData.lastInteractionTime = now;
}
};
async function loadExternalAttractInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalAttract", container => {
return Promise.resolve(new Attractor$1(e.pluginManager, container));
});
});
}
const halfPI = Math.PI * half, toleranceFactor$1 = 10;
function processBounce(container, position, radius, area, enabledCb) {
const query = container.particles.grid.query(area, enabledCb);
for (const particle of query) {
if (area instanceof Circle) {
circleBounce(circleBounceDataFromParticle(particle), {
position,
mass: radius ** squareExp * halfPI,
velocity: Vector.origin,
factor: Vector.origin,
});
}
else if (area instanceof Rectangle) {
rectBounce(particle, calculateBounds(position, radius));
}
}
}
function singleSelectorBounce(container, selector, div, bounceCb) {
const query = safeDocument().querySelectorAll(selector);
if (!query.length) {
return;
}
query.forEach(item => {
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
}, radius = elem.offsetWidth * half * pxRatio, tolerance = toleranceFactor$1 * pxRatio, area = div.type === DivType.circle
? new Circle(pos.x, pos.y, radius + tolerance)
: new Rectangle(elem.offsetLeft * pxRatio - tolerance, elem.offsetTop * pxRatio - tolerance, elem.offsetWidth * pxRatio + tolerance * double, elem.offsetHeight * pxRatio + tolerance * double);
bounceCb(pos, radius, area);
});
}
function divBounce(container, divs, bounceMode, enabledCb) {
divModeExecute(bounceMode, divs, (selector, div) => {
singleSelectorBounce(container, selector, div, (pos, radius, area) => {
processBounce(container, pos, radius, area, enabledCb);
});
});
}
function mouseBounce(container, interactivityData, enabledCb) {
const pxRatio = container.retina.pixelRatio, tolerance = toleranceFactor$1 * pxRatio, mousePos = interactivityData.mouse.position, radius = container.retina.bounceModeDistance;
if (!radius || radius < minRadius$5 || !mousePos) {
return;
}
processBounce(container, mousePos, radius, new Circle(mousePos.x, mousePos.y, radius + tolerance), enabledCb);
}
function rectSideBounce(data) {
const res = { bounced: false }, { pSide, pOtherSide, rectSide, rectOtherSide, velocity, factor } = data;
if (pOtherSide.min < rectOtherSide.min ||
pOtherSide.min > rectOtherSide.max ||
pOtherSide.max < rectOtherSide.min ||
pOtherSide.max > rectOtherSide.max) {
return res;
}
if ((pSide.max >= rectSide.min && pSide.max <= (rectSide.max + rectSide.min) * half && velocity > minVelocity) ||
(pSide.min <= rectSide.max && pSide.min > (rectSide.max + rectSide.min) * half && velocity < minVelocity)) {
res.velocity = velocity * -factor;
res.bounced = true;
}
return res;
}
function rectBounce(particle, divBounds) {
const pPos = particle.getPosition(), size = particle.getRadius(), bounds = calculateBounds(pPos, size), bounceOptions = particle.options.bounce, resH = rectSideBounce({
pSide: {
min: bounds.left,
max: bounds.right,
},
pOtherSide: {
min: bounds.top,
max: bounds.bottom,
},
rectSide: {
min: divBounds.left,
max: divBounds.right,
},
rectOtherSide: {
min: divBounds.top,
max: divBounds.bottom,
},
velocity: particle.velocity.x,
factor: getRangeValue(bounceOptions.horizontal.value),
});
if (resH.bounced) {
if (resH.velocity !== undefined) {
particle.velocity.x = resH.velocity;
}
if (resH.position !== undefined) {
particle.position.x = resH.position;
}
}
const resV = rectSideBounce({
pSide: {
min: bounds.top,
max: bounds.bottom,
},
pOtherSide: {
min: bounds.left,
max: bounds.right,
},
rectSide: {
min: divBounds.top,
max: divBounds.bottom,
},
rectOtherSide: {
min: divBounds.left,
max: divBounds.right,
},
velocity: particle.velocity.y,
factor: getRangeValue(bounceOptions.vertical.value),
});
if (resV.bounced) {
if (resV.velocity !== undefined) {
particle.velocity.y = resV.velocity;
}
if (resV.position !== undefined) {
particle.position.y = resV.position;
}
}
}
class Bounce {
distance = 200;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "distance", data.distance);
}
}
const bounceMode = "bounce";
class Bouncer extends ExternalInteractorBase {
#maxDistance;
constructor(container) {
super(container);
this.#maxDistance = 0;
}
get maxDistance() {
return this.#maxDistance;
}
clear() {
}
init() {
const container = this.container, bounce = container.actualOptions.interactivity?.modes.bounce;
if (!bounce) {
return;
}
this.#maxDistance = bounce.distance;
container.retina.bounceModeDistance = bounce.distance * container.retina.pixelRatio;
}
interact(interactivityData) {
const container = this.container, options = container.actualOptions, events = options.interactivity?.events, mouseMoveStatus = interactivityData.status === mouseMoveEvent;
if (!events) {
return;
}
const hoverEnabled = events.onHover.enable, hoverMode = events.onHover.mode, divs = events.onDiv;
if (mouseMoveStatus && hoverEnabled && isInArray(bounceMode, hoverMode)) {
mouseBounce(this.container, interactivityData, p => this.isEnabled(interactivityData, p));
}
else {
divBounce(this.container, divs, bounceMode, p => this.isEnabled(interactivityData, p));
}
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
if (!events) {
return false;
}
const divs = events.onDiv;
return ((!!mouse.position && events.onHover.enable && isInArray(bounceMode, events.onHover.mode)) ||
isDivModeEnabled(bounceMode, divs));
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "bounce", Bounce, ...sources);
}
reset() {
}
}
async function loadExternalBounceInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalBounce", container => {
return Promise.resolve(new Bouncer(container));
});
});
}
class BubbleBase {
color;
distance = 200;
duration = 0.4;
mix = false;
opacity;
size;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "distance", data.distance);
loadProperty(this, "duration", data.duration);
loadProperty(this, "mix", data.mix);
loadRangeProperty(this, "opacity", data.opacity);
if (data.color !== undefined) {
const sourceColor = isArray(this.color) ? undefined : this.color;
this.color = executeOnSingleOrMultiple(data.color, color => {
return OptionsColor.create(sourceColor, color);
});
}
loadProperty(this, "size", data.size);
}
}
class BubbleDiv extends BubbleBase {
selectors = [];
load(data) {
super.load(data);
if (isNull(data)) {
return;
}
loadProperty(this, "selectors", data.selectors);
}
}
class Bubble extends BubbleBase {
divs;
load(data) {
super.load(data);
if (isNull(data)) {
return;
}
this.divs = executeOnSingleOrMultiple(data.divs, div => {
const tmp = new BubbleDiv();
tmp.load(div);
return tmp;
});
}
}
class BubbleModifier {
div;
enabled = false;
fillColor;
finalColor;
id = "bubble";
inRange = false;
opacity;
priority = 100;
radius;
strokeColor;
}
var ProcessBubbleType;
(function (ProcessBubbleType) {
ProcessBubbleType["color"] = "color";
ProcessBubbleType["opacity"] = "opacity";
ProcessBubbleType["size"] = "size";
})(ProcessBubbleType || (ProcessBubbleType = {}));
function calculateBubbleValue(particleValue, modeValue, optionsValue, ratio) {
if (modeValue >= optionsValue) {
const value = particleValue + (modeValue - optionsValue) * ratio;
return clamp(value, particleValue, modeValue);
}
else if (modeValue < optionsValue) {
const value = particleValue - (optionsValue - modeValue) * ratio;
return clamp(value, modeValue, particleValue);
}
return undefined;
}
const bubbleMode = "bubble", minDistance$5 = 0, defaultClickTime = 0, defaultOpacity$1 = 1, ratioOffset = 1, defaultBubbleValue = 0, minRatio = 0, defaultRatio = 1;
class Bubbler extends ExternalInteractorBase {
handleClickMode;
#maxDistance;
#modifiers = new WeakMap();
#pluginManager;
constructor(pluginManager, container) {
super(container);
this.#pluginManager = pluginManager;
this.#maxDistance = 0;
container.bubble ??= {};
this.handleClickMode = (mode) => {
if (mode !== bubbleMode) {
return;
}
container.bubble ??= {};
container.bubble.clicking = true;
};
}
get maxDistance() {
return this.#maxDistance;
}
clear(particle, _delta, force) {
const mod = this.#modifiers.get(particle);
if (mod?.inRange && !force) {
return;
}
particle.removeModifier(bubbleMode);
this.#modifiers.delete(particle);
}
getOrCreateModifier(particle) {
let mod = this.#modifiers.get(particle);
if (!mod) {
mod = new BubbleModifier();
this.#modifiers.set(particle, mod);
particle.addModifier(mod);
}
return mod;
}
init() {
const container = this.container, bubble = container.actualOptions.interactivity?.modes.bubble;
if (!bubble) {
return;
}
this.#maxDistance = bubble.distance;
container.retina.bubbleModeDistance = bubble.distance * container.retina.pixelRatio;
if (bubble.size !== undefined) {
container.retina.bubbleModeSize = bubble.size * container.retina.pixelRatio;
}
}
interact(interactivityData, delta) {
const options = this.container.actualOptions, events = options.interactivity?.events;
if (!events) {
return;
}
const onHover = events.onHover, onClick = events.onClick, hoverEnabled = onHover.enable, hoverMode = onHover.mode, clickEnabled = onClick.enable, clickMode = onClick.mode, divs = events.onDiv;
if (hoverEnabled && isInArray(bubbleMode, hoverMode)) {
this.#hoverBubble(interactivityData);
}
else if (clickEnabled && isInArray(bubbleMode, clickMode)) {
this.#clickBubble(interactivityData);
}
else {
divModeExecute(bubbleMode, divs, (selector, div) => {
this.#singleSelectorHover(interactivityData, delta, selector, div);
});
}
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
if (!events) {
return false;
}
const { onClick, onDiv, onHover } = events, divBubble = isDivModeEnabled(bubbleMode, onDiv);
if (!(divBubble || (onHover.enable && !!mouse.position) || (onClick.enable && mouse.clickPosition))) {
return false;
}
return isInArray(bubbleMode, onHover.mode) || isInArray(bubbleMode, onClick.mode) || divBubble;
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "bubble", Bubble, ...sources);
}
reset(_interactivityData, particle) {
const mod = this.#modifiers.get(particle);
if (mod) {
mod.enabled = false;
mod.inRange = false;
}
}
#clickBubble(interactivityData) {
const container = this.container, options = container.actualOptions, mouseClickPos = interactivityData.mouse.clickPosition, bubbleOptions = options.interactivity?.modes.bubble;
if (!bubbleOptions || !mouseClickPos) {
return;
}
container.bubble ??= {};
const distance = container.retina.bubbleModeDistance;
if (!distance || distance < minDistance$5) {
return;
}
const query = container.particles.grid.queryCircle(mouseClickPos, distance, p => this.isEnabled(interactivityData, p)), { bubble } = container;
for (const particle of query) {
if (!bubble.clicking) {
continue;
}
const mod = this.getOrCreateModifier(particle);
mod.enabled = !bubble.durationEnd;
mod.inRange = !bubble.durationEnd;
const pos = particle.getPosition(), distMouse = getDistance(pos, mouseClickPos), timeSpent = (performance.now() - (interactivityData.mouse.clickTime ?? defaultClickTime)) / millisecondsToSeconds;
if (timeSpent > bubbleOptions.duration) {
bubble.durationEnd = true;
}
if (timeSpent > bubbleOptions.duration * double) {
bubble.clicking = false;
bubble.durationEnd = false;
}
const sizeData = {
bubbleObj: {
optValue: container.retina.bubbleModeSize,
value: mod.radius,
},
particlesObj: {
optValue: particle.size.max,
value: particle.size.value,
},
type: ProcessBubbleType.size,
};
this.#process(particle, distMouse, timeSpent, sizeData);
const opacityData = {
bubbleObj: {
optValue: bubbleOptions.opacity,
value: mod.opacity,
},
particlesObj: {
optValue: particle.opacity?.max ?? defaultOpacity$1,
value: particle.opacity?.value ?? defaultOpacity$1,
},
type: ProcessBubbleType.opacity,
};
this.#process(particle, distMouse, timeSpent, opacityData);
if (!bubble.durationEnd && distMouse <= distance) {
this.#hoverBubbleColor(particle, distMouse);
}
else {
mod.fillColor = undefined;
mod.strokeColor = undefined;
}
}
}
#hoverBubble(interactivityData) {
const container = this.container, mousePos = interactivityData.mouse.position, distance = container.retina.bubbleModeDistance;
if (!distance || distance < minDistance$5 || !mousePos) {
return;
}
const query = container.particles.grid.queryCircle(mousePos, distance, p => this.isEnabled(interactivityData, p));
for (const particle of query) {
const mod = this.getOrCreateModifier(particle);
mod.enabled = true;
mod.inRange = true;
const pos = particle.getPosition(), pointDistance = getDistance(pos, mousePos), ratio = ratioOffset - pointDistance / distance;
if (pointDistance <= distance) {
if (ratio >= minRatio && interactivityData.status === mouseMoveEvent) {
this.#hoverBubbleSize(particle, ratio);
this.#hoverBubbleOpacity(particle, ratio);
this.#hoverBubbleColor(particle, ratio);
}
}
else {
this.reset(interactivityData, particle);
}
if (interactivityData.status === mouseLeaveEvent) {
this.reset(interactivityData, particle);
}
}
}
#hoverBubbleColor(particle, ratio, divBubble) {
const options = this.container.actualOptions, bubbleOptions = divBubble ?? options.interactivity?.modes.bubble, mod = this.getOrCreateModifier(particle);
if (!bubbleOptions) {
return;
}
if (!mod.finalColor) {
const modeColor = bubbleOptions.color;
if (!modeColor) {
return;
}
const bubbleColor = itemFromSingleOrMultiple(modeColor);
mod.finalColor = rangeColorToHsl(this.#pluginManager, bubbleColor);
}
if (!mod.finalColor) {
return;
}
if (bubbleOptions.mix) {
mod.fillColor = undefined;
mod.strokeColor = undefined;
const pColor = particle.getFillColor();
if (pColor) {
const mixedColor = rgbToHsl(colorMix(pColor, mod.finalColor, ratioOffset - ratio, ratio, this.container.hdr));
mod.fillColor = mixedColor;
mod.strokeColor = mixedColor;
}
else {
mod.fillColor = mod.finalColor;
mod.strokeColor = mod.finalColor;
}
}
else {
mod.fillColor = mod.finalColor;
mod.strokeColor = mod.finalColor;
}
}
#hoverBubbleOpacity(particle, ratio, divBubble) {
const container = this.container, options = container.actualOptions, modeOpacity = divBubble?.opacity ?? options.interactivity?.modes.bubble?.opacity;
if (!modeOpacity) {
return;
}
const pOpacity = particle.opacity?.value ?? defaultOpacity$1, opacity = calculateBubbleValue(pOpacity, modeOpacity, particle.opacity?.max ?? defaultOpacity$1, ratio);
if (opacity !== undefined) {
const mod = this.getOrCreateModifier(particle);
mod.opacity = opacity;
}
}
#hoverBubbleSize(particle, ratio, divBubble) {
const container = this.container, modeSize = divBubble?.size ? divBubble.size * container.retina.pixelRatio : container.retina.bubbleModeSize;
if (modeSize === undefined) {
return;
}
const pSize = particle.size.value, size = calculateBubbleValue(pSize, modeSize, particle.size.max, ratio);
if (size !== undefined) {
const mod = this.getOrCreateModifier(particle);
mod.radius = size;
}
}
#process(particle, distMouse, timeSpent, data) {
const container = this.container, bubbleParam = data.bubbleObj.optValue, options = container.actualOptions, bubbleOptions = options.interactivity?.modes.bubble;
if (!bubbleOptions || bubbleParam === undefined) {
return;
}
const bubbleDuration = bubbleOptions.duration, bubbleDistance = container.retina.bubbleModeDistance, particlesParam = data.particlesObj.optValue, pObjBubble = data.bubbleObj.value, pObj = data.particlesObj.value ?? defaultBubbleValue, type = data.type;
if (!bubbleDistance || bubbleDistance < minDistance$5 || bubbleParam === particlesParam) {
return;
}
container.bubble ??= {};
const mod = this.getOrCreateModifier(particle);
if (container.bubble.durationEnd) {
if (pObjBubble) {
if (type === ProcessBubbleType.size) {
mod.radius = undefined;
}
if (type === ProcessBubbleType.opacity) {
mod.opacity = undefined;
}
}
}
else {
if (distMouse <= bubbleDistance) {
const obj = pObjBubble ?? pObj;
if (obj !== bubbleParam) {
const value = pObj - (timeSpent * (pObj - bubbleParam)) / bubbleDuration;
if (type === ProcessBubbleType.size) {
mod.radius = value;
}
if (type === ProcessBubbleType.opacity) {
mod.opacity = value;
}
}
}
else {
if (type === ProcessBubbleType.size) {
mod.radius = undefined;
}
if (type === ProcessBubbleType.opacity) {
mod.opacity = undefined;
}
}
}
}
#singleSelectorHover(interactivityData, delta, selector, div) {
const container = this.container, selectors = safeDocument().querySelectorAll(selector), bubble = container.actualOptions.interactivity?.modes.bubble;
if (!bubble || !selectors.length) {
return;
}
selectors.forEach(item => {
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
}, repulseRadius = elem.offsetWidth * half * pxRatio, area = div.type === DivType.circle
? new Circle(pos.x, pos.y, repulseRadius)
: new Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio), query = container.particles.grid.query(area, p => this.isEnabled(interactivityData, p));
for (const particle of query) {
if (!area.contains(particle.getPosition())) {
continue;
}
const mod = this.getOrCreateModifier(particle);
mod.enabled = true;
mod.inRange = true;
const divs = bubble.divs, divBubble = divMode(divs, elem);
if (!mod.div || mod.div !== elem) {
this.clear(particle, delta, true);
mod.div = elem;
}
this.#hoverBubbleSize(particle, defaultRatio, divBubble);
this.#hoverBubbleOpacity(particle, defaultRatio, divBubble);
this.#hoverBubbleColor(particle, defaultRatio, divBubble);
}
});
}
}
async function loadExternalBubbleInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalBubble", container => {
return Promise.resolve(new Bubbler(e.pluginManager, container));
});
});
}
class ConnectLinks {
opacity = 0.5;
load(data) {
if (isNull(data)) {
return;
}
loadRangeProperty(this, "opacity", data.opacity);
}
}
class Connect {
distance = 80;
links = new ConnectLinks();
radius = 60;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "distance", data.distance);
this.links.load(data.links);
loadProperty(this, "radius", data.radius);
}
}
const gradientMin = 0, gradientMax = 1, defaultLinksWidth = 0;
function gradient(container, context, p1, p2, opacity) {
const gradStop = Math.floor(p2.getRadius() / p1.getRadius()), color1 = p1.getFillColor(), color2 = p2.getFillColor();
if (!color1 || !color2) {
return;
}
const sourcePos = p1.getPosition(), destPos = p2.getPosition(), midRgb = colorMix(color1, color2, p1.getRadius(), p2.getRadius(), container.hdr), grad = context.createLinearGradient(sourcePos.x, sourcePos.y, destPos.x, destPos.y);
grad.addColorStop(gradientMin, getStyleFromHsl(color1, container.hdr, opacity, container.peakNits, container.hdrMode));
grad.addColorStop(clamp(gradStop, gradientMin, gradientMax), getStyleFromRgb(midRgb, container.hdr, opacity, container.peakNits, container.hdrMode));
grad.addColorStop(gradientMax, getStyleFromHsl(color2, container.hdr, opacity, container.peakNits, container.hdrMode));
return grad;
}
function drawConnectLine(context, width, lineStyle, begin, end) {
drawLine$1(context, begin, end);
context.lineWidth = width;
context.strokeStyle = lineStyle;
context.stroke();
}
function lineStyle(container, ctx, p1, p2) {
const options = container.actualOptions, connectOptions = options.interactivity?.modes.connect;
if (!connectOptions) {
return;
}
return gradient(container, ctx, p1, p2, connectOptions.links.opacity);
}
function drawConnection(container, p1, p2) {
container.canvas.render.draw(ctx => {
const ls = lineStyle(container, ctx, p1, p2);
if (!ls) {
return;
}
const pos1 = p1.getPosition(), pos2 = p2.getPosition();
drawConnectLine(ctx, p1.retina.linksWidth ?? defaultLinksWidth, ls, pos1, pos2);
});
}
const connectMode = "connect", minDistance$4 = 0;
class Connector extends ExternalInteractorBase {
#maxDistance;
constructor(container) {
super(container);
this.#maxDistance = 0;
}
get maxDistance() {
return this.#maxDistance;
}
clear() {
}
init() {
const container = this.container, connect = container.actualOptions.interactivity?.modes.connect;
if (!connect) {
return;
}
this.#maxDistance = connect.distance;
container.retina.connectModeDistance = connect.distance * container.retina.pixelRatio;
container.retina.connectModeRadius = connect.radius * container.retina.pixelRatio;
}
interact(interactivityData) {
const container = this.container, options = container.actualOptions;
if (options.interactivity?.events.onHover.enable && interactivityData.status === "pointermove") {
const mousePos = interactivityData.mouse.position, { connectModeDistance, connectModeRadius } = container.retina;
if (!connectModeDistance ||
connectModeDistance < minDistance$4 ||
!connectModeRadius ||
connectModeRadius < minDistance$4 ||
!mousePos) {
return;
}
const distance = Math.abs(connectModeRadius), query = container.particles.grid.queryCircle(mousePos, distance, p => this.isEnabled(interactivityData, p));
query.forEach((p1, i) => {
const pos1 = p1.getPosition(), indexOffset = 1;
for (const p2 of query.slice(i + indexOffset)) {
const pos2 = p2.getPosition(), distMax = Math.abs(connectModeDistance), xDiff = Math.abs(pos1.x - pos2.x), yDiff = Math.abs(pos1.y - pos2.y);
if (xDiff < distMax && yDiff < distMax) {
drawConnection(container, p1, p2);
}
}
});
}
}
isEnabled(interactivityData, particle) {
const container = this.container, mouse = interactivityData.mouse, events = (particle?.interactivity ?? container.actualOptions.interactivity)?.events;
if (!(events?.onHover.enable && mouse.position)) {
return false;
}
return isInArray(connectMode, events.onHover.mode);
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "connect", Connect, ...sources);
}
reset() {
}
}
async function loadExternalConnectInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalConnect", container => {
return Promise.resolve(new Connector(container));
});
});
}
const toleranceFactor = 10;
function processDestroy(container, area) {
const query = container.particles.grid.query(area);
for (const particle of query) {
particle.destroy();
}
}
function singleSelectorDestroy(container, selector, div) {
const query = safeDocument().querySelectorAll(selector);
if (!query.length) {
return;
}
query.forEach(item => {
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
}, radius = elem.offsetWidth * half * pxRatio, tolerance = toleranceFactor * pxRatio, area = div.type === DivType.circle
? new Circle(pos.x, pos.y, radius + tolerance)
: new Rectangle(elem.offsetLeft * pxRatio - tolerance, elem.offsetTop * pxRatio - tolerance, elem.offsetWidth * pxRatio + tolerance * double, elem.offsetHeight * pxRatio + tolerance * double);
processDestroy(container, area);
});
}
function divDestroy(container, divs, destroyMode) {
divModeExecute(destroyMode, divs, (selector, div) => {
singleSelectorDestroy(container, selector, div);
});
}
function mouseDestroy(container, interactivityData) {
const pxRatio = container.retina.pixelRatio, tolerance = toleranceFactor * pxRatio, mousePos = interactivityData.mouse.position, radius = container.retina.destroyModeDistance;
if (!radius || radius < minRadius$5 || !mousePos) {
return;
}
processDestroy(container, new Circle(mousePos.x, mousePos.y, radius + tolerance));
}
class Destroy {
distance = 200;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "distance", data.distance);
}
}
const destroyMode = "destroy";
class Destroyer extends ExternalInteractorBase {
#maxDistance;
constructor(container) {
super(container);
this.#maxDistance = 0;
}
get maxDistance() {
return this.#maxDistance;
}
clear() {
}
init() {
const container = this.container, destroy = container.actualOptions.interactivity?.modes.destroy;
if (!destroy) {
return;
}
this.#maxDistance = destroy.distance;
container.retina.destroyModeDistance = destroy.distance * container.retina.pixelRatio;
}
interact(interactivityData) {
const container = this.container, options = container.actualOptions, events = options.interactivity?.events, mouseMoveStatus = interactivityData.status === mouseMoveEvent;
if (!events) {
return;
}
const hoverEnabled = events.onHover.enable, hoverMode = events.onHover.mode, divs = events.onDiv;
if (mouseMoveStatus && hoverEnabled && isInArray(destroyMode, hoverMode)) {
mouseDestroy(this.container, interactivityData);
}
else {
divDestroy(this.container, divs, destroyMode);
}
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
if (!events) {
return false;
}
const divs = events.onDiv;
return ((!!mouse.position && events.onHover.enable && isInArray(destroyMode, events.onHover.mode)) ||
isDivModeEnabled(destroyMode, divs));
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "destroy", Destroy, ...sources);
}
reset() {
}
}
async function loadExternalDestroyInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalDestroy", async (container) => {
return Promise.resolve(new Destroyer(container));
});
});
}
class GrabLinks {
blink = false;
color;
consent = false;
opacity = 1;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "blink", data.blink);
if (data.color !== undefined) {
this.color = OptionsColor.create(this.color, data.color);
}
loadProperty(this, "consent", data.consent);
loadRangeProperty(this, "opacity", data.opacity);
}
}
class Grab {
distance = 100;
links = new GrabLinks();
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "distance", data.distance);
this.links.load(data.links);
}
}
const defaultWidth = 0;
function drawGrabLine(context, width, begin, end, colorLine, opacity, hdr = false, peakNits, mode) {
drawLine$1(context, begin, end);
context.strokeStyle = getStyleFromRgb(colorLine, hdr, opacity, peakNits, mode);
context.lineWidth = width;
context.stroke();
}
function drawGrab(container, particle, lineColor, opacity, mousePos) {
container.canvas.render.draw(ctx => {
const beginPos = particle.getPosition();
drawGrabLine(ctx, particle.retina.linksWidth ?? defaultWidth, beginPos, mousePos, lineColor, opacity, container.hdr, container.peakNits, container.hdrMode);
});
}
const grabMode = "grab", minDistance$3 = 0, minOpacity$1 = 0;
class Grabber extends ExternalInteractorBase {
#maxDistance;
#pluginManager;
constructor(pluginManager, container) {
super(container);
this.#pluginManager = pluginManager;
this.#maxDistance = 0;
}
get maxDistance() {
return this.#maxDistance;
}
clear() {
}
init() {
const container = this.container, grab = container.actualOptions.interactivity?.modes.grab;
if (!grab) {
return;
}
this.#maxDistance = grab.distance;
container.retina.grabModeDistance = grab.distance * container.retina.pixelRatio;
}
interact(interactivityData) {
const container = this.container, options = container.actualOptions, interactivity = options.interactivity;
if (!interactivity?.modes.grab ||
!interactivity.events.onHover.enable ||
interactivityData.status !== mouseMoveEvent) {
return;
}
const mousePos = interactivityData.mouse.position;
if (!mousePos) {
return;
}
const distance = container.retina.grabModeDistance;
if (!distance || distance < minDistance$3) {
return;
}
const query = container.particles.grid.queryCircle(mousePos, distance, p => this.isEnabled(interactivityData, p));
for (const particle of query) {
const pos = particle.getPosition(), pointDistance = getDistance(pos, mousePos);
if (pointDistance > distance) {
continue;
}
const grabLineOptions = interactivity.modes.grab.links, lineOpacity = grabLineOptions.opacity, opacityLine = lineOpacity - (pointDistance * lineOpacity) / distance;
if (opacityLine <= minOpacity$1) {
continue;
}
const optColor = grabLineOptions.color ?? particle.options.links?.color;
if (!container.particles.grabLineColor && optColor) {
const linksOptions = interactivity.modes.grab.links;
container.particles.grabLineColor = getLinkRandomColor(this.#pluginManager, optColor, linksOptions.blink, linksOptions.consent);
}
const colorLine = getLinkColor(particle, undefined, container.particles.grabLineColor, container.hdr);
if (!colorLine) {
continue;
}
drawGrab(container, particle, colorLine, opacityLine, mousePos);
}
}
isEnabled(interactivityData, particle) {
const container = this.container, mouse = interactivityData.mouse, events = (particle?.interactivity ?? container.actualOptions.interactivity)?.events;
return !!events?.onHover.enable && !!mouse.position && isInArray(grabMode, events.onHover.mode);
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "grab", Grab, ...sources);
}
reset() {
}
}
async function loadExternalGrabInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalGrab", container => {
return Promise.resolve(new Grabber(e.pluginManager, container));
});
});
}
class Parallax {
force = 2;
smooth = 10;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "force", data.force);
loadProperty(this, "smooth", data.smooth);
}
}
const parallaxMode = "parallax";
class Parallaxer extends ExternalInteractorBase {
maxDistance = 0;
constructor(container) {
super(container);
}
clear() {
}
init() {
}
interact(interactivityData) {
for (const particle of this.container.particles.filter(p => this.isEnabled(interactivityData, p))) {
this.#parallaxInteract(interactivityData, particle);
}
}
isEnabled(interactivityData, particle) {
const container = this.container, mouse = interactivityData.mouse, events = (particle?.interactivity ?? container.actualOptions.interactivity)?.events;
return !!events?.onHover.enable && !!mouse.position && isInArray(parallaxMode, events.onHover.mode);
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "parallax", Parallax, ...sources);
}
reset() {
}
#parallaxInteract(interactivityData, particle) {
if (!this.isEnabled(interactivityData, particle)) {
return;
}
const container = this.container, options = container.actualOptions, parallaxOptions = options.interactivity?.modes.parallax;
if (!parallaxOptions) {
return;
}
const parallaxForce = parallaxOptions.force, mousePos = interactivityData.mouse.position;
if (!mousePos) {
return;
}
const canvasSize = container.canvas.size, canvasCenter = {
x: canvasSize.width * half,
y: canvasSize.height * half,
}, parallaxSmooth = parallaxOptions.smooth, factor = particle.getRadius() / parallaxForce, centerDistance = {
x: (mousePos.x - canvasCenter.x) * factor,
y: (mousePos.y - canvasCenter.y) * factor,
}, { offset } = particle;
offset.x += (centerDistance.x - offset.x) / parallaxSmooth;
offset.y += (centerDistance.y - offset.y) / parallaxSmooth;
}
}
async function loadExternalParallaxInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalParallax", container => {
return Promise.resolve(new Parallaxer(container));
});
});
}
const pauseMode = "pause";
class Pauser extends ExternalInteractorBase {
handleClickMode;
maxDistance = 0;
constructor(container) {
super(container);
this.handleClickMode = (mode) => {
if (mode !== pauseMode) {
return;
}
const container = this.container;
if (container.animationStatus) {
container.pause();
}
else {
container.play();
}
};
}
clear() {
}
init() {
}
interact() {
}
isEnabled(_interactivityData, particle) {
const container = this.container, options = container.actualOptions, events = (particle?.interactivity ?? options.interactivity)?.events;
return !!events && isInArray(pauseMode, events.onClick.mode);
}
reset() {
}
}
async function loadExternalPauseInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalPause", container => {
return Promise.resolve(new Pauser(container));
});
});
}
class Push {
default = true;
groups = [];
particles;
quantity = 4;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "default", data.default);
if (data.groups !== undefined) {
this.groups = data.groups.map(t => t);
}
if (!this.groups.length) {
this.default = true;
}
loadRangeProperty(this, "quantity", data.quantity);
this.particles = executeOnSingleOrMultiple(data.particles, particles => {
return deepExtend({}, particles);
});
}
}
const pushMode = "push", minQuantity = 0;
class Pusher extends ExternalInteractorBase {
handleClickMode;
maxDistance = 0;
constructor(container) {
super(container);
this.handleClickMode = (mode, interactivityData) => {
if (mode !== pushMode) {
return;
}
const container = this.container, options = container.actualOptions, pushOptions = options.interactivity?.modes.push;
if (!pushOptions) {
return;
}
const quantity = getRangeValue(pushOptions.quantity);
if (quantity <= minQuantity) {
return;
}
const group = itemFromArray([undefined, ...pushOptions.groups]), groupOptions = group !== undefined ? container.actualOptions.particles.groups[group] : undefined, particlesOptions = itemFromSingleOrMultiple(pushOptions.particles), overrideOptions = deepExtend(groupOptions, particlesOptions);
container.particles.push(quantity, interactivityData.mouse.position, overrideOptions, group);
};
}
clear() {
}
init() {
}
interact() {
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
return !!events && mouse.clicking && mouse.inside && !!mouse.position && isInArray(pushMode, events.onClick.mode);
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "push", Push, ...sources);
}
reset() {
}
}
async function loadExternalPushInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalPush", container => {
return Promise.resolve(new Pusher(container));
});
});
}
class Remove {
quantity = 2;
load(data) {
if (isNull(data)) {
return;
}
loadRangeProperty(this, "quantity", data.quantity);
}
}
const removeMode = "remove";
class Remover extends ExternalInteractorBase {
handleClickMode;
maxDistance = 0;
constructor(container) {
super(container);
this.handleClickMode = (mode) => {
const container = this.container, options = container.actualOptions;
if (!options.interactivity?.modes.remove || mode !== removeMode) {
return;
}
const removeNb = getRangeValue(options.interactivity.modes.remove.quantity);
for (let i = 0; i < removeNb; i++) {
container.particles.get(i)?.destroy();
}
};
}
clear() {
}
init() {
}
interact() {
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
return !!events && mouse.clicking && mouse.inside && !!mouse.position && isInArray(removeMode, events.onClick.mode);
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "remove", Remove, ...sources);
}
reset() {
}
}
async function loadExternalRemoveInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalRemove", container => {
return Promise.resolve(new Remover(container));
});
});
}
class RepulseBase {
distance = 200;
duration = 0.4;
easing = "ease-out-quad";
factor = 100;
maxSpeed = 50;
restore;
speed = 1;
constructor() {
this.restore = {
enable: false,
delay: 0,
speed: 0.08,
follow: true,
};
}
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "distance", data.distance);
loadProperty(this, "duration", data.duration);
loadProperty(this, "easing", data.easing);
loadProperty(this, "factor", data.factor);
loadProperty(this, "speed", data.speed);
loadProperty(this, "maxSpeed", data.maxSpeed);
if (data.restore !== undefined) {
this.restore.enable = data.restore.enable ?? this.restore.enable;
this.restore.delay = data.restore.delay ?? this.restore.delay;
this.restore.speed = data.restore.speed ?? this.restore.speed;
this.restore.follow = data.restore.follow ?? this.restore.follow;
}
}
}
class RepulseDiv extends RepulseBase {
selectors = [];
load(data) {
super.load(data);
if (isNull(data)) {
return;
}
loadProperty(this, "selectors", data.selectors);
}
}
class Repulse extends RepulseBase {
divs;
load(data) {
super.load(data);
if (isNull(data)) {
return;
}
this.divs = executeOnSingleOrMultiple(data.divs, div => {
const tmp = new RepulseDiv();
tmp.load(div);
return tmp;
});
}
}
const repulseMode = "repulse", minDistance$2 = 0, repulseRadiusFactor = 6, repulseRadiusPower = 3, squarePower = 2, minRadius$2 = 0, minSpeed = 0, easingOffset = 1, minRestoreSpeed = 0.001, maxRestoreSpeed = 1, restoreEpsilon = 0.5;
class Repulser extends ExternalInteractorBase {
handleClickMode;
#clickVec;
#interactedThisFrame;
#maxDistance;
#normVec;
#pluginManager;
#restoreData;
constructor(pluginManager, container) {
super(container);
this.#pluginManager = pluginManager;
this.#maxDistance = 0;
this.#normVec = Vector.origin;
this.#interactedThisFrame = new Set();
this.#clickVec = Vector.origin;
this.#restoreData = new Map();
container.repulse ??= { particles: [] };
this.handleClickMode = (mode, interactivityData) => {
const options = this.container.actualOptions, repulseOpts = options.interactivity?.modes.repulse;
if (!repulseOpts || mode !== repulseMode) {
return;
}
container.repulse ??= { particles: [] };
const repulse = container.repulse;
repulse.clicking = true;
repulse.count = 0;
for (const particle of container.repulse.particles) {
if (!this.isEnabled(interactivityData, particle)) {
continue;
}
particle.velocity.setTo(particle.initialVelocity);
}
repulse.particles = [];
repulse.finish = false;
setTimeout(() => {
if (container.destroyed) {
return;
}
repulse.clicking = false;
}, repulseOpts.duration * millisecondsToSeconds);
};
}
get maxDistance() {
return this.#maxDistance;
}
clear() {
}
init() {
const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
if (!repulse) {
return;
}
this.#maxDistance = repulse.distance;
container.retina.repulseModeDistance = repulse.distance * container.retina.pixelRatio;
}
interact(interactivityData) {
this.#interactedThisFrame.clear();
const container = this.container, options = container.actualOptions, mouseMoveStatus = interactivityData.status === mouseMoveEvent, events = options.interactivity?.events;
if (!events) {
return;
}
const hover = events.onHover, hoverEnabled = hover.enable, hoverMode = hover.mode, click = events.onClick, clickEnabled = click.enable, clickMode = click.mode, divs = events.onDiv;
if (mouseMoveStatus && hoverEnabled && isInArray(repulseMode, hoverMode)) {
this.#hoverRepulse(interactivityData);
}
else if (clickEnabled && isInArray(repulseMode, clickMode)) {
this.#clickRepulse(interactivityData);
}
else {
divModeExecute(repulseMode, divs, (selector, div) => {
this.#singleSelectorRepulse(interactivityData, selector, div);
});
}
this.#restoreParticles();
}
isEnabled(interactivityData, particle) {
const container = this.container, options = container.actualOptions, mouse = interactivityData.mouse, events = (particle?.interactivity ?? options.interactivity)?.events;
if (!events) {
return false;
}
const divs = events.onDiv, hover = events.onHover, click = events.onClick, divRepulse = isDivModeEnabled(repulseMode, divs);
if (!(divRepulse || (hover.enable && !!mouse.position) || (click.enable && mouse.clickPosition))) {
return false;
}
const hoverMode = hover.mode, clickMode = click.mode;
return isInArray(repulseMode, hoverMode) || isInArray(repulseMode, clickMode) || divRepulse;
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "repulse", Repulse, ...sources);
}
reset() {
}
#clickRepulse(interactivityData) {
const container = this.container, repulseOptions = container.actualOptions.interactivity?.modes.repulse;
if (!repulseOptions) {
return;
}
const repulse = container.repulse ?? { particles: [] };
if (!repulse.finish) {
repulse.count ??= 0;
repulse.count++;
if (repulse.count === container.particles.count) {
repulse.finish = true;
}
}
if (repulse.clicking) {
const repulseDistance = container.retina.repulseModeDistance;
if (!repulseDistance || repulseDistance < minDistance$2) {
return;
}
const repulseRadius = Math.pow(repulseDistance / repulseRadiusFactor, repulseRadiusPower), mouseClickPos = interactivityData.mouse.clickPosition;
if (mouseClickPos === undefined) {
return;
}
const range = new Circle(mouseClickPos.x, mouseClickPos.y, repulseRadius), query = container.particles.grid.query(range, p => this.isEnabled(interactivityData, p));
for (const particle of query) {
const { dx, dy, distance } = getDistances(mouseClickPos, particle.position), d = distance ** squarePower, velocity = repulseOptions.speed, force = (-repulseRadius * velocity) / d;
if (d <= repulseRadius) {
this.#trackInteractedParticle(particle);
repulse.particles.push(particle);
this.#clickVec.x = dx;
this.#clickVec.y = dy;
this.#clickVec.length = force;
particle.velocity.setTo(this.#clickVec);
}
}
}
else if (repulse.clicking === false) {
for (const particle of repulse.particles) {
particle.velocity.setTo(particle.initialVelocity);
}
repulse.particles = [];
}
}
#hoverRepulse(interactivityData) {
const container = this.container, mousePos = interactivityData.mouse.position, repulseRadius = container.retina.repulseModeDistance;
if (!repulseRadius || repulseRadius < minRadius$2 || !mousePos) {
return;
}
this.#processRepulse(interactivityData, mousePos, repulseRadius, new Circle(mousePos.x, mousePos.y, repulseRadius));
}
#processRepulse(interactivityData, position, repulseRadius, area, divRepulse) {
const container = this.container, query = container.particles.grid.query(area, p => this.isEnabled(interactivityData, p)), repulseOptions = container.actualOptions.interactivity?.modes.repulse;
if (!repulseOptions) {
return;
}
const { easing, speed, factor, maxSpeed } = repulseOptions, easingFunc = this.#pluginManager.getEasing(easing), velocity = (divRepulse?.speed ?? speed) * factor;
for (const particle of query) {
const { dx, dy, distance } = getDistances(particle.position, position), repulseFactor = clamp(easingFunc(easingOffset - distance / repulseRadius) * velocity, minSpeed, maxSpeed);
this.#normVec.x = !distance ? velocity : (dx / distance) * repulseFactor;
this.#normVec.y = !distance ? velocity : (dy / distance) * repulseFactor;
this.#trackInteractedParticle(particle);
particle.position.addTo(this.#normVec);
}
}
#restoreParticles() {
const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore;
if (!restore?.enable || !this.#restoreData.size) {
return;
}
const now = Date.now(), restoreDelay = restore.delay * millisecondsToSeconds, restoreSpeed = Math.max(minRestoreSpeed, Math.min(maxRestoreSpeed, restore.speed));
for (const [particle, restoreData] of this.#restoreData) {
if (this.#interactedThisFrame.has(particle)) {
continue;
}
if (particle.destroyed) {
this.#restoreData.delete(particle);
continue;
}
const target = restoreData.target;
if (now - restoreData.lastInteractionTime < restoreDelay) {
continue;
}
if (restore.follow && particle.options.move.enable) {
target.x += particle.velocity.x;
target.y += particle.velocity.y;
target.z += particle.velocity.z;
}
const dx = target.x - particle.position.x, dy = target.y - particle.position.y, dz = target.z - particle.position.z;
particle.position.x += dx * restoreSpeed;
particle.position.y += dy * restoreSpeed;
particle.position.z += dz * restoreSpeed;
if (Math.abs(dx) <= restoreEpsilon && Math.abs(dy) <= restoreEpsilon) {
particle.position.x = target.x;
particle.position.y = target.y;
particle.position.z = target.z;
this.#restoreData.delete(particle);
continue;
}
}
}
#singleSelectorRepulse(interactivityData, selector, div) {
const container = this.container, repulse = container.actualOptions.interactivity?.modes.repulse;
if (!repulse) {
return;
}
const query = safeDocument().querySelectorAll(selector);
if (!query.length) {
return;
}
query.forEach(item => {
const elem = item, pxRatio = container.retina.pixelRatio, pos = {
x: (elem.offsetLeft + elem.offsetWidth * half) * pxRatio,
y: (elem.offsetTop + elem.offsetHeight * half) * pxRatio,
}, repulseRadius = elem.offsetWidth * half * pxRatio, area = div.type === DivType.circle
? new Circle(pos.x, pos.y, repulseRadius)
: new Rectangle(elem.offsetLeft * pxRatio, elem.offsetTop * pxRatio, elem.offsetWidth * pxRatio, elem.offsetHeight * pxRatio), divs = repulse.divs, divRepulse = divMode(divs, elem);
this.#processRepulse(interactivityData, pos, repulseRadius, area, divRepulse);
});
}
#trackInteractedParticle(particle) {
this.#interactedThisFrame.add(particle);
const restore = this.container.actualOptions.interactivity?.modes.repulse?.restore;
if (!restore?.enable) {
return;
}
const now = Date.now();
let restoreData = this.#restoreData.get(particle);
if (!restoreData) {
restoreData = {
target: particle.position.copy(),
lastInteractionTime: now,
};
this.#restoreData.set(particle, restoreData);
}
restoreData.lastInteractionTime = now;
if (restore.follow && particle.options.move.enable) {
restoreData.target.x += particle.velocity.x;
restoreData.target.y += particle.velocity.y;
restoreData.target.z += particle.velocity.z;
}
}
}
async function loadExternalRepulseInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
const pluginManager = e.pluginManager;
pluginManager.addInteractor?.("externalRepulse", container => {
return Promise.resolve(new Repulser(pluginManager, container));
});
});
}
class Slow {
factor = 3;
radius = 200;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "factor", data.factor);
loadProperty(this, "radius", data.radius);
}
}
class SlowModifier {
enabled = false;
id = "slow";
priority = 100;
speedFactor = 1;
}
const slowMode = "slow", minRadius$1 = 0;
class Slower extends ExternalInteractorBase {
#maxDistance;
#modifiers = new WeakMap();
constructor(container) {
super(container);
this.#maxDistance = 0;
}
get maxDistance() {
return this.#maxDistance;
}
clear(particle, _delta, force) {
const mod = this.#modifiers.get(particle);
if (mod?.enabled && !force) {
return;
}
particle.removeModifier(slowMode);
this.#modifiers.delete(particle);
}
getOrCreateModifier(particle) {
let mod = this.#modifiers.get(particle);
if (!mod) {
mod = new SlowModifier();
this.#modifiers.set(particle, mod);
particle.addModifier(mod);
}
return mod;
}
init() {
const container = this.container, slow = container.actualOptions.interactivity?.modes.slow;
if (!slow) {
return;
}
this.#maxDistance = slow.radius;
container.retina.slowModeRadius = slow.radius * container.retina.pixelRatio;
}
interact() {
}
isEnabled(interactivityData, particle) {
const container = this.container, mouse = interactivityData.mouse, events = (particle?.interactivity ?? container.actualOptions.interactivity)?.events;
return !!events?.onHover.enable && !!mouse.position && isInArray(slowMode, events.onHover.mode);
}
loadModeOptions(options, ...sources) {
loadOptionProperty(options, "slow", Slow, ...sources);
}
reset(interactivityData, particle) {
const mod = this.#modifiers.get(particle);
if (mod) {
mod.enabled = false;
}
const container = this.container, options = container.actualOptions, mousePos = interactivityData.mouse.position, radius = container.retina.slowModeRadius, slowOptions = options.interactivity?.modes.slow;
if (!slowOptions || !radius || radius < minRadius$1 || !mousePos) {
return;
}
const particlePos = particle.getPosition(), dist = getDistance(mousePos, particlePos), proximityFactor = dist / radius, slowFactor = slowOptions.factor;
if (dist > radius) {
return;
}
const activeMod = this.getOrCreateModifier(particle);
activeMod.enabled = true;
activeMod.speedFactor = proximityFactor / slowFactor;
}
}
async function loadExternalSlowInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("externalSlow", container => {
return Promise.resolve(new Slower(container));
});
});
}
const shapeTypes = ["image", "images"];
const stringStart = 0, defaultOpacity = 1;
const currentColorRegex = /(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d.]+%?\))|currentcolor/gi;
function replaceColorSvg(imageShape, color, opacity, hdr = false, peakNits, mode) {
const { svgData } = imageShape;
if (!svgData) {
return "";
}
const colorStyle = getStyleFromHsl(color, hdr, opacity, peakNits, mode);
if (svgData.includes("fill")) {
return svgData.replaceAll(currentColorRegex, () => colorStyle);
}
const preFillIndex = svgData.indexOf(">");
return `${svgData.substring(stringStart, preFillIndex)} fill="${colorStyle}"${svgData.substring(preFillIndex)}`;
}
async function loadImage(image) {
return new Promise(resolve => {
image.loading = true;
const img = new Image();
image.element = img;
img.addEventListener("load", () => {
image.loading = false;
resolve();
});
img.addEventListener("error", () => {
image.element = undefined;
image.error = true;
image.loading = false;
getLogger().error(`Error loading image: ${image.source}`);
resolve();
});
img.src = image.source;
});
}
async function downloadSvgImage(image) {
if (image.type !== "svg") {
await loadImage(image);
return;
}
image.loading = true;
const response = await fetch(image.source);
if (response.ok) {
image.svgData = await response.text();
}
else {
getLogger().error("Image not found");
image.error = true;
}
image.loading = false;
}
function replaceImageColor(image, imageData, color, particle, hdr = false, peakNits, mode) {
const svgColoredData = replaceColorSvg(image, color, particle.opacity?.value ?? defaultOpacity, hdr, peakNits, mode), imageRes = {
color,
data: {
...image,
svgData: svgColoredData,
},
loaded: false,
ratio: imageData.width / imageData.height,
replaceColor: imageData.replaceColor,
source: imageData.src,
};
return new Promise(resolve => {
const svg = new Blob([svgColoredData], { type: "image/svg+xml" }), url = URL.createObjectURL(svg), img = new Image();
img.addEventListener("load", () => {
imageRes.loaded = true;
imageRes.element = img;
resolve(imageRes);
URL.revokeObjectURL(url);
});
const errorHandler = async () => {
URL.revokeObjectURL(url);
const img2 = {
...image,
error: false,
loading: true,
};
await loadImage(img2);
imageRes.loaded = true;
imageRes.element = img2.element;
resolve(imageRes);
};
img.addEventListener("error", () => void errorHandler());
img.src = url;
});
}
const sides$3 = 12;
class ImageDrawer {
#container;
#engine;
constructor(engine, container) {
this.#engine = engine;
this.#container = container;
}
draw(data) {
const { context, radius, particle, opacity } = data, image = particle.image, element = image?.element;
if (!image) {
return;
}
context.globalAlpha = opacity;
if (element) {
const ratio = image.ratio, pos = {
x: -radius,
y: -radius,
}, diameter = radius * double;
context.drawImage(element, pos.x, pos.y, diameter, diameter / ratio);
}
context.globalAlpha = defaultAlpha$1;
}
getSidesCount() {
return sides$3;
}
async init(container) {
const options = container.actualOptions;
if (!options.preload || !this.#engine.loadImage) {
return;
}
const promises = [];
for (const imageData of options.preload) {
promises.push(this.#engine.loadImage(container, imageData));
}
await Promise.all(promises);
}
loadShape(particle) {
const container = this.#container;
if (!particle.shape || !shapeTypes.includes(particle.shape)) {
return;
}
const imageData = particle.shapeData;
if (!imageData) {
return;
}
const images = this.#engine.getImages?.(container), image = images?.find((t) => t.name === imageData.name || t.source === imageData.src);
if (image) {
return;
}
void this.#loadImageShape(container, imageData).then(() => {
this.loadShape(particle);
});
}
particleInit(container, particle) {
if (particle.shape !== "image" && particle.shape !== "images") {
return;
}
const images = this.#engine.getImages?.(container), imageData = particle.shapeData;
if (!imageData) {
return;
}
const color = particle.getFillColor(), image = images?.find((t) => t.name === imageData.name || t.source === imageData.src);
if (!image) {
return;
}
const replaceColor = imageData.replaceColor;
if (image.loading) {
setTimeout(() => {
this.particleInit(container, particle);
});
return;
}
void (async () => {
let imageRes;
if (image.svgData && color) {
imageRes = await replaceImageColor(image, imageData, color, particle, container.hdr, container.peakNits, container.hdrMode);
}
else {
imageRes = {
color,
data: image,
element: image.element,
loaded: true,
ratio: imageData.width && imageData.height ? imageData.width / imageData.height : (image.ratio ?? defaultRatio$1),
replaceColor: replaceColor,
source: imageData.src,
};
}
if (!imageRes.ratio) {
imageRes.ratio = 1;
}
const close = imageData.close ?? particle.shapeClose, imageShape = {
image: imageRes,
close,
};
particle.image = imageShape.image;
particle.shapeClose = imageShape.close;
})();
}
#loadImageShape = async (container, imageShape) => {
if (!this.#engine.loadImage) {
throw new Error(`Image shape not initialized`);
}
await this.#engine.loadImage(container, {
name: imageShape.name,
replaceColor: imageShape.replaceColor,
src: imageShape.src,
});
};
}
class Preload {
height;
name;
replaceColor;
src = "";
width;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "height", data.height);
loadProperty(this, "name", data.name);
loadProperty(this, "replaceColor", data.replaceColor);
loadProperty(this, "src", data.src);
loadProperty(this, "width", data.width);
}
}
class ImagePreloaderPlugin {
id = "image-preloader";
#engine;
constructor(engine) {
this.#engine = engine;
}
async getPlugin(container) {
const { ImagePreloaderInstance } = await Promise.resolve().then(function () { return ImagePreloaderInstance$1; });
return new ImagePreloaderInstance(this.#engine, container);
}
loadOptions(_container, options, source) {
if (!source?.preload) {
return;
}
options.preload ??= [];
const preloadOptions = options.preload;
for (const item of source.preload) {
const existing = preloadOptions.find(t => t.name === item.name || t.src === item.src);
if (existing) {
existing.load(item);
}
else {
const preload = new Preload();
preload.load(item);
preloadOptions.push(preload);
}
}
}
needsPlugin() {
return true;
}
}
const extLength = 3;
function addLoadImageToEngine(engine) {
engine.getImages ??= (container) => {
engine.images ??= new Map();
let images = engine.images.get(container);
if (!images) {
images = [];
engine.images.set(container, images);
}
return images;
};
engine.loadImage ??= async (container, data) => {
if (!engine.getImages) {
throw new Error("No images collection found");
}
if (!data.name && !data.src) {
throw new Error("No image source provided");
}
engine.images ??= new Map();
const containerImages = engine.getImages(container);
if (containerImages.some((t) => t.name === data.name || t.source === data.src)) {
return;
}
try {
const image = {
name: data.name ?? data.src,
source: data.src,
type: data.src.substring(data.src.length - extLength),
error: false,
loading: true,
replaceColor: data.replaceColor,
ratio: data.width && data.height ? data.width / data.height : undefined,
};
containerImages.push(image);
engine.images.set(container, containerImages);
let imageFunc;
if (data.replaceColor) {
imageFunc = downloadSvgImage;
}
else {
imageFunc = loadImage;
}
await imageFunc(image);
}
catch {
throw new Error(`${data.name ?? data.src} not found`);
}
};
}
async function loadImageShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
addLoadImageToEngine(e);
e.pluginManager.addPlugin(new ImagePreloaderPlugin(e));
e.pluginManager.addShape(shapeTypes, container => Promise.resolve(new ImageDrawer(e, container)));
});
}
class LifeDelay extends ValueWithRandom {
sync = false;
load(data) {
if (isNull(data)) {
return;
}
super.load(data);
loadProperty(this, "sync", data.sync);
}
}
class LifeDuration extends ValueWithRandom {
sync = false;
load(data) {
if (isNull(data)) {
return;
}
super.load(data);
loadProperty(this, "sync", data.sync);
}
}
class Life {
count = 0;
delay = new LifeDelay();
duration = new LifeDuration();
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "count", data.count);
this.delay.load(data.delay);
this.duration.load(data.duration);
}
}
const noTime$1 = 0, infiniteValue$1 = -1, noLife = 0, minCanvasSize = 0;
function updateLife(particle, delta, canvasSize) {
if (!particle.life) {
return;
}
const life = particle.life;
let justSpawned = false;
if (particle.spawning) {
life.delayTime += delta.value;
if (life.delayTime >= particle.life.delay) {
justSpawned = true;
particle.spawning = false;
life.delayTime = noTime$1;
life.time = noTime$1;
}
else {
return;
}
}
if (life.duration === infiniteValue$1) {
return;
}
if (justSpawned) {
life.time = noTime$1;
}
else {
life.time += delta.value;
}
if (life.time < life.duration) {
return;
}
life.time = noTime$1;
if (particle.life.count > noLife) {
particle.life.count--;
}
if (particle.life.count === noLife) {
particle.destroy();
return;
}
const widthRange = setRangeValue(minCanvasSize, canvasSize.width), heightRange = setRangeValue(minCanvasSize, canvasSize.width);
particle.position.x = randomInRangeValue(widthRange);
particle.position.y = randomInRangeValue(heightRange);
particle.spawning = true;
life.delayTime = noTime$1;
life.time = noTime$1;
particle.reset();
const lifeOptions = particle.options.life;
if (lifeOptions) {
life.delay = getRangeValue(lifeOptions.delay.value) * millisecondsToSeconds;
life.duration = getRangeValue(lifeOptions.duration.value) * millisecondsToSeconds;
}
}
const noTime = 0, identity$1 = 1, infiniteValue = -1;
class LifeUpdater {
#container;
constructor(container) {
this.#container = container;
}
init(particle) {
const container = this.#container, particlesOptions = particle.options, lifeOptions = particlesOptions.life;
if (!lifeOptions) {
return;
}
const delayFactor = lifeOptions.delay.sync ? identity$1 : getRandom(), durationFactor = lifeOptions.duration.sync ? identity$1 : getRandom();
particle.life = {
delay: container.retina.reduceFactor
? ((getRangeValue(lifeOptions.delay.value) * delayFactor) / container.retina.reduceFactor) *
millisecondsToSeconds
: noTime,
delayTime: noTime,
duration: container.retina.reduceFactor
? ((getRangeValue(lifeOptions.duration.value) * durationFactor) / container.retina.reduceFactor) *
millisecondsToSeconds
: noTime,
time: noTime,
count: lifeOptions.count,
};
if (particle.life.duration <= noTime) {
particle.life.duration = infiniteValue;
}
if (particle.life.count <= noTime) {
particle.life.count = infiniteValue;
}
particle.spawning = particle.life.delay > noTime;
}
isEnabled(particle) {
return !particle.destroyed;
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "life", Life, ...sources);
}
update(particle, delta) {
if (!this.isEnabled(particle) || !particle.life) {
return;
}
updateLife(particle, delta, this.#container.canvas.size);
}
}
async function loadLifeUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("life", container => {
return Promise.resolve(new LifeUpdater(container));
});
});
}
function drawLine(data) {
const { context, particle, radius } = data, shapeData = particle.shapeData, centerY = 0;
context.moveTo(-radius, centerY);
context.lineTo(radius, centerY);
context.lineCap = shapeData?.cap ?? "butt";
}
const sides$2 = 1;
class LineDrawer {
draw(data) {
drawLine(data);
}
getSidesCount() {
return sides$2;
}
}
async function loadLineShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addShape(["line"], () => Promise.resolve(new LineDrawer()));
});
}
class Attract {
distance = 200;
enable = false;
rotate;
constructor() {
this.rotate = {
x: 3000,
y: 3000,
};
}
load(data) {
if (isNull(data)) {
return;
}
loadRangeProperty(this, "distance", data.distance);
loadProperty(this, "enable", data.enable);
if (data.rotate) {
const rotateX = data.rotate.x;
if (rotateX !== undefined) {
this.rotate.x = rotateX;
}
const rotateY = data.rotate.y;
if (rotateY !== undefined) {
this.rotate.y = rotateY;
}
}
}
}
const attractFactor = 1000, identity = 1;
class Attractor extends ParticlesInteractorBase {
#maxDistance;
constructor(container) {
super(container);
this.#maxDistance = 0;
}
get maxDistance() {
return this.#maxDistance;
}
clear() {
}
init() {
}
interact(p1) {
if (!p1.options.attract?.enable) {
return;
}
const container = this.container;
if (isNull(p1.attractDistance)) {
const attractDistance = getRangeValue(p1.options.attract.distance);
if (attractDistance > this.#maxDistance) {
this.#maxDistance = attractDistance;
}
p1.attractDistance = attractDistance * container.retina.pixelRatio;
}
const distance = p1.attractDistance, pos1 = p1.getPosition(), query = container.particles.grid.queryCircle(pos1, distance);
for (const p2 of query) {
if (p1 === p2 || !p2.options.attract?.enable || p2.destroyed || p2.spawning) {
continue;
}
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 / p1Factor;
p1.velocity.x -= ax * p1Factor;
p1.velocity.y -= ay * p1Factor;
p2.velocity.x += ax * p2Factor;
p2.velocity.y += ay * p2Factor;
}
}
isEnabled(particle) {
return particle.options.attract?.enable ?? false;
}
loadParticlesOptions(options, ...sources) {
loadOptionProperty(options, "attract", Attract, ...sources);
}
reset() {
}
}
async function loadParticlesAttractInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addInteractor?.("particlesAttract", container => {
return Promise.resolve(new Attractor(container));
});
});
}
var CollisionMode;
(function (CollisionMode) {
CollisionMode["absorb"] = "absorb";
CollisionMode["bounce"] = "bounce";
CollisionMode["destroy"] = "destroy";
})(CollisionMode || (CollisionMode = {}));
class CollisionsAbsorb {
speed = 2;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "speed", data.speed);
}
}
class CollisionsOverlap {
enable = true;
retries = 0;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "enable", data.enable);
loadProperty(this, "retries", data.retries);
}
}
class Collisions {
absorb = new CollisionsAbsorb();
bounce = new ParticlesBounce();
enable = false;
maxSpeed = 50;
mode = CollisionMode.bounce;
overlap = new CollisionsOverlap();
load(data) {
if (isNull(data)) {
return;
}
this.absorb.load(data.absorb);
this.bounce.load(data.bounce);
loadProperty(this, "enable", data.enable);
loadRangeProperty(this, "maxSpeed", data.maxSpeed);
loadProperty(this, "mode", data.mode);
this.overlap.load(data.overlap);
}
}
const minAbsorbAmount = 0;
function updateAbsorb(p1, r1, p2, r2, delta, pixelRatio) {
if (!p1.options.collisions || !p2.options.collisions) {
return;
}
const absorbSpeed = p1.options.collisions.absorb.speed, shrinkAmount = clamp(absorbSpeed * delta.factor, minAbsorbAmount, r2);
p1.size.value = Math.sqrt(r1 * r1 + shrinkAmount * shrinkAmount);
p2.size.value -= shrinkAmount;
if (p2.size.value <= pixelRatio) {
p2.size.value = 0;
p2.destroy();
}
}
function absorb(p1, p2, delta, pixelRatio) {
const r1 = p1.getRadius(), r2 = p2.getRadius();
if (!r1 && r2) {
p1.destroy();
}
else if (r1 && !r2) {
p2.destroy();
}
else if (r1 && r2) {
if (r1 >= r2) {
updateAbsorb(p1, r1, p2, r2, delta, pixelRatio);
}
else {
updateAbsorb(p2, r2, p1, r1, delta, pixelRatio);
}
}
}
const energyCorrectionMinRatio = 1e-6, energyDriftThreshold = 1e-4, correctionFactorNeutral = 1, fixBounceSpeed = (p) => {
if (!p.options.collisions) {
return;
}
p.collisionMaxSpeed ??= getRangeValue(p.options.collisions.maxSpeed);
if (p.velocity.length > p.collisionMaxSpeed) {
p.velocity.length = p.collisionMaxSpeed;
}
};
function bounce(p1, p2) {
const m1 = p1.getMass(), m2 = p2.getMass(), speed1Before = p1.velocity.length, speed2Before = p2.velocity.length, keBefore = m1 * speed1Before * speed1Before + m2 * speed2Before * speed2Before;
circleBounce(circleBounceDataFromParticle(p1), circleBounceDataFromParticle(p2));
const speed1After = p1.velocity.length, speed2After = p2.velocity.length, keAfter = m1 * speed1After * speed1After + m2 * speed2After * speed2After;
if (keAfter > keBefore * energyCorrectionMinRatio) {
const correctionFactor = Math.sqrt(keBefore / keAfter);
if (Math.abs(correctionFactor - correctionFactorNeutral) > energyDriftThreshold) {
p1.velocity.length = speed1After * correctionFactor;
p2.velocity.length = speed2After * correctionFactor;
}
}
fixBounceSpeed(p1);
fixBounceSpeed(p2);
}
function destroy(p1, p2) {
if (!p1.unbreakable && !p2.unbreakable) {
bounce(p1, p2);
}
const p1Radius = p1.getRadius(), p2Radius = p2.getRadius();
if (!p1Radius && p2Radius) {
p1.destroy();
}
else if (p1Radius && !p2Radius) {
p2.destroy();
}
else if (p1Radius && p2Radius) {
const deleteP = p1.getRadius() >= p2.getRadius() ? p2 : p1;
deleteP.destroy();
}
}
function resolveCollision(p1, p2, delta, pixelRatio) {
if (!p1.options.collisions || !p2.options.collisions) {
return;
}
switch (p1.options.collisions.mode) {
case CollisionMode.absorb: {
absorb(p1, p2, delta, pixelRatio);
break;
}
case CollisionMode.bounce: {
bounce(p1, p2);
break;
}
case CollisionMode.destroy: {
destroy(p1, p2);
break;
}
}
}
class Collider extends ParticlesInteractorBase {
maxDistance;
constructor(container) {
super(container);
this.maxDistance = 0;
}
clear() {
}
init() {
}
interact(p1, _interactivityData, delta) {
if (p1.destroyed || p1.spawning) {
return;
}
const container = this.container, pos1 = p1.getPosition(), radius1 = p1.getRadius(), query = container.particles.grid.queryCircle(pos1, radius1 * double);
for (const p2 of query) {
if (p1 === p2 ||
p1.id >= p2.id ||
!p1.options.collisions?.enable ||
!p2.options.collisions?.enable ||
p1.options.collisions.mode !== p2.options.collisions.mode ||
p2.destroyed ||
p2.spawning) {
continue;
}
const pos2 = p2.getPosition(), radius2 = p2.getRadius();
if (Math.abs(Math.round(pos1.z) - Math.round(pos2.z)) > radius1 + radius2) {
continue;
}
const dist = getDistance(pos1, pos2), distP = radius1 + radius2;
if (dist > distP) {
continue;
}
resolveCollision(p1, p2, delta, container.retina.pixelRatio);
}
}
isEnabled(particle) {
return !!particle.options.collisions?.enable;
}
loadParticlesOptions(options, ...sources) {
loadOptionProperty(options, "collisions", Collisions, ...sources);
}
reset() {
}
}
class OverlapPlugin {
id = "overlap";
async getPlugin(container) {
const { OverlapPluginInstance } = await Promise.resolve().then(function () { return OverlapPluginInstance$1; });
return new OverlapPluginInstance(container);
}
loadOptions() {
}
needsPlugin() {
return true;
}
}
async function loadParticlesCollisionsInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
ensureInteractivityPluginLoaded(e);
e.pluginManager.addPlugin(new OverlapPlugin());
e.pluginManager.addInteractor?.("particlesCollisions", container => {
return Promise.resolve(new Collider(container));
});
});
}
class CircleWarp extends Circle {
#canvasSize;
constructor(x, y, radius, canvasSize) {
super(x, y, radius);
this.#canvasSize = canvasSize;
}
contains(point) {
if (super.contains(point))
return true;
const { width, height } = this.#canvasSize, { x, y } = point;
return (super.contains({ x: x - width, y }) ||
super.contains({ x: x + width, y }) ||
super.contains({ x, y: y - height }) ||
super.contains({ x, y: y + height }) ||
super.contains({ x: x - width, y: y - height }) ||
super.contains({ x: x + width, y: y + height }) ||
super.contains({ x: x - width, y: y + height }) ||
super.contains({ x: x + width, y: y - height }));
}
intersects(range) {
if (super.intersects(range))
return true;
const { width, height } = this.#canvasSize, pos = range.position, shifts = [
{ x: -width, y: 0 },
{ x: width, y: 0 },
{ x: 0, y: -height },
{ x: 0, y: height },
{ x: -width, y: -height },
{ x: width, y: height },
{ x: -width, y: height },
{ x: width, y: -height },
];
for (const shift of shifts) {
const shiftedPos = { x: pos.x + shift.x, y: pos.y + shift.y };
let shiftedRange;
if (range instanceof Circle) {
shiftedRange = new Circle(shiftedPos.x, shiftedPos.y, range.radius);
}
else {
const rect = range;
shiftedRange = new Rectangle(shiftedPos.x, shiftedPos.y, rect.size.width, rect.size.height);
}
if (super.intersects(shiftedRange))
return true;
}
return false;
}
}
class LinksShadow {
blur = 5;
color = new OptionsColor();
enable = false;
constructor() {
this.color.value = "#000";
}
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "blur", data.blur);
this.color = OptionsColor.create(this.color, data.color);
loadProperty(this, "enable", data.enable);
}
}
class LinksTriangle {
color;
enable = false;
frequency = 1;
opacity;
load(data) {
if (isNull(data)) {
return;
}
if (data.color !== undefined) {
this.color = OptionsColor.create(this.color, data.color);
}
loadProperty(this, "enable", data.enable);
loadProperty(this, "frequency", data.frequency);
loadProperty(this, "opacity", data.opacity);
}
}
class Links {
blink = false;
color = new OptionsColor();
consent = false;
distance = 100;
enable = false;
frequency = 1;
id;
opacity = 1;
shadow = new LinksShadow();
triangles = new LinksTriangle();
warp = false;
width = 1;
constructor() {
this.color.value = "#fff";
}
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "id", data.id);
loadProperty(this, "blink", data.blink);
this.color = OptionsColor.create(this.color, data.color);
loadProperty(this, "consent", data.consent);
loadProperty(this, "distance", data.distance);
loadProperty(this, "enable", data.enable);
loadProperty(this, "frequency", data.frequency);
loadProperty(this, "opacity", data.opacity);
this.shadow.load(data.shadow);
this.triangles.load(data.triangles);
loadProperty(this, "width", data.width);
loadProperty(this, "warp", data.warp);
}
}
const opacityOffset = 1, minDistance$1 = 0;
function getWarpDistance(pos1, pos2, canvasSize) {
const { dx, dy } = getDistances(pos1, pos2), absDiffs = { x: Math.abs(dx), y: Math.abs(dy) }, warpDistances = {
x: Math.min(absDiffs.x, canvasSize.width - absDiffs.x),
y: Math.min(absDiffs.y, canvasSize.height - absDiffs.y),
};
return Math.hypot(warpDistances.x, warpDistances.y);
}
class Linker extends ParticlesInteractorBase {
#maxDistance;
#pluginManager;
constructor(pluginManager, container) {
super(container);
this.#pluginManager = pluginManager;
this.#maxDistance = 0;
}
get maxDistance() {
return this.#maxDistance;
}
clear() {
}
init() {
this.container.particles.linksColor = undefined;
this.container.particles.linksColors = new Map();
}
interact(p1) {
if (!p1.options.links) {
return;
}
p1.links = [];
if (p1.linksDistance && p1.linksDistance > this.#maxDistance) {
this.#maxDistance = p1.linksDistance;
}
const pos1 = p1.getPosition(), container = this.container, canvasSize = container.canvas.size;
if (pos1.x < originPoint.x || pos1.y < originPoint.y || pos1.x > canvasSize.width || pos1.y > canvasSize.height) {
return;
}
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);
for (const p2 of query) {
const linkOpt2 = p2.options.links;
if (p1 === p2 ||
!linkOpt2?.enable ||
linkOpt1.id !== linkOpt2.id ||
p2.spawning ||
p2.destroyed ||
!p2.links ||
p1.links.some(t => t.destination === p2) ||
p2.links.some(t => t.destination === p1)) {
continue;
}
const pos2 = p2.getPosition();
if (pos2.x < originPoint.x || pos2.y < originPoint.y || pos2.x > canvasSize.width || pos2.y > canvasSize.height) {
continue;
}
const distDirect = getDistances(pos1, pos2).distance, distWarp = warp && linkOpt2.warp ? getWarpDistance(pos1, pos2, canvasSize) : distDirect, distance = Math.min(distDirect, distWarp);
if (distance > optDistance) {
continue;
}
const opacityLine = (opacityOffset - distance / optDistance) * optOpacity;
this.#setColor(p1);
p1.links.push({
destination: p2,
opacity: opacityLine,
color: this.#getLinkColor(p1, p2),
isWarped: distWarp < distDirect,
});
}
}
isEnabled(particle) {
return !!particle.options.links?.enable;
}
loadParticlesOptions(options, ...sources) {
loadOptionProperty(options, "links", Links, ...sources);
}
reset() {
}
#getLinkColor(p1, p2) {
const container = this.container, linksOptions = p1.options.links;
if (!linksOptions) {
return;
}
const linkColor = linksOptions.id !== undefined
? container.particles.linksColors.get(linksOptions.id)
: container.particles.linksColor;
return getLinkColor(p1, p2, linkColor, container.hdr);
}
#setColor(p1) {
if (!p1.options.links) {
return;
}
const container = this.container, linksOptions = p1.options.links;
let linkColor = linksOptions.id === undefined
? container.particles.linksColor
: container.particles.linksColors.get(linksOptions.id);
if (linkColor) {
return;
}
linkColor = getLinkRandomColor(this.#pluginManager, linksOptions.color, linksOptions.blink, linksOptions.consent);
if (linksOptions.id === undefined) {
container.particles.linksColor = linkColor;
}
else {
container.particles.linksColors.set(linksOptions.id, linkColor);
}
}
}
class LinksPlugin {
id = "links";
#pluginManager;
constructor(pluginManager) {
this.#pluginManager = pluginManager;
}
async getPlugin(container) {
const { LinkInstance } = await Promise.resolve().then(function () { return LinkInstance$1; });
return new LinkInstance(this.#pluginManager, container);
}
loadOptions() {
}
needsPlugin() {
return true;
}
}
async function loadParticlesLinksInteraction(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register((e) => {
const pluginManager = e.pluginManager;
ensureInteractivityPluginLoaded(e);
pluginManager.addPlugin(new LinksPlugin(pluginManager));
pluginManager.addInteractor?.("particlesLinks", container => {
return Promise.resolve(new Linker(pluginManager, container));
});
});
}
const polygonCache = new Map(), noOffset = 0;
function getUnitPolygon(sides) {
const cached = polygonCache.get(sides);
if (cached) {
return cached;
}
const step = doublePI / sides, isOdd = !!(sides % double), baseAngle = (-Math.PI + (isOdd ? noOffset : step)) * half, verts = [];
for (let i = 0; i < sides; i++) {
const angle = baseAngle + i * step;
verts[i] = {
x: Math.cos(angle),
y: Math.sin(angle),
};
}
polygonCache.set(sides, verts);
return verts;
}
function drawPolygon(data, side) {
const { context, radius } = data, sides = side.count.numerator / side.count.denominator, verts = getUnitPolygon(sides);
context.beginPath();
for (let i = 0; i < verts.length; i++) {
const vert = verts[i];
if (!vert) {
continue;
}
const x = vert.x * radius, y = vert.y * radius;
if (i) {
context.lineTo(x, y);
}
else {
context.moveTo(x, y);
}
}
context.closePath();
}
const defaultSides$1 = 5;
class PolygonDrawerBase {
draw(data) {
const { particle, radius } = data, side = this.getSidesData(particle, radius);
drawPolygon(data, side);
}
getSidesCount(particle) {
const polygon = particle.shapeData;
return Math.round(getRangeValue(polygon?.sides ?? defaultSides$1));
}
}
const yFactor$1 = 2.66, sidesFactor$1 = 3;
class PolygonDrawer extends PolygonDrawerBase {
getSidesData(particle, radius) {
const { sides } = particle;
return {
count: {
denominator: 1,
numerator: sides,
},
length: (radius * yFactor$1) / (sides / sidesFactor$1),
};
}
}
const sides$1 = 3, yFactor = 2.66, sidesFactor = 3;
class TriangleDrawer extends PolygonDrawerBase {
getSidesCount() {
return sides$1;
}
getSidesData(_particle, radius) {
return {
count: {
denominator: 1,
numerator: sides$1,
},
length: (radius * yFactor) / (sides$1 / sidesFactor),
};
}
}
async function loadGenericPolygonShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addShape(["polygon"], () => Promise.resolve(new PolygonDrawer()));
});
}
async function loadTriangleShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addShape(["triangle"], () => Promise.resolve(new TriangleDrawer()));
});
}
async function loadPolygonShape(engine) {
engine.checkVersion("4.4.0");
await Promise.all([
loadGenericPolygonShape(engine),
loadTriangleShape(engine),
]);
}
class RotateAnimation {
decay = 0;
enable = false;
speed = 0;
sync = false;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "enable", data.enable);
loadRangeProperty(this, "speed", data.speed);
loadRangeProperty(this, "decay", data.decay);
loadProperty(this, "sync", data.sync);
}
}
class Rotate extends ValueWithRandom {
animation = new RotateAnimation();
direction = exports.RotateDirection.clockwise;
path = false;
load(data) {
if (isNull(data)) {
return;
}
super.load(data);
loadProperty(this, "direction", data.direction);
this.animation.load(data.animation);
loadProperty(this, "path", data.path);
}
}
const doublePIDeg = 360;
class RotateUpdater {
#container;
constructor(container) {
this.#container = container;
}
init(particle) {
const rotateOptions = particle.options.rotate;
if (!rotateOptions) {
return;
}
particle.rotate = {
enable: rotateOptions.animation.enable,
value: degToRad(getRangeValue(rotateOptions.value)),
min: 0,
max: doublePI,
};
particle.pathRotation = rotateOptions.path;
let rotateDirection = rotateOptions.direction;
if (rotateDirection === exports.RotateDirection.random) {
const index = Math.floor(getRandom() * double), minIndex = 0;
rotateDirection = index > minIndex ? exports.RotateDirection.counterClockwise : exports.RotateDirection.clockwise;
}
switch (rotateDirection) {
case exports.RotateDirection.counterClockwise:
case "counterClockwise":
particle.rotate.status = exports.AnimationStatus.decreasing;
break;
case exports.RotateDirection.clockwise:
particle.rotate.status = exports.AnimationStatus.increasing;
break;
}
const rotateAnimation = rotateOptions.animation;
if (rotateAnimation.enable) {
particle.rotate.decay = identity$2 - getRangeValue(rotateAnimation.decay);
particle.rotate.velocity =
(getRangeValue(rotateAnimation.speed) / doublePIDeg) * this.#container.retina.reduceFactor;
if (!rotateAnimation.sync) {
particle.rotate.velocity *= getRandom();
}
}
particle.rotation = particle.rotate.value;
}
isEnabled(particle) {
const rotate = particle.options.rotate;
if (!rotate) {
return false;
}
return !particle.destroyed && !particle.spawning && (!!rotate.value || rotate.animation.enable || rotate.path);
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "rotate", Rotate, ...sources);
}
update(particle, delta) {
if (!this.isEnabled(particle)) {
return;
}
particle.isRotating = !!particle.rotate;
if (!particle.rotate) {
return;
}
updateAnimation(particle, particle.rotate, false, exports.DestroyType.none, delta);
particle.rotation = particle.rotate.value;
}
}
async function loadRotateUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("rotate", container => {
return Promise.resolve(new RotateUpdater(container));
});
});
}
function drawSquare(data) {
const { context, radius } = data, fixedRadius = radius * Math.SQRT1_2, fixedDiameter = fixedRadius * double;
context.rect(-fixedRadius, -fixedRadius, fixedDiameter, fixedDiameter);
}
const sides = 4;
class SquareDrawer {
draw(data) {
drawSquare(data);
}
getSidesCount() {
return sides;
}
}
async function loadSquareShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addShape(["edge", "square"], () => Promise.resolve(new SquareDrawer()));
});
}
const defaultInset$1 = 2, origin = { x: 0, y: 0 };
function drawStar(data) {
const { context, particle, radius } = data, sides = particle.sides, inset = particle.starInset ?? defaultInset$1;
context.moveTo(origin.x, origin.y - radius);
for (let i = 0; i < sides; i++) {
context.rotate(Math.PI / sides);
context.lineTo(origin.x, origin.y - radius * inset);
context.rotate(Math.PI / sides);
context.lineTo(origin.x, origin.y - radius);
}
}
const defaultInset = 2, defaultSides = 5;
class StarDrawer {
draw(data) {
drawStar(data);
}
getSidesCount(particle) {
const star = particle.shapeData;
return Math.round(getRangeValue(star?.sides ?? defaultSides));
}
particleInit(_container, particle) {
const star = particle.shapeData;
particle.starInset = getRangeValue(star?.inset ?? defaultInset);
}
}
async function loadStarShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addShape(["star"], () => Promise.resolve(new StarDrawer()));
});
}
async function loadSlim(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(async (e) => {
const loadInteractivityForSlim = async (e) => {
await loadInteractivityPlugin(e);
await Promise.all([
loadExternalParallaxInteraction(e),
loadExternalAttractInteraction(e),
loadExternalBounceInteraction(e),
loadExternalBubbleInteraction(e),
loadExternalConnectInteraction(e),
loadExternalDestroyInteraction(e),
loadExternalGrabInteraction(e),
loadExternalPauseInteraction(e),
loadExternalPushInteraction(e),
loadExternalRemoveInteraction(e),
loadExternalRepulseInteraction(e),
loadExternalSlowInteraction(e),
loadParticlesAttractInteraction(e),
loadParticlesCollisionsInteraction(e),
loadParticlesLinksInteraction(e),
]);
};
await Promise.all([
loadBasic(e),
loadInteractivityForSlim(e),
loadEasingQuadPlugin(e),
loadEmojiShape(e),
loadImageShape(e),
loadLineShape(e),
loadPolygonShape(e),
loadSquareShape(e),
loadStarShape(e),
loadLifeUpdater(e),
loadPaintUpdater(e),
loadRotateUpdater(e),
]);
});
}
const validTypes = ["text", "character", "char", "multiline-text"];
const firstIndex$1 = 0, minLength$1 = 0;
function drawText(data) {
const { context, particle, fill, stroke, radius, opacity } = data, character = particle.shapeData;
if (!character) {
return;
}
const textData = character.value;
particle.textLines ??= itemFromSingleOrMultiple(textData, particle.randomIndexData)?.split("\n") ?? [];
particle.maxTextLength ??= particle.textLines.length
? Math.max(...particle.textLines.map(t => t.length))
: (particle.textLines[firstIndex$1]?.length ?? minLength$1);
if (!particle.textLines.length || !particle.maxTextLength) {
return;
}
const lines = particle.textLines, style = character.style ?? "", weight = character.weight ?? "400", font = character.font ?? "Verdana", size = (Math.round(radius) * double) / (lines.length * particle.maxTextLength);
context.font = `${style} ${weight} ${size.toString()}px "${font}"`;
const originalGlobalAlpha = context.globalAlpha;
context.globalAlpha = opacity;
for (let i = 0; i < lines.length; i++) {
const currentLine = lines[i];
if (!currentLine) {
continue;
}
drawTextLine(context, currentLine, size, i, fill, stroke);
}
context.globalAlpha = originalGlobalAlpha;
}
function drawTextLine(context, line, size, index, fill, stroke) {
const pos = {
x: -(line.length * size * half),
y: size * half + index * size,
};
if (fill) {
context.fillText(line, pos.x, pos.y);
}
if (stroke) {
context.strokeText(line, pos.x, pos.y);
}
}
const firstIndex = 0, minLength = 0;
class TextDrawer {
draw(data) {
drawText(data);
}
async init(container) {
const options = container.actualOptions;
if (validTypes.find(t => isInArray(t, options.particles.shape.type))) {
const shapeOptions = validTypes
.map(t => options.particles.shape.options[t])
.find(t => !!t), promises = [];
executeOnSingleOrMultiple(shapeOptions, shape => {
promises.push(loadFont(shape.font, shape.weight));
});
await Promise.all(promises);
}
}
particleInit(_container, particle) {
if (!particle.shape || !validTypes.includes(particle.shape)) {
return;
}
const character = particle.shapeData;
if (character === undefined) {
return;
}
const textData = character.value;
if (!textData) {
return;
}
particle.textLines = itemFromSingleOrMultiple(textData, particle.randomIndexData)?.split("\n") ?? [];
particle.maxTextLength = particle.textLines.length
? Math.max(...particle.textLines.map(t => t.length))
: (particle.textLines[firstIndex]?.length ?? minLength);
}
}
async function loadTextShape(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addShape(validTypes, () => Promise.resolve(new TextDrawer()));
});
}
var TiltDirection;
(function (TiltDirection) {
TiltDirection["clockwise"] = "clockwise";
TiltDirection["counterClockwise"] = "counter-clockwise";
TiltDirection["random"] = "random";
})(TiltDirection || (TiltDirection = {}));
class TiltAnimation {
decay = 0;
enable = false;
speed = 0;
sync = false;
load(data) {
if (isNull(data)) {
return;
}
loadProperty(this, "enable", data.enable);
loadRangeProperty(this, "speed", data.speed);
loadRangeProperty(this, "decay", data.decay);
loadProperty(this, "sync", data.sync);
}
}
class Tilt extends ValueWithRandom {
animation = new TiltAnimation();
direction = TiltDirection.clockwise;
enable = false;
load(data) {
super.load(data);
if (isNull(data)) {
return;
}
this.animation.load(data.animation);
loadProperty(this, "direction", data.direction);
loadProperty(this, "enable", data.enable);
}
}
const maxAngle$2 = 360;
class TiltUpdater {
#container;
constructor(container) {
this.#container = container;
}
getTransformValues(particle) {
const tilt = particle.tilt?.enable && particle.tilt;
return {
b: tilt ? Math.cos(tilt.value) * tilt.cosDirection : undefined,
c: tilt ? Math.sin(tilt.value) * tilt.sinDirection : undefined,
};
}
init(particle) {
const tiltOptions = particle.options.tilt;
if (!tiltOptions) {
return;
}
particle.tilt = {
enable: tiltOptions.enable,
value: degToRad(getRangeValue(tiltOptions.value)),
sinDirection: getRandom() >= half ? identity$2 : -identity$2,
cosDirection: getRandom() >= half ? identity$2 : -identity$2,
min: 0,
max: doublePI,
};
let tiltDirection = tiltOptions.direction;
if (tiltDirection === TiltDirection.random) {
const index = Math.floor(getRandom() * double), minIndex = 0;
tiltDirection = index > minIndex ? TiltDirection.counterClockwise : TiltDirection.clockwise;
}
switch (tiltDirection) {
case TiltDirection.counterClockwise:
case "counterClockwise":
particle.tilt.status = exports.AnimationStatus.decreasing;
break;
case TiltDirection.clockwise:
particle.tilt.status = exports.AnimationStatus.increasing;
break;
}
const tiltAnimation = particle.options.tilt?.animation;
if (tiltAnimation?.enable) {
particle.tilt.decay = identity$2 - getRangeValue(tiltAnimation.decay);
particle.tilt.velocity = (getRangeValue(tiltAnimation.speed) / maxAngle$2) * this.#container.retina.reduceFactor;
if (!tiltAnimation.sync) {
particle.tilt.velocity *= getRandom();
}
}
}
isEnabled(particle) {
const tiltAnimation = particle.options.tilt?.animation;
return !particle.destroyed && !particle.spawning && !!tiltAnimation?.enable;
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "tilt", Tilt, ...sources);
}
update(particle, delta) {
if (!this.isEnabled(particle) || !particle.tilt) {
return;
}
updateAnimation(particle, particle.tilt, false, exports.DestroyType.none, delta);
}
}
async function loadTiltUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("tilt", container => {
return Promise.resolve(new TiltUpdater(container));
});
});
}
class TwinkleLinksValues {
color;
enable = false;
frequency = 0.05;
opacity = 1;
load(data) {
if (isNull(data)) {
return;
}
if (data.color !== undefined) {
this.color = OptionsColor.create(this.color, data.color);
}
loadProperty(this, "enable", data.enable);
loadProperty(this, "frequency", data.frequency);
loadRangeProperty(this, "opacity", data.opacity);
}
}
class TwinkleParticlesValues {
enable = false;
fillColor;
frequency = 0.05;
opacity = 1;
strokeColor;
load(data) {
if (isNull(data)) {
return;
}
if (data.fillColor !== undefined) {
this.fillColor = OptionsColor.create(this.fillColor, data.fillColor);
}
if (data.strokeColor !== undefined) {
this.strokeColor = OptionsColor.create(this.strokeColor, data.strokeColor);
}
loadProperty(this, "enable", data.enable);
loadProperty(this, "frequency", data.frequency);
loadRangeProperty(this, "opacity", data.opacity);
}
}
class Twinkle {
links = new TwinkleLinksValues();
particles = new TwinkleParticlesValues();
load(data) {
if (isNull(data)) {
return;
}
this.links.load(data.links);
this.particles.load(data.particles);
}
}
class TwinkleUpdater {
#container;
#pluginManager;
constructor(pluginManager, container) {
this.#pluginManager = pluginManager;
this.#container = container;
}
getColorStyles(particle, _context, _radius, opacity) {
const pOptions = particle.options, container = this.#container, twinkleOptions = pOptions["twinkle"];
if (!twinkleOptions) {
return {};
}
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 = () => {
if (!twinkleFillRgb) {
return undefined;
}
return getStyleFromHsl(twinkleFillRgb, container.hdr, twinklingOpacity, container.peakNits, container.hdrMode);
}, getTwinkleStrokeStyle = () => {
if (!twinkleStrokeRgb) {
return undefined;
}
return getStyleFromHsl(twinkleStrokeRgb, container.hdr, twinklingOpacity, container.peakNits, container.hdrMode);
}, twinkleFillStyle = getTwinkleFillStyle(), twinkleStrokeStyle = getTwinkleStrokeStyle(), res = {}, needsTwinkle = twinkling && (!!twinkleFillStyle || !!twinkleStrokeStyle);
res.fill = needsTwinkle ? twinkleFillStyle : undefined;
res.stroke = needsTwinkle ? twinkleStrokeStyle : undefined;
return res;
}
init() {
}
isEnabled(particle) {
const pOptions = particle.options, twinkleOptions = pOptions.twinkle;
if (!twinkleOptions) {
return false;
}
return twinkleOptions.particles.enable;
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "twinkle", Twinkle, ...sources);
}
update() {
}
}
async function loadTwinkleUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("twinkle", container => {
return Promise.resolve(new TwinkleUpdater(e.pluginManager, container));
});
});
}
class WobbleSpeed {
angle = 50;
move = 10;
load(data) {
if (isNull(data)) {
return;
}
loadRangeProperty(this, "angle", data.angle);
loadRangeProperty(this, "move", data.move);
}
}
class Wobble {
distance = 5;
enable = false;
speed = new WobbleSpeed();
load(data) {
if (isNull(data)) {
return;
}
loadRangeProperty(this, "distance", data.distance);
loadProperty(this, "enable", data.enable);
if (data.speed !== undefined) {
if (isNumber(data.speed)) {
this.speed.load({ angle: data.speed });
}
else {
const rangeSpeed = data.speed;
if ("min" in rangeSpeed) {
this.speed.load({ angle: rangeSpeed });
}
else {
this.speed.load(data.speed);
}
}
}
}
}
const defaultDistance$1 = 0, distanceFactor = 60;
function updateWobble(container, particle, delta) {
const { wobble: wobbleOptions } = particle.options, { wobble } = particle;
if (!wobbleOptions?.enable || !wobble) {
return;
}
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 / distanceFactor), max = doublePI, { position } = particle;
wobble.angle += angleSpeed;
if (wobble.angle > max) {
wobble.angle -= max;
}
position.x += distance * Math.cos(wobble.angle);
position.y += distance * Math.abs(Math.sin(wobble.angle));
}
const maxAngle$1 = 360, moveSpeedFactor$1 = 10, defaultDistance = 0;
class WobbleUpdater {
#container;
constructor(container) {
this.#container = container;
}
init(particle) {
const wobbleOpt = particle.options.wobble;
if (wobbleOpt?.enable) {
particle.wobble = {
angle: getRandom() * doublePI,
angleSpeed: getRangeValue(wobbleOpt.speed.angle) / maxAngle$1,
moveSpeed: getRangeValue(wobbleOpt.speed.move) / moveSpeedFactor$1,
};
}
else {
particle.wobble = {
angle: 0,
angleSpeed: 0,
moveSpeed: 0,
};
}
particle.retina.wobbleDistance =
getRangeValue(wobbleOpt?.distance ?? defaultDistance) * this.#container.retina.pixelRatio;
}
isEnabled(particle) {
return !particle.destroyed && !particle.spawning && !!particle.options.wobble?.enable;
}
loadOptions(options, ...sources) {
loadOptionProperty(options, "wobble", Wobble, ...sources);
}
update(particle, delta) {
if (!this.isEnabled(particle)) {
return;
}
updateWobble(this.#container, particle, delta);
}
}
async function loadWobbleUpdater(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(e => {
e.pluginManager.addParticleUpdater("wobble", container => {
return Promise.resolve(new WobbleUpdater(container));
});
});
}
async function loadFull(engine) {
engine.checkVersion("4.4.0");
await engine.pluginManager.register(async (e) => {
const loadEmittersPluginBundle = async (e) => {
await loadEmittersPlugin(e);
await Promise.all([
loadEmittersShapeCircle(e),
loadEmittersShapeSquare(e),
]);
}, loadInteractivityForFull = async (e) => {
await loadSlim(e);
await Promise.all([
loadExternalDragInteraction(e),
loadExternalTrailInteraction(e),
loadAbsorbersPlugin(e),
loadEmittersPluginBundle(e),
]);
};
await Promise.all([
loadInteractivityForFull(e),
loadDestroyUpdater(e),
loadRollUpdater(e),
loadTiltUpdater(e),
loadTwinkleUpdater(e),
loadWobbleUpdater(e),
loadTextShape(e),
]);
});
}
const globalObject = globalThis;
globalObject.loadFull = loadFull;
globalObject.tsParticles = tsParticles;
const fColorIndex = 0, sColorIndex = 1;
function setTransformValue(factor, newFactor, key) {
const newValue = newFactor[key];
if (newValue !== undefined) {
factor[key] = (factor[key] ?? defaultTransformValue) * newValue;
}
}
class RenderManager {
#backgroundElement;
#backgroundWarnings;
#canvasClearPlugins;
#canvasManager;
#colorPlugins;
#container;
#context;
#contextSettings;
#drawParticlePlugins;
#drawParticlesCleanupPlugins;
#drawParticlesSetupPlugins;
#layers;
#pluginManager;
#postDrawUpdaters;
#preDrawUpdaters;
#reusableColorStyles = {};
#reusablePluginColors = [undefined, undefined];
#reusableTransform = {};
constructor(pluginManager, container, canvasManager) {
this.#pluginManager = pluginManager;
this.#container = container;
this.#canvasManager = canvasManager;
this.#context = null;
this.#backgroundElement = null;
this.#backgroundWarnings = new Set();
this.#preDrawUpdaters = [];
this.#postDrawUpdaters = [];
this.#canvasClearPlugins = [];
this.#colorPlugins = [];
this.#drawParticlePlugins = [];
this.#drawParticlesCleanupPlugins = [];
this.#drawParticlesSetupPlugins = [];
this.#layers = {
0: [],
1: [],
2: [],
3: [],
4: [],
5: [],
6: [],
7: [],
};
}
get settings() {
return this.#contextSettings;
}
canvasClear() {
if (!this.#container.actualOptions.clear) {
return;
}
this.draw(ctx => {
ctx.clearRect(originPoint.x, originPoint.y, this.#canvasManager.size.width, this.#canvasManager.size.height);
});
}
clear() {
for (const plugin of this.#canvasClearPlugins) {
if (plugin.canvasClear?.() ?? false) {
return;
}
}
for (const layer of Object.values(exports.DrawLayer)) {
if (typeof layer === "number") {
for (const plugin of this.#getLayerPlugins(layer)) {
if (plugin.canvasClear?.() ?? false) {
return;
}
}
}
}
this.canvasClear();
}
destroy() {
this.stop();
this.#backgroundElement = null;
this.#backgroundWarnings.clear();
this.#preDrawUpdaters = [];
this.#postDrawUpdaters = [];
this.#canvasClearPlugins = [];
this.#colorPlugins = [];
this.#drawParticlePlugins = [];
this.#drawParticlesCleanupPlugins = [];
this.#drawParticlesSetupPlugins = [];
for (const layer of Object.values(exports.DrawLayer)) {
if (typeof layer === "number") {
this.#layers[layer] = [];
}
}
}
draw(cb) {
const ctx = this.#context;
if (!ctx) {
return;
}
return cb(ctx);
}
drawParticle(particle, delta) {
if (particle.spawning || particle.destroyed) {
return;
}
const radius = particle.getRadius();
if (radius <= minimumSize) {
return;
}
const pfColor = particle.getFillColor(), psColor = particle.getStrokeColor();
let [fColor, sColor] = this.#getPluginParticleColors(particle);
fColor ??= pfColor;
sColor ??= psColor;
if (!fColor && !sColor) {
return;
}
const container = this.#container, zIndexOptions = particle.options.zIndex, zIndexFactor = zIndexFactorOffset - particle.zIndexFactor, { fillOpacity, opacity, strokeOpacity } = particle.getOpacity(), transform = this.#reusableTransform, colorStyles = this.#reusableColorStyles, fill = fColor
? getStyleFromHsl(fColor, container.hdr, fillOpacity * opacity, container.peakNits, container.hdrMode)
: undefined, stroke = sColor
? getStyleFromHsl(sColor, container.hdr, strokeOpacity * opacity, container.peakNits, container.hdrMode)
: fill;
transform.a = transform.b = transform.c = transform.d = undefined;
colorStyles.fill = fill;
colorStyles.stroke = stroke;
this.draw((context) => {
for (const plugin of this.#drawParticlesSetupPlugins) {
plugin.drawParticleSetup?.(context, particle, delta);
}
this.#applyPreDrawUpdaters(context, particle, radius, opacity, colorStyles, transform);
this.#drawParticle({
container,
context,
particle,
delta,
colorStyles,
radius: radius * zIndexFactor ** zIndexOptions.sizeRate,
opacity: opacity,
transform,
});
this.#applyPostDrawUpdaters(particle);
for (const plugin of this.#drawParticlesCleanupPlugins) {
plugin.drawParticleCleanup?.(context, particle, delta);
}
});
}
drawParticlePlugins(particle, delta) {
this.draw(ctx => {
for (const plugin of this.#drawParticlePlugins) {
this.#drawParticlePlugin(ctx, plugin, particle, delta);
}
});
}
drawParticles(delta) {
const { particles, actualOptions } = this.#container;
this.clear();
particles.update(delta);
this.draw(ctx => {
const width = this.#canvasManager.size.width, height = this.#canvasManager.size.height;
if (this.#backgroundElement) {
try {
ctx.drawImage(this.#backgroundElement, originPoint.x, originPoint.y, width, height);
}
catch {
this.#warnOnce("background-element-draw-error", "Error drawing background element onto canvas");
}
}
const background = actualOptions.background;
if (background.draw) {
try {
background.draw(ctx, delta);
}
catch {
this.#warnOnce("background-draw-error", "Error in background.draw callback");
}
}
for (const plugin of this.#getLayerPlugins(exports.DrawLayer.BackgroundMask)) {
plugin.canvasPaint?.();
}
for (const plugin of this.#getLayerPlugins(exports.DrawLayer.CanvasSetup)) {
plugin.drawSettingsSetup?.(ctx, delta);
}
for (const plugin of this.#getLayerPlugins(exports.DrawLayer.PluginContent)) {
plugin.draw?.(ctx, delta);
}
particles.drawParticles(delta);
for (const plugin of this.#getLayerPlugins(exports.DrawLayer.CanvasCleanup)) {
plugin.clearDraw?.(ctx, delta);
plugin.drawSettingsCleanup?.(ctx, delta);
}
});
}
init() {
this.initUpdaters();
this.initPlugins();
this.#resolveBackgroundElement();
this.paint();
}
initPlugins() {
this.#canvasClearPlugins = [];
this.#colorPlugins = [];
this.#drawParticlePlugins = [];
this.#drawParticlesSetupPlugins = [];
this.#drawParticlesCleanupPlugins = [];
for (const layer of Object.values(exports.DrawLayer)) {
if (typeof layer === "number") {
this.#layers[layer] = [];
}
}
for (const plugin of this.#container.plugins) {
if (plugin.particleFillColor ?? plugin.particleStrokeColor) {
this.#colorPlugins.push(plugin);
}
if (plugin.drawParticle) {
this.#drawParticlePlugins.push(plugin);
}
if (plugin.drawParticleSetup) {
this.#drawParticlesSetupPlugins.push(plugin);
}
if (plugin.drawParticleCleanup) {
this.#drawParticlesCleanupPlugins.push(plugin);
}
if (plugin.canvasClear) {
this.#canvasClearPlugins.push(plugin);
}
if (plugin.canvasPaint) {
this.#getLayerPlugins(exports.DrawLayer.BackgroundMask).push(plugin);
}
if (plugin.drawSettingsSetup) {
this.#getLayerPlugins(exports.DrawLayer.CanvasSetup).push(plugin);
}
if (plugin.draw) {
this.#getLayerPlugins(exports.DrawLayer.PluginContent).push(plugin);
}
if (plugin.clearDraw ?? plugin.drawSettingsCleanup) {
this.#getLayerPlugins(exports.DrawLayer.CanvasCleanup).push(plugin);
}
}
}
initUpdaters() {
this.#preDrawUpdaters = [];
this.#postDrawUpdaters = [];
for (const updater of this.#container.particleUpdaters) {
if (updater.afterDraw) {
this.#postDrawUpdaters.push(updater);
}
if (updater.getColorStyles ?? updater.getTransformValues ?? updater.beforeDraw) {
this.#preDrawUpdaters.push(updater);
}
}
}
paint() {
let handled = false;
for (const plugin of this.#getLayerPlugins(exports.DrawLayer.BackgroundMask)) {
handled = plugin.canvasPaint?.() ?? false;
if (handled) {
break;
}
}
if (handled) {
return;
}
this.paintBase();
}
paintBase(baseColor) {
this.draw(ctx => {
ctx.fillStyle = baseColor ?? "rgba(0,0,0,0)";
ctx.fillRect(originPoint.x, originPoint.y, this.#canvasManager.size.width, this.#canvasManager.size.height);
});
}
paintImage(image, opacity) {
this.draw(ctx => {
if (!image) {
return;
}
const prevAlpha = ctx.globalAlpha;
ctx.globalAlpha = opacity;
ctx.drawImage(image, originPoint.x, originPoint.y, this.#canvasManager.size.width, this.#canvasManager.size.height);
ctx.globalAlpha = prevAlpha;
});
}
setContext(context) {
this.#context = context;
if (this.#context) {
this.#context.globalCompositeOperation = defaultCompositeValue;
}
}
setContextSettings(settings) {
this.#contextSettings = settings;
}
stop() {
this.draw(ctx => {
ctx.clearRect(originPoint.x, originPoint.y, this.#canvasManager.size.width, this.#canvasManager.size.height);
});
}
#applyPostDrawUpdaters(particle) {
for (const updater of this.#postDrawUpdaters) {
updater.afterDraw?.(particle);
}
}
#applyPreDrawUpdaters(ctx, particle, radius, zOpacity, colorStyles, transform) {
for (const updater of this.#preDrawUpdaters) {
if (updater.getColorStyles) {
const { fill, stroke } = updater.getColorStyles(particle, ctx, radius, zOpacity);
if (fill) {
colorStyles.fill = fill;
}
if (stroke) {
colorStyles.stroke = stroke;
}
}
if (updater.getTransformValues) {
const updaterTransform = updater.getTransformValues(particle);
for (const key in updaterTransform) {
setTransformValue(transform, updaterTransform, key);
}
}
updater.beforeDraw?.(particle);
}
}
#drawAfterEffect(drawer, data) {
if (!drawer?.drawAfter) {
return;
}
const { particle } = data;
if (!particle.effect) {
return;
}
drawer.drawAfter(data);
}
#drawBeforeEffect(drawer, data) {
if (!drawer?.drawBefore) {
return;
}
const { particle } = data;
if (!particle.effect) {
return;
}
drawer.drawBefore(data);
}
#drawParticle(data) {
const { container, context, particle, delta, colorStyles, radius, opacity, transform } = data, { effectDrawers, shapeDrawers } = container, pos = particle.getPosition(), transformData = particle.getTransformData(transform), drawScale = defaultZoom, drawPosition = {
x: pos.x,
y: pos.y,
};
context.setTransform(transformData.a, transformData.b, transformData.c, transformData.d, pos.x, pos.y);
if (colorStyles.fill) {
context.fillStyle = colorStyles.fill;
}
const fillEnabled = !!particle.fillEnabled, strokeWidth = particle.strokeWidth ?? minStrokeWidth;
context.lineWidth = strokeWidth;
if (colorStyles.stroke) {
context.strokeStyle = colorStyles.stroke;
}
const drawData = {
context,
particle,
radius,
drawRadius: radius * drawScale,
opacity,
delta,
pixelRatio: container.retina.pixelRatio,
fill: fillEnabled,
stroke: strokeWidth > minStrokeWidth,
transformData,
position: { ...pos },
drawPosition,
drawScale,
};
for (const plugin of container.plugins) {
plugin.drawParticleTransform?.(drawData);
}
const effect = particle.effect ? effectDrawers.get(particle.effect) : undefined, shape = particle.shape ? shapeDrawers.get(particle.shape) : undefined;
this.#drawBeforeEffect(effect, drawData);
this.#drawShapeBeforeDraw(shape, drawData);
this.#drawShape(shape, drawData);
this.#drawShapeAfterDraw(shape, drawData);
this.#drawAfterEffect(effect, drawData);
context.resetTransform();
}
#drawParticlePlugin(context, plugin, particle, delta) {
if (!plugin.drawParticle) {
return;
}
plugin.drawParticle(context, particle, delta);
}
#drawShape(drawer, data) {
if (!drawer) {
return;
}
const { context, fill, particle, stroke } = data;
if (!particle.shape) {
return;
}
context.beginPath();
drawer.draw(data);
if (particle.shapeClose) {
context.closePath();
}
if (fill) {
context.fill();
}
if (stroke) {
context.stroke();
}
}
#drawShapeAfterDraw(drawer, data) {
if (!drawer?.afterDraw) {
return;
}
const { particle } = data;
if (!particle.shape) {
return;
}
drawer.afterDraw(data);
}
#drawShapeBeforeDraw(drawer, data) {
if (!drawer?.beforeDraw) {
return;
}
const { particle } = data;
if (!particle.shape) {
return;
}
drawer.beforeDraw(data);
}
#getLayerPlugins(layer) {
return this.#layers[layer];
}
#getPluginParticleColors(particle) {
let fColor, sColor;
for (const plugin of this.#colorPlugins) {
if (!fColor && plugin.particleFillColor) {
fColor = rangeColorToHsl(this.#pluginManager, plugin.particleFillColor(particle));
}
if (!sColor && plugin.particleStrokeColor) {
sColor = rangeColorToHsl(this.#pluginManager, plugin.particleStrokeColor(particle));
}
if (fColor && sColor) {
break;
}
}
this.#reusablePluginColors[fColorIndex] = fColor;
this.#reusablePluginColors[sColorIndex] = sColor;
return this.#reusablePluginColors;
}
#resolveBackgroundElement() {
const background = this.#container.actualOptions.background;
this.#backgroundElement = null;
if (!background.element) {
return;
}
if (typeof background.element === "string") {
if (typeof document !== "undefined") {
const node = document.querySelector(background.element);
if ((typeof HTMLCanvasElement !== "undefined" && node instanceof HTMLCanvasElement) ||
(typeof HTMLVideoElement !== "undefined" && node instanceof HTMLVideoElement) ||
(typeof HTMLImageElement !== "undefined" && node instanceof HTMLImageElement)) {
this.#backgroundElement = node;
}
else if (node) {
this.#warnOnce("background-element-not-supported", `Background element "${background.element}" is not a supported drawable element (canvas, video, or img)`);
}
else {
this.#warnOnce("background-element-not-found", `Background element selector "${background.element}" not found`);
}
}
}
else if ((typeof HTMLCanvasElement !== "undefined" && background.element instanceof HTMLCanvasElement) ||
(typeof OffscreenCanvas !== "undefined" && background.element instanceof OffscreenCanvas) ||
(typeof HTMLVideoElement !== "undefined" && background.element instanceof HTMLVideoElement) ||
(typeof HTMLImageElement !== "undefined" && background.element instanceof HTMLImageElement)) {
this.#backgroundElement = background.element;
}
}
#warnOnce(key, message) {
if (!this.#backgroundWarnings.has(key)) {
this.#backgroundWarnings.add(key);
getLogger().warning(message);
}
}
}
const transferredCanvases = new WeakMap(), getTransferredCanvas = (canvas) => {
const transferredCanvas = transferredCanvases.get(canvas);
if (transferredCanvas) {
return transferredCanvas;
}
if (typeof canvas.transferControlToOffscreen !== "function") {
throw new TypeError("OffscreenCanvas is required but not supported by this browser");
}
try {
const offscreenCanvas = canvas.transferControlToOffscreen();
transferredCanvases.set(canvas, offscreenCanvas);
return offscreenCanvas;
}
catch {
throw new TypeError("OffscreenCanvas transfer failed");
}
}, isHtmlCanvasElement = (canvas) => {
return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
};
function setStyle(canvas, style, important = false) {
if (!style) {
return;
}
const element = canvas, elementStyle = element.style, keys = new Set();
for (let i = 0; i < elementStyle.length; i++) {
const key = elementStyle.item(i);
if (!key) {
continue;
}
keys.add(key);
}
for (let i = 0; i < style.length; i++) {
const key = style.item(i);
if (!key) {
continue;
}
keys.add(key);
}
for (const key of keys) {
const value = style.getPropertyValue(key);
if (value) {
elementStyle.setProperty(key, value, important ? "important" : "");
}
else {
elementStyle.removeProperty(key);
}
}
}
class CanvasManager {
domElement;
render;
renderCanvas;
size;
zoom = defaultZoom;
#container;
#generated;
#hdrMediaListeners;
#mutationObserver;
#originalStyle;
#pluginManager;
#pointerEvents;
#resizePlugins;
#standardSize;
#zoomCenter;
constructor(pluginManager, container) {
this.#pluginManager = pluginManager;
this.#container = container;
this.render = new RenderManager(pluginManager, container, this);
this.#standardSize = {
height: 0,
width: 0,
};
const pxRatio = container.retina.pixelRatio, stdSize = this.#standardSize;
this.size = {
height: stdSize.height * pxRatio,
width: stdSize.width * pxRatio,
};
this.#generated = false;
this.#resizePlugins = [];
this.#pointerEvents = "none";
}
get #fullScreen() {
return this.#container.actualOptions.fullScreen.enable;
}
destroy() {
this.stop();
if (this.#generated) {
const element = this.domElement;
element?.remove();
this.domElement = undefined;
this.renderCanvas = undefined;
}
else {
this.#resetOriginalStyle();
}
this.render.destroy();
this.#resizePlugins = [];
}
getZoomCenter() {
const pxRatio = this.#container.retina.pixelRatio, { width, height } = this.size;
if (this.#zoomCenter) {
return this.#zoomCenter;
}
return {
x: (width * half) / pxRatio,
y: (height * half) / pxRatio,
};
}
init() {
this.#safeMutationObserver(obs => {
obs.disconnect();
});
this.#mutationObserver = safeMutationObserver(records => {
for (const record of records) {
if (record.type === "attributes" && record.attributeName === "style") {
this.#repairStyle();
}
}
});
this.resize();
this.#initStyle();
this.#safeMutationObserver(obs => {
const element = this.domElement;
if (!element || !(element instanceof Node)) {
return;
}
obs.observe(element, { attributes: true });
});
this.initPlugins();
this.#initContext();
this.initBackground();
this.#initHdrListeners();
this.render.init();
}
initBackground() {
const container = this.#container, options = container.actualOptions, background = options.background, element = this.domElement;
if (!element) {
return;
}
const elementStyle = element.style, color = rangeColorToRgb(this.#pluginManager, background.color);
if (color) {
const hdrOptions = container.actualOptions.hdr;
elementStyle.backgroundColor = getStyleFromRgb(color, container.hdr, background.opacity, hdrOptions.peakNits, hdrOptions.mode);
}
else {
elementStyle.backgroundColor = "";
}
elementStyle.backgroundImage = background.image || "";
elementStyle.backgroundPosition = background.position || "";
elementStyle.backgroundRepeat = background.repeat || "";
elementStyle.backgroundSize = background.size || "";
}
initPlugins() {
this.#resizePlugins = [];
for (const plugin of this.#container.plugins) {
if (plugin.resize) {
this.#resizePlugins.push(plugin);
}
}
}
loadCanvas(canvas) {
if (this.#generated && this.domElement) {
this.domElement.remove();
}
const domCanvas = isHtmlCanvasElement(canvas) ? canvas : undefined;
this.domElement = domCanvas;
this.#generated = domCanvas ? domCanvas.dataset[generatedAttribute] === "true" : false;
this.renderCanvas = domCanvas ? getTransferredCanvas(domCanvas) : canvas;
const domElement = this.domElement;
if (domElement) {
domElement.ariaHidden = "true";
this.#originalStyle = cloneStyle(domElement.style);
}
const standardSize = this.#standardSize, renderCanvas = this.renderCanvas;
if (domElement) {
standardSize.height = domElement.offsetHeight;
standardSize.width = domElement.offsetWidth;
}
else {
standardSize.height = renderCanvas.height;
standardSize.width = renderCanvas.width;
}
const pxRatio = this.#container.retina.pixelRatio, retinaSize = this.size;
renderCanvas.height = retinaSize.height = standardSize.height * pxRatio;
renderCanvas.width = retinaSize.width = standardSize.width * pxRatio;
}
resize() {
const element = this.domElement;
if (!element) {
return false;
}
const container = this.#container, renderCanvas = this.renderCanvas;
if (renderCanvas === undefined) {
return false;
}
const currentSize = container.canvas.#standardSize, newSize = {
width: element.offsetWidth,
height: element.offsetHeight,
}, pxRatio = container.retina.pixelRatio, retinaSize = {
width: newSize.width * pxRatio,
height: newSize.height * pxRatio,
};
if (newSize.height === currentSize.height &&
newSize.width === currentSize.width &&
retinaSize.height === renderCanvas.height &&
retinaSize.width === renderCanvas.width) {
return false;
}
const oldSize = { ...currentSize };
currentSize.height = newSize.height;
currentSize.width = newSize.width;
const canvasSize = this.size;
renderCanvas.width = canvasSize.width = retinaSize.width;
renderCanvas.height = canvasSize.height = retinaSize.height;
if (this.#container.started) {
container.particles.setResizeFactor({
width: currentSize.width / oldSize.width,
height: currentSize.height / oldSize.height,
});
}
return true;
}
setPointerEvents(type) {
const element = this.domElement;
if (!element) {
return;
}
this.#pointerEvents = type;
this.#repairStyle();
}
setZoom(zoomLevel, center) {
this.zoom = zoomLevel;
this.#zoomCenter = center;
}
stop() {
this.#safeMutationObserver(obs => {
obs.disconnect();
});
this.#mutationObserver = undefined;
this.#removeHdrListeners();
this.render.stop();
}
async windowResize() {
if (!this.domElement || !this.resize()) {
return;
}
const container = this.#container, needsRefresh = container.updateActualOptions();
container.particles.setDensity();
this.#applyResizePlugins();
if (needsRefresh) {
await container.refresh();
}
}
#applyResizePlugins() {
for (const plugin of this.#resizePlugins) {
plugin.resize?.();
}
}
#initContext() {
const container = this.#container, canSupportHdr = container.actualOptions.hdr.enable &&
safeMatchMedia("(color-gamut: p3)")?.matches &&
safeMatchMedia("(dynamic-range: high)")?.matches;
container.hdr = canSupportHdr ?? false;
container.hdrMode = container.actualOptions.hdr.mode;
container.peakNits = container.actualOptions.hdr.peakNits;
const renderCanvas = this.renderCanvas;
if (!renderCanvas) {
return;
}
this.render.setContextSettings({
alpha: true,
desynchronized: true,
willReadFrequently: false,
...(canSupportHdr
? { colorSpace: "display-p3", colorType: "float16" }
: { colorSpace: "srgb" }),
});
let context;
try {
context = renderCanvas.getContext("2d", this.render.settings);
}
catch {
context = null;
}
if (canSupportHdr && !context) {
container.hdr = false;
const sdrSettings = {
alpha: true,
desynchronized: true,
willReadFrequently: false,
colorSpace: "srgb",
};
this.render.setContextSettings(sdrSettings);
try {
context = renderCanvas.getContext("2d", sdrSettings);
}
catch {
context = null;
}
}
this.render.setContext(context);
}
#initHdrListeners() {
this.#removeHdrListeners();
const p3Query = safeMatchMedia("(color-gamut: p3)"), hdrQuery = safeMatchMedia("(dynamic-range: high)"), handleChange = () => {
this.#recreateRenderCanvas();
this.#initContext();
this.initBackground();
}, listeners = [];
if (p3Query) {
p3Query.addEventListener("change", handleChange);
listeners.push({ handler: handleChange, mql: p3Query });
}
if (hdrQuery) {
hdrQuery.addEventListener("change", handleChange);
listeners.push({ handler: handleChange, mql: hdrQuery });
}
this.#hdrMediaListeners = listeners;
}
#initStyle() {
const element = this.domElement, options = this.#container.actualOptions;
if (!element) {
return;
}
if (this.#fullScreen) {
this.#setFullScreenStyle();
}
else {
this.#resetOriginalStyle();
}
for (const key in options.style) {
if (!key || !(key in options.style)) {
continue;
}
const value = options.style[key];
if (!value) {
continue;
}
element.style.setProperty(key, value, "important");
}
}
#recreateRenderCanvas() {
const renderCanvas = this.renderCanvas;
if (!renderCanvas) {
return;
}
if (this.domElement) {
return;
}
this.renderCanvas = new OffscreenCanvas(renderCanvas.width, renderCanvas.height);
}
#removeHdrListeners() {
if (!this.#hdrMediaListeners) {
return;
}
for (const { handler, mql } of this.#hdrMediaListeners) {
mql.removeEventListener("change", handler);
}
this.#hdrMediaListeners = undefined;
}
#repairStyle() {
const element = this.domElement;
if (!element) {
return;
}
this.#safeMutationObserver(observer => {
observer.disconnect();
});
this.#initStyle();
this.initBackground();
const pointerEvents = this.#pointerEvents;
element.style.pointerEvents = pointerEvents;
element.style.setProperty("pointer-events", pointerEvents);
this.#safeMutationObserver(observer => {
if (!(element instanceof Node)) {
return;
}
observer.observe(element, { attributes: true });
});
}
#resetOriginalStyle() {
const element = this.domElement, originalStyle = this.#originalStyle;
if (!element || !originalStyle) {
return;
}
setStyle(element, originalStyle, true);
}
#safeMutationObserver(callback) {
if (!this.#mutationObserver) {
return;
}
callback(this.#mutationObserver);
}
#setFullScreenStyle() {
const element = this.domElement;
if (!element) {
return;
}
setStyle(element, getFullScreenStyle(this.#container.actualOptions.fullScreen.zIndex), true);
}
}
class EventListeners {
#container;
#handlers;
#resizeObserver;
#resizeTimeout;
constructor(container) {
this.#container = container;
this.#handlers = {
visibilityChange: () => {
this.#handleVisibilityChange();
},
resize: () => {
this.#handleWindowResize();
},
};
}
addListeners() {
this.#manageListeners(true);
}
removeListeners() {
this.#manageListeners(false);
}
#handleVisibilityChange() {
const container = this.#container, options = container.actualOptions;
if (!options.pauseOnBlur) {
return;
}
if (safeDocument().hidden) {
container.pageHidden = true;
container.pause();
}
else {
container.pageHidden = false;
if (container.animationStatus) {
container.play(true);
}
else {
container.draw(true);
}
}
}
#handleWindowResize() {
if (this.#resizeTimeout) {
clearTimeout(this.#resizeTimeout);
this.#resizeTimeout = undefined;
}
const handleResize = async () => {
const canvas = this.#container.canvas;
await canvas.windowResize();
};
this.#resizeTimeout = setTimeout(() => void handleResize(), this.#container.actualOptions.resize.delay * millisecondsToSeconds);
}
#manageListeners(add) {
const handlers = this.#handlers;
this.#manageResize(add);
manageListener(document, visibilityChangeEvent, handlers.visibilityChange, add, false);
}
#manageResize(add) {
const handlers = this.#handlers, container = this.#container, options = container.actualOptions;
if (!options.resize.enable) {
return;
}
if (typeof ResizeObserver === "undefined") {
manageListener(globalThis, resizeEvent, handlers.resize, add);
return;
}
const canvasEl = container.canvas.domElement;
if (this.#resizeObserver && !add) {
if (canvasEl) {
this.#resizeObserver.unobserve(canvasEl);
}
this.#resizeObserver.disconnect();
this.#resizeObserver = undefined;
}
else if (!this.#resizeObserver && add && canvasEl) {
this.#resizeObserver = new ResizeObserver((entries) => {
const entry = entries.find(e => e.target === canvasEl);
if (!entry) {
return;
}
this.#handleWindowResize();
});
this.#resizeObserver.observe(canvasEl);
}
}
}
function loadEffectData(effect, effectOptions, id, reduceDuplicates) {
const effectData = effectOptions.options[effect];
return deepExtend({
close: effectOptions.close,
}, itemFromSingleOrMultiple(effectData, id, reduceDuplicates));
}
function loadShapeData(shape, shapeOptions, id, reduceDuplicates) {
const shapeData = shapeOptions.options[shape];
return deepExtend({
close: shapeOptions.close,
}, itemFromSingleOrMultiple(shapeData, id, reduceDuplicates));
}
function fixOutMode(data) {
if (!isInArray(data.outMode, data.checkModes)) {
return;
}
const diameter = data.radius * double;
if (data.coord > data.maxCoord - diameter) {
data.setCb(-data.radius);
}
else if (data.coord < diameter) {
data.setCb(data.radius);
}
}
function normalizeAngle(angle, modulus) {
const normalized = angle % modulus;
return normalized < defaultAngle ? normalized + modulus : normalized;
}
function initParticleState(particle, id, group) {
particle.id = id;
particle.group = group;
particle.justWarped = false;
particle.effectClose = true;
particle.shapeClose = true;
particle.pathRotation = false;
particle.lastPathTime = 0;
particle.destroyed = false;
particle.unbreakable = false;
particle.isRotating = false;
particle.rotation = 0;
particle.misplaced = false;
particle.retina = {
maxDistance: {},
maxSpeed: 0,
moveDrift: 0,
moveSpeed: 0,
sizeAnimationSpeed: 0,
};
particle.size = {
value: 1,
max: 1,
min: 1,
enable: false,
};
particle.outType = exports.ParticleOutType.normal;
particle.ignoresResizeRatio = true;
}
function resolveParticleOptions(particle, container, pluginManager, overrideOptions) {
const mainOptions = container.actualOptions, particlesOptions = loadParticlesOptions(pluginManager, container, mainOptions.particles), reduceDuplicates = particlesOptions.reduceDuplicates;
particle.effect = itemFromSingleOrMultiple(particlesOptions.effect.type, particle.id, reduceDuplicates);
particle.shape = itemFromSingleOrMultiple(particlesOptions.shape.type, particle.id, reduceDuplicates);
const effectOptions = particlesOptions.effect, shapeOptions = particlesOptions.shape;
if (overrideOptions) {
if (overrideOptions.effect) {
const overrideEffectType = overrideOptions.effect.type;
if (overrideEffectType && overrideEffectType !== particle.effect) {
const effect = itemFromSingleOrMultiple(overrideEffectType, particle.id, reduceDuplicates);
if (effect) {
particle.effect = effect;
}
}
effectOptions.load(overrideOptions.effect);
}
if (overrideOptions.shape) {
const overrideShapeType = overrideOptions.shape.type;
if (overrideShapeType && overrideShapeType !== particle.shape) {
const shape = itemFromSingleOrMultiple(overrideShapeType, particle.id, reduceDuplicates);
if (shape) {
particle.shape = shape;
}
}
shapeOptions.load(overrideOptions.shape);
}
}
if (particle.effect === randomColorValue) {
const availableEffects = [...container.effectDrawers.keys()];
particle.effect = availableEffects[Math.floor(getRandom() * availableEffects.length)];
}
if (particle.shape === randomColorValue) {
const availableShapes = [...container.shapeDrawers.keys()];
particle.shape = availableShapes[Math.floor(getRandom() * availableShapes.length)];
}
particle.effectData = particle.effect
? loadEffectData(particle.effect, effectOptions, particle.id, reduceDuplicates)
: undefined;
particle.shapeData = particle.shape
? loadShapeData(particle.shape, shapeOptions, particle.id, reduceDuplicates)
: undefined;
particlesOptions.load(overrideOptions);
const effectData = particle.effectData, shapeData = particle.shapeData;
if (effectData) {
particlesOptions.load(effectData.particles);
}
if (shapeData) {
particlesOptions.load(shapeData.particles);
}
particle.effectClose = effectData?.close ?? particlesOptions.effect.close;
particle.shapeClose = shapeData?.close ?? particlesOptions.shape.close;
return particlesOptions;
}
function initParticleDrawers(particle, container) {
let effectDrawer, shapeDrawer;
if (particle.effect) {
effectDrawer = container.effectDrawers.get(particle.effect);
}
if (effectDrawer?.loadEffect) {
effectDrawer.loadEffect(particle);
}
if (particle.shape) {
shapeDrawer = container.shapeDrawers.get(particle.shape);
}
if (shapeDrawer?.loadShape) {
shapeDrawer.loadShape(particle);
}
const sideCountFunc = shapeDrawer?.getSidesCount;
if (sideCountFunc) {
particle.sides = sideCountFunc(particle);
}
}
function runUpdaterPreInit(updaters, particle) {
for (const updater of updaters) {
updater.preInit?.(particle);
}
}
function runUpdaterInit(updaters, particle) {
for (const updater of updaters) {
updater.init(particle);
}
}
function runDrawerInit(container, particle) {
const shapeDrawer = particle.shape ? container.shapeDrawers.get(particle.shape) : undefined, effectDrawer = particle.effect ? container.effectDrawers.get(particle.effect) : undefined;
effectDrawer?.particleInit?.(container, particle);
shapeDrawer?.particleInit?.(container, particle);
}
function runParticleCreatedPlugins(container, particle) {
for (const plugin of container.particleCreatedPlugins) {
plugin.particleCreated?.(particle);
}
}
class Particle {
backColor;
destroyed;
direction;
effect;
effectClose;
effectData;
fillColor;
fillEnabled;
fillOpacity;
group;
id;
ignoresResizeRatio;
initialPosition;
initialVelocity;
isRotating;
justWarped;
lastPathTime;
misplaced;
moveCenter;
offset;
opacity;
options;
outType;
pathRotation;
position;
randomIndexData;
retina;
roll;
rotation;
shape;
shapeClose;
shapeData;
sides;
size;
spawning;
strokeColor;
strokeOpacity;
strokeWidth;
unbreakable;
velocity;
zIndexFactor;
#cachedOpacityData = {
fillOpacity: defaultOpacity$3,
opacity: defaultOpacity$3,
strokeOpacity: defaultOpacity$3,
};
#cachedPosition = Vector3d.origin;
#cachedRotateData = { sin: 0, cos: 0 };
#cachedTransform = {
a: 1,
b: 0,
c: 0,
d: 1,
};
#container;
#modifiers = [];
#pluginManager;
constructor(pluginManager, container) {
this.#pluginManager = pluginManager;
this.#container = container;
}
addModifier(modifier) {
this.#modifiers.push(modifier);
this.#modifiers.sort((a, b) => a.priority - b.priority);
}
clearModifiers() {
this.#modifiers.length = 0;
}
destroy(override) {
if (this.unbreakable || this.destroyed) {
return;
}
this.destroyed = true;
this.clearModifiers();
const container = this.#container, shapeDrawer = this.shape ? container.shapeDrawers.get(this.shape) : undefined;
shapeDrawer?.particleDestroy?.(this);
for (const plugin of container.particleDestroyedPlugins) {
plugin.particleDestroyed?.(this, override);
}
for (const updater of container.particleUpdaters) {
updater.particleDestroyed?.(this, override);
}
this.#container.dispatchEvent(exports.EventType.particleDestroyed, {
particle: this,
});
}
draw(delta) {
const container = this.#container, render = container.canvas.render;
render.drawParticlePlugins(this, delta);
render.drawParticle(this, delta);
}
getAngle() {
return this.rotation + (this.pathRotation ? this.velocity.angle : defaultAngle);
}
getFillColor() {
return this.#getRollColor(this.#applyModifiers(getHslFromAnimation(this.fillColor), m => m.fillColor));
}
getMass() {
return this.getRadius() ** squareExp * Math.PI * half;
}
getModifier(id) {
return this.#modifiers.find(m => m.id === id);
}
getOpacity() {
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;
this.#cachedOpacityData.fillOpacity = fillOpacity;
this.#cachedOpacityData.opacity = opacity * zOpacityFactor;
this.#cachedOpacityData.strokeOpacity = strokeOpacity;
return this.#cachedOpacityData;
}
getPosition() {
this.#cachedPosition.x = this.position.x + this.offset.x;
this.#cachedPosition.y = this.position.y + this.offset.y;
this.#cachedPosition.z = this.position.z;
return this.#cachedPosition;
}
getRadius() {
return this.#applyModifiers(this.size.value, m => m.radius);
}
getRotateData() {
const angle = this.getAngle();
this.#cachedRotateData.sin = Math.sin(angle);
this.#cachedRotateData.cos = Math.cos(angle);
return this.#cachedRotateData;
}
getStrokeColor() {
return this.#getRollColor(this.#applyModifiers(getHslFromAnimation(this.strokeColor), m => m.strokeColor));
}
getTransformData(externalTransform) {
const rotateData = this.getRotateData(), rotating = this.isRotating;
this.#cachedTransform.a = rotateData.cos * (externalTransform.a ?? defaultTransform.a);
this.#cachedTransform.b = rotating
? rotateData.sin * (externalTransform.b ?? identity$2)
: (externalTransform.b ?? defaultTransform.b);
this.#cachedTransform.c = rotating
? -rotateData.sin * (externalTransform.c ?? identity$2)
: (externalTransform.c ?? defaultTransform.c);
this.#cachedTransform.d = rotateData.cos * (externalTransform.d ?? defaultTransform.d);
return this.#cachedTransform;
}
init(id, position, overrideOptions, group) {
const container = this.#container;
initParticleState(this, id, group);
this.options = resolveParticleOptions(this, container, this.#pluginManager, overrideOptions);
container.retina.initParticle(this);
runUpdaterPreInit(container.particleUpdaters, this);
this.#initPosition(position);
this.initialVelocity = this.#calculateVelocity();
this.velocity = this.initialVelocity.copy();
this.zIndexFactor = this.position.z / container.zLayers;
this.sides = 24;
initParticleDrawers(this, container);
this.spawning = false;
runUpdaterInit(container.particleUpdaters, this);
runDrawerInit(container, this);
runParticleCreatedPlugins(container, this);
}
isInsideCanvas(direction) {
return this.#getInsideCanvasResult({ direction }).inside;
}
isInsideCanvasForOutMode(outMode, direction) {
return this.#getInsideCanvasResult({ direction, outMode }).inside;
}
isShowingBack() {
if (!this.roll) {
return false;
}
const angle = this.roll.angle;
if (this.roll.horizontal && this.roll.vertical) {
const adjustedAngle = normalizeAngle(angle, doublePI);
return adjustedAngle >= Math.PI * half && adjustedAngle < Math.PI * triple * half;
}
if (this.roll.horizontal) {
const adjustedAngle = normalizeAngle(angle + Math.PI * half, doublePI);
return adjustedAngle >= Math.PI && adjustedAngle < Math.PI * double;
}
if (this.roll.vertical) {
const adjustedAngle = normalizeAngle(angle, doublePI);
return adjustedAngle >= Math.PI && adjustedAngle < Math.PI * double;
}
return false;
}
isVisible() {
return !this.destroyed && !this.spawning && this.isInsideCanvas();
}
removeModifier(id) {
const idx = this.#modifiers.findIndex(m => m.id === id);
if (idx >= defaultAngle) {
this.#modifiers.splice(idx, identity$2);
}
}
reset() {
for (const updater of this.#container.particleUpdaters) {
updater.reset?.(this);
}
}
#applyModifiers(base, getter) {
let value = base;
for (const mod of this.#modifiers) {
if (mod.enabled) {
const override = getter(mod);
if (override !== undefined) {
value = override;
}
}
}
return value;
}
#calcPosition(position, zIndex) {
let tryCount = defaultRetryCount, posVec = position ? Vector3d.create(position.x, position.y, zIndex) : undefined;
const container = this.#container, plugins = container.particlePositionPlugins, outModes = this.options.move.outModes, radius = this.getRadius(), canvasSize = container.canvas.size;
for (;;) {
for (const plugin of plugins) {
const pluginPos = plugin.particlePosition?.(posVec, this);
if (pluginPos) {
return Vector3d.create(pluginPos.x, pluginPos.y, zIndex);
}
}
const exactPosition = calcExactPositionOrRandomFromSize({
size: canvasSize,
position: posVec,
}), pos = Vector3d.create(exactPosition.x, exactPosition.y, zIndex);
this.#fixHorizontal(pos, radius, outModes.left ?? outModes.default);
this.#fixHorizontal(pos, radius, outModes.right ?? outModes.default);
this.#fixVertical(pos, radius, outModes.top ?? outModes.default);
this.#fixVertical(pos, radius, outModes.bottom ?? outModes.default);
let isValidPosition = true;
for (const plugin of container.particles.checkParticlePositionPlugins) {
isValidPosition = plugin.checkParticlePosition?.(this, pos, tryCount) ?? true;
if (!isValidPosition) {
break;
}
}
if (isValidPosition) {
return pos;
}
tryCount += tryCountIncrement;
posVec = undefined;
}
}
#calculateVelocity() {
const moveOptions = this.options.move, baseVelocity = getParticleBaseVelocity(this.direction), res = baseVelocity.copy();
if (moveOptions.direction === exports.MoveDirection.inside || moveOptions.direction === exports.MoveDirection.outside) {
return res;
}
const rad = degToRad(getRangeValue(moveOptions.angle.value)), radOffset = degToRad(getRangeValue(moveOptions.angle.offset)), range = {
left: radOffset - rad * half,
right: radOffset + rad * half,
};
if (!moveOptions.straight) {
res.angle += randomInRangeValue(setRangeValue(range.left, range.right));
}
if (moveOptions.random && typeof moveOptions.speed === "number") {
res.length *= getRandom();
}
return res;
}
#fixHorizontal(pos, radius, outMode) {
fixOutMode({
outMode,
checkModes: [exports.OutMode.bounce],
coord: pos.x,
maxCoord: this.#container.canvas.size.width,
setCb: (value) => (pos.x += value),
radius,
});
}
#fixVertical(pos, radius, outMode) {
fixOutMode({
outMode,
checkModes: [exports.OutMode.bounce],
coord: pos.y,
maxCoord: this.#container.canvas.size.height,
setCb: (value) => (pos.y += value),
radius,
});
}
#getDefaultInsideCanvasResult(direction, outMode) {
const radius = this.getRadius(), canvasSize = this.#container.canvas.size, position = this.position, isBounce = outMode === exports.OutMode.bounce;
if (direction === exports.OutModeDirection.bottom) {
return {
inside: isBounce ? position.y + radius < canvasSize.height : position.y - radius < canvasSize.height,
reason: "default",
};
}
if (direction === exports.OutModeDirection.left) {
return {
inside: isBounce ? position.x - radius > defaultAngle : position.x + radius > defaultAngle,
reason: "default",
};
}
if (direction === exports.OutModeDirection.right) {
return {
inside: isBounce ? position.x + radius < canvasSize.width : position.x - radius < canvasSize.width,
reason: "default",
};
}
if (direction === exports.OutModeDirection.top) {
return {
inside: isBounce ? position.y - radius > defaultAngle : position.y + radius > defaultAngle,
reason: "default",
};
}
return {
inside: position.x >= -radius &&
position.y >= -radius &&
position.y <= canvasSize.height + radius &&
position.x <= canvasSize.width + radius,
reason: "default",
};
}
#getInsideCanvasCallbackData(direction, outMode) {
return {
canvasSize: this.#container.canvas.size,
direction,
outMode,
particle: this,
radius: this.getRadius(),
};
}
#getInsideCanvasResult(data) {
const defaultResult = this.#getDefaultInsideCanvasResult(data.direction, data.outMode), container = this.#container, shapeDrawer = this.shape ? container.shapeDrawers.get(this.shape) : undefined, effectDrawer = this.effect ? container.effectDrawers.get(this.effect) : undefined, shapeCheck = shapeDrawer?.isInsideCanvas, effectCheck = effectDrawer?.isInsideCanvas;
if (!shapeCheck && !effectCheck) {
return defaultResult;
}
const callbackData = this.#getInsideCanvasCallbackData(data.direction, data.outMode), shapeResult = shapeCheck ? this.#normalizeInsideCanvasResult(shapeCheck(callbackData), "shape") : undefined, effectResult = effectCheck ? this.#normalizeInsideCanvasResult(effectCheck(callbackData), "effect") : undefined;
if (shapeResult && effectResult) {
const margin = Math.max(shapeResult.margin ?? defaultAngle, effectResult.margin ?? defaultAngle);
return {
inside: shapeResult.inside && effectResult.inside,
margin: margin > defaultAngle ? margin : undefined,
reason: "combined",
};
}
return shapeResult ?? effectResult ?? defaultResult;
}
#getRollColor(color) {
if (!color || !this.roll || (!this.backColor && !this.roll.alter)) {
return color;
}
if (!this.isShowingBack()) {
return color;
}
if (this.backColor) {
return this.backColor;
}
if (this.roll.alter) {
return alterHsl(color, this.roll.alter.type, this.roll.alter.value);
}
return color;
}
#initPosition(position) {
const container = this.#container, zIndexValue = Math.floor(getRangeValue(this.options.zIndex.value)), initialPosition = this.#calcPosition(position, clamp(zIndexValue, minZ, container.zLayers));
if (!initialPosition) {
throw new Error("a valid position cannot be found for particle");
}
this.position = initialPosition;
this.initialPosition = this.position.copy();
const canvasSize = container.canvas.size;
this.moveCenter = {
...getPosition(this.options.move.center, canvasSize),
radius: this.options.move.center.radius,
mode: this.options.move.center.mode,
};
this.direction = getParticleDirectionAngle(this.options.move.direction, this.position, this.moveCenter);
switch (this.options.move.direction) {
case exports.MoveDirection.inside:
this.outType = exports.ParticleOutType.inside;
break;
case exports.MoveDirection.outside:
this.outType = exports.ParticleOutType.outside;
break;
}
this.offset = Vector.origin;
}
#normalizeInsideCanvasResult(result, reason) {
if (typeof result === "boolean") {
return {
inside: result,
reason,
};
}
return {
inside: result.inside,
margin: result.margin,
reason: result.reason ?? reason,
};
}
}
class SpatialHashGrid {
#cellSize;
#cells = new Map();
#circlePool = [];
#circlePoolIdx;
#pendingCellSize;
#rectanglePool = [];
#rectanglePoolIdx;
constructor(cellSize) {
this.#cellSize = cellSize;
this.#circlePoolIdx = 0;
this.#rectanglePoolIdx = 0;
}
clear() {
this.#cells.clear();
const pendingCellSize = this.#pendingCellSize;
if (pendingCellSize) {
this.#cellSize = pendingCellSize;
}
this.#pendingCellSize = undefined;
}
insert(particle) {
const { x, y } = particle.getPosition(), key = this.#cellKeyFromCoords(x, y);
if (!this.#cells.has(key)) {
this.#cells.set(key, []);
}
this.#cells.get(key)?.push(particle);
}
query(range, check, out = []) {
const bounds = this.#getRangeBounds(range);
if (!bounds) {
return out;
}
const minCellX = Math.floor(bounds.minX / this.#cellSize), maxCellX = Math.floor(bounds.maxX / this.#cellSize), minCellY = Math.floor(bounds.minY / this.#cellSize), maxCellY = Math.floor(bounds.maxY / this.#cellSize);
for (let cx = minCellX; cx <= maxCellX; cx++) {
for (let cy = minCellY; cy <= maxCellY; cy++) {
const key = `${cx}_${cy}`, cellParticles = this.#cells.get(key);
if (!cellParticles) {
continue;
}
for (const p of cellParticles) {
if (check && !check(p)) {
continue;
}
if (range.contains(p.getPosition())) {
out.push(p);
}
}
}
}
return out;
}
queryCircle(position, radius, check, out = []) {
const circle = this.#acquireCircle(position.x, position.y, radius), result = this.query(circle, check, out);
this.#releaseShapes();
return result;
}
queryRectangle(position, size, check, out = []) {
const rect = this.#acquireRectangle(position.x, position.y, size.width, size.height), result = this.query(rect, check, out);
this.#releaseShapes();
return result;
}
setCellSize(cellSize) {
this.#pendingCellSize = cellSize;
}
#acquireCircle(x, y, r) {
return (this.#circlePool[this.#circlePoolIdx++] ??= new Circle(x, y, r)).reset(x, y, r);
}
#acquireRectangle(x, y, w, h) {
return (this.#rectanglePool[this.#rectanglePoolIdx++] ??= new Rectangle(x, y, w, h)).reset(x, y, w, h);
}
#cellKeyFromCoords(x, y) {
const cellX = Math.floor(x / this.#cellSize), cellY = Math.floor(y / this.#cellSize);
return `${cellX}_${cellY}`;
}
#getRangeBounds(range) {
if (range instanceof Circle) {
const r = range.radius, { x, y } = range.position;
return {
minX: x - r,
maxX: x + r,
minY: y - r,
maxY: y + r,
};
}
if (range instanceof Rectangle) {
const { x, y } = range.position, { width, height } = range.size;
return {
minX: x,
maxX: x + width,
minY: y,
maxY: y + height,
};
}
return null;
}
#releaseShapes() {
this.#circlePoolIdx = 0;
this.#rectanglePoolIdx = 0;
}
}
class ParticlesManager {
checkParticlePositionPlugins;
grid;
#array;
#container;
#groupLimits;
#limit;
#nextId;
#particleBuckets;
#particleResetPlugins;
#particleUpdatePlugins;
#pluginManager;
#pool;
#postParticleUpdatePlugins;
#postUpdatePlugins;
#resizeFactor;
#updatePlugins;
#zBuckets;
constructor(pluginManager, container) {
this.#pluginManager = pluginManager;
this.#container = container;
this.#nextId = 0;
this.#array = [];
this.#pool = [];
this.#limit = 0;
this.#groupLimits = new Map();
this.#particleBuckets = new Map();
this.#zBuckets = this.#createBuckets(this.#container.zLayers);
this.grid = new SpatialHashGrid(spatialHashGridCellSize);
this.checkParticlePositionPlugins = [];
this.#particleResetPlugins = [];
this.#particleUpdatePlugins = [];
this.#postUpdatePlugins = [];
this.#postParticleUpdatePlugins = [];
this.#updatePlugins = [];
}
get count() {
return this.#array.length;
}
addParticle(position, overrideOptions, group, initializer) {
const limitMode = this.#container.actualOptions.particles.number.limit.mode, limit = group === undefined ? this.#limit : (this.#groupLimits.get(group) ?? this.#limit), currentCount = this.count;
if (limit > minLimit) {
switch (limitMode) {
case exports.LimitMode.delete: {
const countToRemove = currentCount + countOffset - limit;
if (countToRemove > minCount) {
this.removeQuantity(countToRemove);
}
break;
}
case exports.LimitMode.wait:
if (currentCount >= limit) {
return;
}
break;
}
}
try {
const particle = this.#pool.pop() ?? new Particle(this.#pluginManager, this.#container);
particle.init(this.#nextId, position, overrideOptions, group);
let canAdd = true;
if (initializer) {
canAdd = initializer(particle);
}
if (!canAdd) {
this.#pool.push(particle);
return;
}
this.#array.push(particle);
this.#insertParticleIntoBucket(particle);
this.#nextId++;
this.#container.dispatchEvent(exports.EventType.particleAdded, {
particle,
});
return particle;
}
catch (e) {
getLogger().warning(`error adding particle: ${e}`);
}
return undefined;
}
clear() {
this.#array = [];
this.#particleBuckets.clear();
this.#resetBuckets(this.#container.zLayers);
}
destroy() {
this.#array = [];
this.#pool.length = 0;
this.#particleBuckets.clear();
this.#zBuckets = [];
this.checkParticlePositionPlugins = [];
this.#particleResetPlugins = [];
this.#particleUpdatePlugins = [];
this.#postUpdatePlugins = [];
this.#postParticleUpdatePlugins = [];
this.#updatePlugins = [];
}
drawParticles(delta) {
for (let i = this.#zBuckets.length - one; i >= minIndex; i--) {
const bucket = this.#zBuckets[i];
if (!bucket) {
continue;
}
for (const particle of bucket) {
particle.draw(delta);
}
}
}
filter(condition) {
return this.#array.filter(condition);
}
find(condition) {
return this.#array.find(condition);
}
get(index) {
return this.#array[index];
}
async init() {
const container = this.#container, options = container.actualOptions;
this.checkParticlePositionPlugins = [];
this.#updatePlugins = [];
this.#particleUpdatePlugins = [];
this.#postUpdatePlugins = [];
this.#particleResetPlugins = [];
this.#postParticleUpdatePlugins = [];
this.#particleBuckets.clear();
this.#resetBuckets(container.zLayers);
this.grid = new SpatialHashGrid(spatialHashGridCellSize * container.retina.pixelRatio);
for (const plugin of container.plugins) {
if (plugin.redrawInit) {
await plugin.redrawInit();
}
if (plugin.checkParticlePosition) {
this.checkParticlePositionPlugins.push(plugin);
}
if (plugin.update) {
this.#updatePlugins.push(plugin);
}
if (plugin.particleUpdate) {
this.#particleUpdatePlugins.push(plugin);
}
if (plugin.postUpdate) {
this.#postUpdatePlugins.push(plugin);
}
if (plugin.particleReset) {
this.#particleResetPlugins.push(plugin);
}
if (plugin.postParticleUpdate) {
this.#postParticleUpdatePlugins.push(plugin);
}
}
await this.#container.initDrawersAndUpdaters();
for (const drawer of this.#container.effectDrawers.values()) {
await drawer.init?.(container);
}
for (const drawer of this.#container.shapeDrawers.values()) {
await drawer.init?.(container);
}
let handled = false;
for (const plugin of container.plugins) {
handled = plugin.particlesInitialization?.() ?? handled;
if (handled) {
break;
}
}
if (!handled) {
const particlesOptions = options.particles, groups = particlesOptions.groups;
for (const group in groups) {
const groupOptions = groups[group];
if (!groupOptions) {
continue;
}
for (let i = this.count, j = 0; j < groupOptions.number.value && i < particlesOptions.number.value; i++, j++) {
this.addParticle(undefined, groupOptions, group);
}
}
for (let i = this.count; i < particlesOptions.number.value; i++) {
this.addParticle();
}
}
}
push(nb, position, overrideOptions, group) {
for (let i = 0; i < nb; i++) {
this.addParticle(position, overrideOptions, group);
}
}
async redraw() {
this.clear();
await this.init();
this.#container.canvas.render.drawParticles({ value: 0, factor: 0 });
}
remove(particle, group, override) {
this.removeAt(this.#array.indexOf(particle), undefined, group, override);
}
removeAt(index, quantity = defaultRemoveQuantity, group, override) {
if (index < minIndex || index > this.count) {
return;
}
let deleted = 0;
for (let i = index; deleted < quantity && i < this.count; i++) {
if (this.#removeParticle(i, group, override)) {
i--;
deleted++;
}
}
}
removeQuantity(quantity, group) {
this.removeAt(minIndex, quantity, group);
}
setDensity() {
const options = this.#container.actualOptions, groups = options.particles.groups;
let pluginsCount = 0;
for (const plugin of this.#container.plugins) {
if (plugin.particlesDensityCount) {
pluginsCount += plugin.particlesDensityCount();
}
}
for (const group in groups) {
const groupData = groups[group];
if (!groupData) {
continue;
}
const groupDataOptions = loadParticlesOptions(this.#pluginManager, this.#container, groupData);
this.#applyDensity(groupDataOptions, pluginsCount, group);
}
this.#applyDensity(options.particles, pluginsCount);
}
setResizeFactor(factor) {
this.#resizeFactor = factor;
}
update(delta) {
this.grid.clear();
for (const plugin of this.#updatePlugins) {
plugin.update?.(delta);
}
const particlesToDelete = this.#updateParticlesPhase1(delta);
for (const plugin of this.#postUpdatePlugins) {
plugin.postUpdate?.(delta);
}
this.#updateParticlesPhase2(delta, particlesToDelete);
if (particlesToDelete.size) {
for (const particle of particlesToDelete) {
this.remove(particle);
}
}
this.#resizeFactor = undefined;
}
#addToPool(...particles) {
this.#pool.push(...particles);
}
#applyDensity(options, pluginsCount, group, groupOptions) {
const numberOptions = options.number;
if (!numberOptions.density.enable) {
if (group === undefined) {
this.#limit = numberOptions.limit.value;
}
else if (groupOptions?.number.limit.value ?? numberOptions.limit.value) {
this.#groupLimits.set(group, groupOptions?.number.limit.value ?? numberOptions.limit.value);
}
return;
}
const densityFactor = this.#initDensityFactor(numberOptions.density), optParticlesNumber = numberOptions.value, optParticlesLimit = numberOptions.limit.value > minLimit ? numberOptions.limit.value : optParticlesNumber, particlesNumber = Math.min(optParticlesNumber, optParticlesLimit) * densityFactor + pluginsCount, particlesCount = Math.min(this.count, this.filter(t => t.group === group).length);
if (group === undefined) {
this.#limit = numberOptions.limit.value * densityFactor;
}
else {
this.#groupLimits.set(group, numberOptions.limit.value * densityFactor);
}
if (particlesCount < particlesNumber) {
this.push(Math.abs(particlesNumber - particlesCount), undefined, options, group);
}
else if (particlesCount > particlesNumber) {
this.removeQuantity(particlesCount - particlesNumber, group);
}
}
#createBuckets(zLayers) {
const bucketCount = Math.max(Math.floor(zLayers), one);
return Array.from({ length: bucketCount }, () => []);
}
#getBucketIndex(zIndex) {
const maxBucketIndex = this.#zBuckets.length - one;
if (maxBucketIndex <= minIndex) {
return minIndex;
}
return Math.min(Math.max(Math.floor(zIndex), minIndex), maxBucketIndex);
}
#initDensityFactor(densityOptions) {
const container = this.#container;
if (!densityOptions.enable) {
return defaultDensityFactor;
}
const canvasSize = container.canvas.size, pxRatio = container.retina.pixelRatio;
if (!canvasSize.width || !canvasSize.height) {
return defaultDensityFactor;
}
return ((canvasSize.width * canvasSize.height) / (densityOptions.height * densityOptions.width * pxRatio ** squareExp));
}
#insertParticleIntoBucket(particle) {
const bucketIndex = this.#getBucketIndex(particle.position.z), bucket = this.#zBuckets[bucketIndex];
if (!bucket) {
return;
}
bucket.push(particle);
this.#particleBuckets.set(particle.id, bucketIndex);
}
#removeParticle(index, group, override) {
const particle = this.#array[index];
if (!particle) {
return false;
}
if (particle.group !== group) {
return false;
}
this.#array.splice(index, deleteCount);
this.#removeParticleFromBucket(particle);
particle.destroy(override);
this.#container.dispatchEvent(exports.EventType.particleRemoved, {
particle,
});
this.#addToPool(particle);
return true;
}
#removeParticleFromBucket(particle) {
const bucketIndex = this.#particleBuckets.get(particle.id) ?? this.#getBucketIndex(particle.position.z), bucket = this.#zBuckets[bucketIndex];
if (!bucket) {
this.#particleBuckets.delete(particle.id);
return;
}
const idx = bucket.findIndex(p => p.id === particle.id);
if (idx >= minIndex) {
bucket.splice(idx, deleteCount);
}
this.#particleBuckets.delete(particle.id);
}
#resetBuckets(zLayers) {
const bucketCount = Math.max(Math.floor(zLayers), one);
if (this.#zBuckets.length !== bucketCount) {
this.#zBuckets = this.#createBuckets(bucketCount);
return;
}
for (const bucket of this.#zBuckets) {
bucket.length = minIndex;
}
}
#updateParticleBucket(particle) {
const newBucketIndex = this.#getBucketIndex(particle.position.z), currentBucketIndex = this.#particleBuckets.get(particle.id);
if (currentBucketIndex === undefined) {
this.#insertParticleIntoBucket(particle);
return;
}
if (currentBucketIndex === newBucketIndex) {
return;
}
const currentBucket = this.#zBuckets[currentBucketIndex];
if (currentBucket) {
const idx = currentBucket.findIndex(p => p.id === particle.id);
if (idx >= minIndex) {
currentBucket.splice(idx, deleteCount);
}
}
const newBucket = this.#zBuckets[newBucketIndex];
if (!newBucket) {
this.#particleBuckets.set(particle.id, newBucketIndex);
return;
}
newBucket.push(particle);
if (newBucket.length >= double) {
const prev = newBucket[newBucket.length - double];
if (prev && particle.id < prev.id) {
newBucket.sort((a, b) => a.id - b.id);
}
}
this.#particleBuckets.set(particle.id, newBucketIndex);
}
#updateParticlesPhase1(delta) {
const particlesToDelete = new Set(), resizeFactor = this.#resizeFactor;
for (const particle of this.#array) {
if (resizeFactor && !particle.ignoresResizeRatio) {
particle.position.x *= resizeFactor.width;
particle.position.y *= resizeFactor.height;
particle.initialPosition.x *= resizeFactor.width;
particle.initialPosition.y *= resizeFactor.height;
}
particle.ignoresResizeRatio = false;
for (const plugin of this.#particleResetPlugins) {
plugin.particleReset?.(particle);
}
for (const plugin of this.#particleUpdatePlugins) {
if (particle.destroyed) {
break;
}
plugin.particleUpdate?.(particle, delta);
}
if (particle.destroyed) {
particlesToDelete.add(particle);
continue;
}
this.grid.insert(particle);
}
return particlesToDelete;
}
#updateParticlesPhase2(delta, particlesToDelete) {
for (const particle of this.#array) {
if (particle.destroyed) {
particlesToDelete.add(particle);
continue;
}
for (const updater of this.#container.particleUpdaters) {
updater.update(particle, delta);
}
if (!particle.spawning) {
for (const plugin of this.#postParticleUpdatePlugins) {
plugin.postParticleUpdate?.(particle, delta);
}
}
this.#updateParticleBucket(particle);
}
}
}
class Retina {
pixelRatio;
reduceFactor;
#container;
constructor(container) {
this.#container = container;
this.pixelRatio = defaultRatio$1;
this.reduceFactor = defaultReduceFactor;
}
init() {
const container = this.#container, options = container.actualOptions;
this.pixelRatio = options.detectRetina ? devicePixelRatio : defaultRatio$1;
this.reduceFactor = defaultReduceFactor;
const ratio = this.pixelRatio, canvas = container.canvas, element = canvas.domElement;
if (element) {
canvas.size.width = element.offsetWidth * ratio;
canvas.size.height = element.offsetHeight * ratio;
}
}
initParticle(particle) {
const options = particle.options, ratio = this.pixelRatio, moveOptions = options.move, moveDistance = moveOptions.distance, props = particle.retina;
props.maxSpeed = getRangeValue(moveOptions.gravity.maxSpeed) * ratio;
props.moveDrift = getRangeValue(moveOptions.drift) * ratio;
props.moveSpeed = getRangeValue(moveOptions.speed) * ratio;
const maxDistance = props.maxDistance;
maxDistance.horizontal = moveDistance.horizontal === undefined ? undefined : moveDistance.horizontal * ratio;
maxDistance.vertical = moveDistance.vertical === undefined ? undefined : moveDistance.vertical * ratio;
}
}
function guardCheck(container) {
return !container.destroyed;
}
function updateDelta(delta, value, fpsLimit = defaultFps, smooth = false) {
delta.value = value;
delta.factor = smooth ? defaultFps / fpsLimit : (defaultFps * value) / millisecondsToSeconds;
}
function loadContainerOptions(pluginManager, container, ...sourceOptionsArr) {
const options = new Options(pluginManager, container);
loadOptions(options, ...sourceOptionsArr);
return options;
}
class Container {
actualOptions;
canvas;
destroyed;
effectDrawers;
fpsLimit;
hdr;
hdrMode;
id;
pageHidden;
particleCreatedPlugins;
particleDestroyedPlugins;
particlePositionPlugins;
particleUpdaters;
particles;
peakNits;
plugins;
retina;
shapeDrawers;
started;
zLayers;
#delay;
#delayTimeout;
#delta = { value: 0, factor: 0 };
#dispatchCallback;
#drawAnimationFrame;
#duration;
#eventListeners;
#firstStart;
#initialSourceOptions;
#lastFrameTime;
#lifeTime;
#onDestroy;
#options;
#paused;
#pluginManager;
#smooth;
#sourceOptions;
constructor(params) {
const { dispatchCallback, pluginManager, id, onDestroy, sourceOptions } = params;
this.#pluginManager = pluginManager;
this.#dispatchCallback = dispatchCallback;
this.#onDestroy = onDestroy;
this.id = Symbol(id);
this.fpsLimit = 120;
this.hdr = false;
this.hdrMode = exports.HdrMode.standard;
this.peakNits = 400;
this.#smooth = false;
this.#delay = 0;
this.#duration = 0;
this.#lifeTime = 0;
this.#firstStart = true;
this.started = false;
this.destroyed = false;
this.#paused = true;
this.#lastFrameTime = 0;
this.zLayers = 100;
this.pageHidden = false;
this.#sourceOptions = sourceOptions;
this.#initialSourceOptions = sourceOptions;
this.effectDrawers = new Map();
this.shapeDrawers = new Map();
this.particleUpdaters = [];
this.retina = new Retina(this);
this.canvas = new CanvasManager(this.#pluginManager, this);
this.particles = new ParticlesManager(this.#pluginManager, this);
this.plugins = [];
this.particleDestroyedPlugins = [];
this.particleCreatedPlugins = [];
this.particlePositionPlugins = [];
this.#options = loadContainerOptions(this.#pluginManager, this);
this.actualOptions = loadContainerOptions(this.#pluginManager, this);
this.#eventListeners = new EventListeners(this);
this.dispatchEvent(exports.EventType.containerBuilt);
}
get animationStatus() {
return !this.#paused && !this.pageHidden && guardCheck(this);
}
get options() {
return this.#options;
}
get sourceOptions() {
return this.#sourceOptions;
}
addLifeTime(value) {
this.#lifeTime += value;
}
alive() {
return !this.#duration || this.#lifeTime <= this.#duration;
}
destroy(remove = true) {
if (!guardCheck(this)) {
return;
}
this.stop();
this.particles.destroy();
this.canvas.destroy();
for (const [, effectDrawer] of this.effectDrawers) {
effectDrawer.destroy?.(this);
}
for (const [, shapeDrawer] of this.shapeDrawers) {
shapeDrawer.destroy?.(this);
}
for (const plugin of this.plugins) {
plugin.destroy?.();
}
this.effectDrawers = new Map();
this.shapeDrawers = new Map();
this.particleUpdaters = [];
this.plugins.length = 0;
this.#pluginManager.clearPlugins(this);
this.destroyed = true;
this.#onDestroy(remove);
this.dispatchEvent(exports.EventType.containerDestroyed);
}
dispatchEvent(type, data) {
this.#dispatchCallback(type, {
container: this,
data,
});
}
draw(force) {
if (!guardCheck(this)) {
return;
}
let refreshTime = force;
this.#drawAnimationFrame = animate((timestamp) => {
if (refreshTime) {
this.#lastFrameTime = undefined;
refreshTime = false;
}
this.#nextFrame(timestamp);
});
}
async export(type, options = {}) {
for (const plugin of this.plugins) {
if (!plugin.export) {
continue;
}
const res = await plugin.export(type, options);
if (!res.supported) {
continue;
}
return res.blob;
}
getLogger().error(`Export plugin with type ${type} not found`);
return undefined;
}
async init() {
if (!guardCheck(this)) {
return;
}
const allContainerPlugins = new Map();
for (const plugin of this.#pluginManager.plugins) {
const containerPlugin = await plugin.getPlugin(this);
if (containerPlugin.preInit) {
await containerPlugin.preInit();
}
allContainerPlugins.set(plugin, containerPlugin);
}
await this.initDrawersAndUpdaters();
this.#options = loadContainerOptions(this.#pluginManager, this, this.#initialSourceOptions, this.sourceOptions);
this.actualOptions = loadContainerOptions(this.#pluginManager, this, this.#options);
this.plugins.length = 0;
this.particleDestroyedPlugins.length = 0;
this.particleCreatedPlugins.length = 0;
this.particlePositionPlugins.length = 0;
for (const [plugin, containerPlugin] of allContainerPlugins) {
if (plugin.needsPlugin(this.actualOptions)) {
this.plugins.push(containerPlugin);
if (containerPlugin.particleCreated) {
this.particleCreatedPlugins.push(containerPlugin);
}
if (containerPlugin.particleDestroyed) {
this.particleDestroyedPlugins.push(containerPlugin);
}
if (containerPlugin.particlePosition) {
this.particlePositionPlugins.push(containerPlugin);
}
}
}
this.retina.init();
this.canvas.init();
this.updateActualOptions();
this.canvas.initBackground();
this.canvas.resize();
const { delay, duration, fpsLimit, smooth, zLayers } = this.actualOptions, hdrOptions = this.actualOptions.hdr;
this.hdrMode = hdrOptions.mode;
this.peakNits = hdrOptions.peakNits;
this.zLayers = zLayers;
this.#duration = getRangeValue(duration) * millisecondsToSeconds;
this.#delay = getRangeValue(delay) * millisecondsToSeconds;
this.#lifeTime = 0;
this.fpsLimit = fpsLimit > minFpsLimit ? fpsLimit : defaultFpsLimit;
this.#smooth = smooth;
for (const plugin of this.plugins) {
await plugin.init?.();
}
await this.particles.init();
this.dispatchEvent(exports.EventType.containerInit);
this.particles.setDensity();
for (const plugin of this.plugins) {
plugin.particlesSetup?.();
}
this.dispatchEvent(exports.EventType.particlesSetup);
}
async initDrawersAndUpdaters() {
const pluginManager = this.#pluginManager;
this.effectDrawers = await pluginManager.getEffectDrawers(this, true);
this.shapeDrawers = await pluginManager.getShapeDrawers(this, true);
this.particleUpdaters = await pluginManager.getUpdaters(this, true);
}
pause() {
if (!guardCheck(this)) {
return;
}
if (this.#drawAnimationFrame !== undefined) {
cancelAnimation(this.#drawAnimationFrame);
this.#drawAnimationFrame = undefined;
}
if (this.#paused) {
return;
}
for (const plugin of this.plugins) {
plugin.pause?.();
}
if (!this.pageHidden) {
this.#paused = true;
}
this.dispatchEvent(exports.EventType.containerPaused);
}
play(force) {
if (!guardCheck(this)) {
return;
}
const needsUpdate = this.#paused || force;
if (this.#firstStart && !this.actualOptions.autoPlay) {
this.#firstStart = false;
return;
}
if (this.#paused) {
this.#paused = false;
}
if (needsUpdate) {
for (const plugin of this.plugins) {
if (plugin.play) {
plugin.play();
}
}
}
this.dispatchEvent(exports.EventType.containerPlay);
this.draw(needsUpdate ?? false);
}
async refresh() {
if (!guardCheck(this)) {
return;
}
this.stop();
return this.start();
}
async reset(sourceOptions) {
if (!guardCheck(this)) {
return;
}
this.#initialSourceOptions = sourceOptions;
this.#sourceOptions = sourceOptions;
this.#options = loadContainerOptions(this.#pluginManager, this, this.#initialSourceOptions, this.sourceOptions);
this.actualOptions = loadContainerOptions(this.#pluginManager, this, this.#options);
return this.refresh();
}
async start() {
if (!guardCheck(this) || this.started) {
return;
}
await this.init();
this.started = true;
await new Promise(resolve => {
const start = async () => {
this.#eventListeners.addListeners();
for (const plugin of this.plugins) {
await plugin.start?.();
}
this.dispatchEvent(exports.EventType.containerStarted);
this.play();
resolve();
};
this.#delayTimeout = setTimeout(() => void start(), this.#delay);
});
}
stop() {
if (!guardCheck(this) || !this.started) {
return;
}
if (this.#delayTimeout) {
clearTimeout(this.#delayTimeout);
this.#delayTimeout = undefined;
}
this.#firstStart = true;
this.started = false;
this.#eventListeners.removeListeners();
this.pause();
this.particles.clear();
this.canvas.stop();
for (const plugin of this.plugins) {
plugin.stop?.();
}
this.particleCreatedPlugins.length = 0;
this.particleDestroyedPlugins.length = 0;
this.particlePositionPlugins.length = 0;
this.#sourceOptions = this.#options;
this.dispatchEvent(exports.EventType.containerStopped);
}
updateActualOptions() {
let refresh = false;
for (const plugin of this.plugins) {
if (plugin.updateActualOptions) {
refresh = plugin.updateActualOptions() || refresh;
}
}
return refresh;
}
#nextFrame(timestamp) {
try {
if (!this.#smooth &&
this.#lastFrameTime !== undefined &&
timestamp < this.#lastFrameTime + millisecondsToSeconds / this.fpsLimit) {
this.draw(false);
return;
}
this.#lastFrameTime ??= timestamp;
updateDelta(this.#delta, timestamp - this.#lastFrameTime, this.fpsLimit, this.#smooth);
this.addLifeTime(this.#delta.value);
this.#lastFrameTime = timestamp;
if (this.#delta.value > millisecondsToSeconds) {
this.draw(false);
return;
}
this.canvas.render.drawParticles(this.#delta);
if (!this.alive()) {
this.destroy();
return;
}
if (this.animationStatus) {
this.draw(false);
}
}
catch (e) {
getLogger().error("error in animation loop", e);
}
}
}
var Container$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Container: Container
});
const touchDelay = 500;
class InteractivityEventListeners {
#canPush = true;
#clickPositionPlugins;
#container;
#handlers;
#interactionManager;
#touches;
constructor(container, interactionManager) {
this.#container = container;
this.#clickPositionPlugins = [];
this.#interactionManager = interactionManager;
this.#touches = new Map();
this.#handlers = {
mouseDown: () => {
this.#mouseDown();
},
mouseLeave: () => {
this.#mouseTouchFinish();
},
mouseMove: (e) => {
this.#mouseTouchMove(e);
},
mouseUp: (e) => {
this.#mouseTouchClick(e);
},
touchStart: (e) => {
this.#touchStart(e);
},
touchMove: (e) => {
this.#mouseTouchMove(e);
},
touchEnd: (e) => {
this.#touchEnd(e);
},
touchCancel: (e) => {
this.#touchEnd(e);
},
touchEndClick: (e) => {
this.#touchEndClick(e);
},
visibilityChange: () => {
this.#handleVisibilityChange();
},
};
}
addListeners() {
this.#manageListeners(true);
}
init() {
this.#clickPositionPlugins.length = 0;
for (const plugin of this.#container.plugins.filter(p => !!p.clickPositionValid)) {
this.#clickPositionPlugins.push(plugin);
}
}
removeListeners() {
this.#manageListeners(false);
}
#doMouseTouchClick(e) {
const container = this.#container, interactionManager = this.#interactionManager, options = container.actualOptions;
if (this.#canPush) {
const mouseInteractivity = interactionManager.interactivityData.mouse, mousePos = mouseInteractivity.position;
if (!mousePos) {
return;
}
mouseInteractivity.clickPosition = { ...mousePos };
mouseInteractivity.clickTime = performance.now();
const onClick = options.interactivity?.events.onClick;
if (!onClick?.mode) {
return;
}
executeOnSingleOrMultiple(onClick.mode, mode => {
interactionManager.handleClickMode(mode);
});
}
if (e.type === "touchend") {
setTimeout(() => {
this.#mouseTouchFinish();
}, touchDelay);
}
}
#handleVisibilityChange() {
this.#mouseTouchFinish();
}
#manageInteractivityListeners(add) {
const handlers = this.#handlers, container = this.#container, interactionManager = this.#interactionManager, options = container.actualOptions, interactivityEl = interactionManager.interactivityData.element;
if (!interactivityEl) {
return;
}
const html = interactivityEl, canvas = container.canvas;
canvas.setPointerEvents(html === canvas.domElement ? "initial" : "none");
if (add && !(options.interactivity?.events.onHover.enable || options.interactivity?.events.onClick.enable)) {
return;
}
manageListener(interactivityEl, mouseMoveEvent, handlers.mouseMove, add);
manageListener(interactivityEl, touchStartEvent, handlers.touchStart, add);
manageListener(interactivityEl, touchMoveEvent, handlers.touchMove, add);
if (add) {
if (options.interactivity?.events.onClick.enable) {
manageListener(interactivityEl, touchEndEvent, handlers.touchEndClick, add);
manageListener(interactivityEl, mouseUpEvent, handlers.mouseUp, add);
manageListener(interactivityEl, mouseDownEvent, handlers.mouseDown, add);
}
else {
manageListener(interactivityEl, touchEndEvent, handlers.touchEnd, add);
}
}
else {
manageListener(interactivityEl, touchEndEvent, handlers.touchEndClick, add);
manageListener(interactivityEl, mouseUpEvent, handlers.mouseUp, add);
manageListener(interactivityEl, mouseDownEvent, handlers.mouseDown, add);
manageListener(interactivityEl, touchEndEvent, handlers.touchEnd, add);
}
manageListener(interactivityEl, mouseLeaveEvent, handlers.mouseLeave, add);
manageListener(interactivityEl, touchCancelEvent, handlers.touchCancel, add);
}
#manageListeners(add) {
const handlers = this.#handlers, container = this.#container, interactionManager = this.#interactionManager, options = container.actualOptions, detectType = options.interactivity?.detectsOn, canvasEl = container.canvas.domElement;
if (detectType === InteractivityDetect.window) {
interactionManager.interactivityData.element = safeDocument();
}
else if (detectType === InteractivityDetect.parent && canvasEl) {
interactionManager.interactivityData.element = canvasEl.parentElement ?? canvasEl.parentNode;
}
else {
interactionManager.interactivityData.element = canvasEl;
}
this.#manageInteractivityListeners(add);
manageListener(document, visibilityChangeEvent, handlers.visibilityChange, add, false);
}
#mouseDown() {
const { interactivityData } = this.#interactionManager, { mouse } = interactivityData;
mouse.clicking = true;
mouse.downPosition = mouse.position;
}
#mouseTouchClick(e) {
const container = this.#container, interactionManager = this.#interactionManager, options = container.actualOptions, { mouse } = interactionManager.interactivityData;
mouse.inside = true;
let handled = false;
const mousePosition = mouse.position;
if (!mousePosition || !options.interactivity?.events.onClick.enable) {
return;
}
for (const plugin of this.#clickPositionPlugins) {
handled = plugin.clickPositionValid?.(mousePosition) ?? false;
if (handled) {
break;
}
}
if (!handled) {
this.#doMouseTouchClick(e);
}
mouse.clicking = false;
}
#mouseTouchFinish() {
const { interactivityData } = this.#interactionManager, { mouse } = interactivityData;
delete mouse.position;
delete mouse.clickPosition;
delete mouse.downPosition;
interactivityData.status = mouseLeaveEvent;
mouse.inside = false;
mouse.clicking = false;
}
#mouseTouchMove(e) {
const container = this.#container, interactionManager = this.#interactionManager, options = container.actualOptions, interactivity = interactionManager.interactivityData, canvasEl = container.canvas.domElement;
if (!interactivity.element) {
return;
}
interactivity.mouse.inside = true;
let pos;
if (e.type.startsWith("pointer")) {
this.#canPush = true;
const mouseEvent = e;
if (interactivity.element === safeDocument()) {
if (canvasEl) {
const clientRect = canvasEl.getBoundingClientRect();
pos = {
x: mouseEvent.clientX - clientRect.left,
y: mouseEvent.clientY - clientRect.top,
};
}
}
else if (options.interactivity?.detectsOn === InteractivityDetect.parent) {
const source = mouseEvent.target, target = mouseEvent.currentTarget;
if (canvasEl) {
const sourceRect = source.getBoundingClientRect(), targetRect = target.getBoundingClientRect(), canvasRect = canvasEl.getBoundingClientRect();
pos = {
x: mouseEvent.offsetX + double * sourceRect.left - (targetRect.left + canvasRect.left),
y: mouseEvent.offsetY + double * sourceRect.top - (targetRect.top + canvasRect.top),
};
}
else {
pos = {
x: mouseEvent.offsetX,
y: mouseEvent.offsetY,
};
}
}
else if (mouseEvent.target === canvasEl) {
pos = {
x: mouseEvent.offsetX,
y: mouseEvent.offsetY,
};
}
}
else {
this.#canPush = e.type !== "touchmove";
if (canvasEl) {
const touchEvent = e, lastTouch = touchEvent.touches[touchEvent.touches.length - lengthOffset], canvasRect = canvasEl.getBoundingClientRect();
if (!lastTouch) {
return;
}
pos = {
x: lastTouch.clientX - canvasRect.left,
y: lastTouch.clientY - canvasRect.top,
};
}
}
const pxRatio = container.retina.pixelRatio;
if (pos) {
pos.x *= pxRatio;
pos.y *= pxRatio;
}
interactivity.mouse.position = pos;
interactivity.status = mouseMoveEvent;
}
#touchEnd(e) {
const evt = e, touches = Array.from(evt.changedTouches);
for (const touch of touches) {
this.#touches.delete(touch.identifier);
}
this.#mouseTouchFinish();
}
#touchEndClick(e) {
const evt = e, touches = Array.from(evt.changedTouches);
for (const touch of touches) {
this.#touches.delete(touch.identifier);
}
this.#mouseTouchClick(e);
}
#touchStart(e) {
const evt = e, touches = Array.from(evt.changedTouches);
for (const touch of touches) {
this.#touches.set(touch.identifier, performance.now());
}
this.#mouseTouchMove(e);
}
}
const clickRadius = 1, touchEndLengthOffset = 1, minCoordinate = 0;
function safeIntersectionObserver(callback) {
if (typeof IntersectionObserver === "undefined") {
return;
}
return new IntersectionObserver(callback);
}
class InteractionManager {
interactivityData;
#clickHandlers;
#container;
#eventListeners;
#externalInteractors;
#interactors;
#intersectionObserver;
#particleInteractors;
#pluginManager;
constructor(pluginManager, container) {
this.#container = container;
this.#pluginManager = pluginManager;
this.#interactors = [];
this.#externalInteractors = [];
this.#particleInteractors = [];
this.#clickHandlers = new Map();
this.#eventListeners = new InteractivityEventListeners(container, this);
this.interactivityData = {
mouse: {
clicking: false,
inside: false,
},
};
this.#intersectionObserver = safeIntersectionObserver(entries => {
this.#intersectionManager(entries);
});
}
addClickHandler(callback) {
const container = this.#container, interactivityData = this.interactivityData;
if (container.destroyed) {
return;
}
const el = interactivityData.element;
if (!el) {
return;
}
const clickOrTouchHandler = (e, pos, radius) => {
if (container.destroyed) {
return;
}
const pxRatio = container.retina.pixelRatio, posRetina = {
x: pos.x * pxRatio,
y: pos.y * pxRatio,
}, particles = container.particles.grid.queryCircle(posRetina, radius * pxRatio);
callback(e, particles);
}, clickHandler = (e) => {
if (container.destroyed) {
return;
}
const mouseEvent = e, pos = {
x: mouseEvent.offsetX,
y: mouseEvent.offsetY,
};
clickOrTouchHandler(e, pos, clickRadius);
}, touchStartHandler = () => {
if (container.destroyed) {
return;
}
touched = true;
touchMoved = false;
}, touchMoveHandler = () => {
if (container.destroyed) {
return;
}
touchMoved = true;
}, touchEndHandler = (e) => {
if (container.destroyed) {
return;
}
if (touched && !touchMoved) {
const touchEvent = e, lastTouch = touchEvent.touches[touchEvent.touches.length - touchEndLengthOffset];
if (!lastTouch) {
return;
}
const element = container.canvas.domElement, canvasRect = element ? element.getBoundingClientRect() : undefined, pos = {
x: lastTouch.clientX - (canvasRect ? canvasRect.left : minCoordinate),
y: lastTouch.clientY - (canvasRect ? canvasRect.top : minCoordinate),
};
clickOrTouchHandler(e, pos, Math.max(lastTouch.radiusX, lastTouch.radiusY));
}
touched = false;
touchMoved = false;
}, touchCancelHandler = () => {
if (container.destroyed) {
return;
}
touched = false;
touchMoved = false;
};
let touched = false, touchMoved = false;
this.#clickHandlers.set(clickEvent, clickHandler);
this.#clickHandlers.set(touchStartEvent, touchStartHandler);
this.#clickHandlers.set(touchMoveEvent, touchMoveHandler);
this.#clickHandlers.set(touchEndEvent, touchEndHandler);
this.#clickHandlers.set(touchCancelEvent, touchCancelHandler);
for (const [key, handler] of this.#clickHandlers) {
el.addEventListener(key, handler);
}
}
addListeners() {
this.#eventListeners.addListeners();
}
clearClickHandlers() {
const container = this.#container, interactivityData = this.interactivityData;
if (container.destroyed) {
return;
}
for (const [key, handler] of this.#clickHandlers) {
interactivityData.element?.removeEventListener(key, handler);
}
this.#clickHandlers.clear();
}
externalInteract(delta) {
for (const interactor of this.#externalInteractors) {
const interactivityData = this.interactivityData;
if (interactor.isEnabled(interactivityData)) {
interactor.interact(interactivityData, delta);
}
}
}
handleClickMode(mode) {
if (this.#container.destroyed) {
return;
}
const interactivityData = this.interactivityData;
for (const interactor of this.#externalInteractors) {
interactor.handleClickMode?.(mode, interactivityData);
}
}
init() {
this.#eventListeners.init();
for (const interactor of this.#interactors) {
switch (interactor.type) {
case InteractorType.external:
this.#externalInteractors.push(interactor);
break;
case InteractorType.particles:
this.#particleInteractors.push(interactor);
break;
}
interactor.init();
}
}
async initInteractors() {
const interactors = await this.#pluginManager.getInteractors?.(this.#container, true);
if (!interactors) {
return;
}
this.#interactors = interactors;
this.#externalInteractors = [];
this.#particleInteractors = [];
}
particlesInteract(particle, delta) {
const interactivityData = this.interactivityData;
for (const interactor of this.#externalInteractors) {
interactor.clear(particle, delta);
}
for (const interactor of this.#particleInteractors) {
if (interactor.isEnabled(particle, interactivityData)) {
interactor.interact(particle, interactivityData, delta);
}
}
}
removeListeners() {
this.#eventListeners.removeListeners();
}
reset(particle) {
const interactivityData = this.interactivityData;
for (const interactor of this.#externalInteractors) {
if (interactor.isEnabled(interactivityData)) {
interactor.reset(interactivityData, particle);
}
}
for (const interactor of this.#particleInteractors) {
if (interactor.isEnabled(particle, interactivityData)) {
interactor.reset(interactivityData, particle);
}
}
}
startObserving() {
const interactivityData = this.interactivityData;
if (interactivityData.element instanceof HTMLElement && this.#intersectionObserver) {
this.#intersectionObserver.observe(interactivityData.element);
}
}
stopObserving() {
const interactivityData = this.interactivityData;
if (interactivityData.element instanceof HTMLElement && this.#intersectionObserver) {
this.#intersectionObserver.unobserve(interactivityData.element);
}
}
updateMaxDistance() {
let maxTotalDistance = 0;
for (const interactor of this.#interactors) {
if (interactor.maxDistance > maxTotalDistance) {
maxTotalDistance = interactor.maxDistance;
}
}
const container = this.#container;
container.particles.grid.setCellSize(maxTotalDistance * container.retina.pixelRatio);
}
#intersectionManager(entries) {
const container = this.#container;
if (container.destroyed || !container.actualOptions.pauseOnOutsideViewport) {
return;
}
for (const entry of entries) {
if (entry.target !== this.interactivityData.element) {
continue;
}
if (entry.isIntersecting) {
container.play();
}
else {
container.pause();
}
}
}
}
class InteractivityPluginInstance {
interactionManager;
#container;
#pluginManager;
constructor(pluginManager, container) {
this.#container = container;
this.#pluginManager = pluginManager;
this.interactionManager = new InteractionManager(pluginManager, container);
this.#container.addClickHandler = (callback) => {
this.interactionManager.addClickHandler(callback);
};
}
addClickHandler(callback) {
this.interactionManager.addClickHandler(callback);
}
clearClickHandlers() {
this.interactionManager.clearClickHandlers();
}
destroy() {
this.clearClickHandlers();
this.#pluginManager.interactors?.delete(this.#container);
}
particleCreated(particle) {
const interactivityParticle = particle, interactivity = new Interactivity(this.#pluginManager, this.#container);
interactivity.load(this.#container.actualOptions.interactivity);
interactivity.load(interactivityParticle.options.interactivity);
interactivityParticle.interactivity = interactivity;
}
particleReset(particle) {
this.interactionManager.reset(particle);
}
postParticleUpdate(particle, delta) {
this.interactionManager.particlesInteract(particle, delta);
}
postUpdate(delta) {
this.interactionManager.externalInteract(delta);
this.interactionManager.updateMaxDistance();
}
async preInit() {
await this.interactionManager.initInteractors();
this.interactionManager.init();
}
async redrawInit() {
await this.interactionManager.initInteractors();
this.interactionManager.init();
}
start() {
this.interactionManager.addListeners();
this.interactionManager.startObserving();
return Promise.resolve();
}
stop() {
this.interactionManager.removeListeners();
this.interactionManager.stopObserving();
}
}
var InteractivityPluginInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
InteractivityPluginInstance: InteractivityPluginInstance
});
const defaultIndex$1 = 0;
class AbsorbersInstancesManager {
#containerArrays;
#pluginManager;
constructor(pluginManager) {
this.#pluginManager = pluginManager;
this.#containerArrays = new Map();
}
async addAbsorber(container, options, position) {
const { AbsorberInstance } = await Promise.resolve().then(function () { return AbsorberInstance$1; }), absorber = new AbsorberInstance(this.#pluginManager, container, options, position), array = this.getArray(container);
array.push(absorber);
return absorber;
}
clear(container) {
this.initContainer(container);
this.#containerArrays.set(container, []);
}
getArray(container) {
this.initContainer(container);
let array = this.#containerArrays.get(container);
if (!array) {
array = [];
this.#containerArrays.set(container, array);
}
return array;
}
initContainer(container) {
if (this.#containerArrays.has(container)) {
return;
}
this.#containerArrays.set(container, []);
container.getAbsorber ??= (idxOrName) => {
const array = this.getArray(container);
return idxOrName === undefined || isNumber(idxOrName)
? array[idxOrName ?? defaultIndex$1]
: array.find(t => t.name === idxOrName);
};
container.addAbsorber ??= (options, position) => {
return this.addAbsorber(container, options, position);
};
}
removeAbsorber(container, absorber) {
const index = this.getArray(container).indexOf(absorber), deleteCount = 1;
if (index >= defaultIndex$1) {
this.getArray(container).splice(index, deleteCount);
}
}
}
var AbsorbersInstancesManager$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
AbsorbersInstancesManager: AbsorbersInstancesManager
});
class AbsorbersPluginInstance {
#container;
#instancesManager;
constructor(container, instancesManager) {
this.#container = container;
this.#instancesManager = instancesManager;
this.#instancesManager.initContainer(container);
}
draw(context) {
for (const absorber of this.#instancesManager.getArray(this.#container)) {
absorber.draw(context);
}
}
async init() {
const absorbers = this.#container.actualOptions.absorbers, promises = executeOnSingleOrMultiple(absorbers, async (absorber) => {
await this.#instancesManager.addAbsorber(this.#container, absorber);
});
if (isArray(promises)) {
await Promise.all(promises);
}
else {
await promises;
}
}
particleUpdate(particle, delta) {
for (const absorber of this.#instancesManager.getArray(this.#container)) {
absorber.attract(particle, delta);
if (particle.destroyed) {
break;
}
}
}
resize() {
for (const absorber of this.#instancesManager.getArray(this.#container)) {
absorber.resize();
}
}
stop() {
this.#instancesManager.clear(this.#container);
}
}
var AbsorbersPluginInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
AbsorbersPluginInstance: AbsorbersPluginInstance
});
const shapeGenerators = new Map();
class ShapeManager {
addShapeGenerator(name, generator) {
if (!this.getShapeGenerator(name)) {
shapeGenerators.set(name, generator);
}
}
getShapeGenerator(name) {
return shapeGenerators.get(name);
}
getSupportedShapeGenerators() {
return shapeGenerators.keys();
}
}
var ShapeManager$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
ShapeManager: ShapeManager
});
const defaultIndex = 0;
class EmittersInstancesManager {
#containerArrays;
#pluginManager;
constructor(pluginManager) {
this.#containerArrays = new Map();
this.#pluginManager = pluginManager;
}
async addEmitter(container, options, position) {
const emitterOptions = new Emitter();
emitterOptions.load(options);
const { EmitterInstance } = await Promise.resolve().then(function () { return EmitterInstance$1; }), emitter = new EmitterInstance(this.#pluginManager, container, (emitter) => {
this.removeEmitter(container, emitter);
}, emitterOptions, position);
await emitter.init();
this.getArray(container).push(emitter);
return emitter;
}
clear(container) {
this.initContainer(container);
this.#containerArrays.set(container, []);
}
getArray(container) {
this.initContainer(container);
let array = this.#containerArrays.get(container);
if (!array) {
array = [];
this.#containerArrays.set(container, array);
}
return array;
}
initContainer(container) {
if (this.#containerArrays.has(container)) {
return;
}
this.#containerArrays.set(container, []);
container.getEmitter = (idxOrName) => {
const array = this.getArray(container);
return idxOrName === undefined || isNumber(idxOrName)
? array[idxOrName ?? defaultIndex]
: array.find(t => t.name === idxOrName);
};
container.addEmitter = async (options, position) => this.addEmitter(container, options, position);
container.removeEmitter = (idxOrName) => {
const emitter = container.getEmitter?.(idxOrName);
if (emitter) {
this.removeEmitter(container, emitter);
}
};
container.playEmitter = (idxOrName) => {
const emitter = container.getEmitter?.(idxOrName);
if (emitter) {
emitter.externalPlay();
}
};
container.pauseEmitter = (idxOrName) => {
const emitter = container.getEmitter?.(idxOrName);
if (emitter) {
emitter.externalPause();
}
};
}
removeEmitter(container, emitter) {
const index = this.getArray(container).indexOf(emitter), minIndex = 0, deleteCount = 1;
if (index >= minIndex) {
this.getArray(container).splice(index, deleteCount);
}
}
}
var EmittersInstancesManager$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
EmittersInstancesManager: EmittersInstancesManager
});
class EmittersPluginInstance {
#container;
#instancesManager;
constructor(instancesManager, container) {
this.#instancesManager = instancesManager;
this.#container = container;
this.#instancesManager.initContainer(container);
}
async init() {
const emittersOptions = this.#container.actualOptions.emitters;
if (isArray(emittersOptions)) {
for (const emitterOptions of emittersOptions) {
await this.#instancesManager.addEmitter(this.#container, emitterOptions);
}
}
else {
await this.#instancesManager.addEmitter(this.#container, emittersOptions);
}
}
pause() {
for (const emitter of this.#instancesManager.getArray(this.#container)) {
emitter.pause();
}
}
play() {
for (const emitter of this.#instancesManager.getArray(this.#container)) {
emitter.play();
}
}
resize() {
for (const emitter of this.#instancesManager.getArray(this.#container)) {
emitter.resize();
}
}
stop() {
this.#instancesManager.clear(this.#container);
}
update(delta) {
this.#instancesManager.getArray(this.#container).forEach(emitter => {
emitter.update(delta);
});
}
}
var EmittersPluginInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
EmittersPluginInstance: EmittersPluginInstance
});
class BlendPluginInstance {
layer = exports.DrawLayer.CanvasSetup;
#container;
#defaultCompositeValue;
constructor(container) {
this.#container = container;
}
drawParticleCleanup(context, particle) {
if (!particle.options.blend?.enable) {
return;
}
context.globalCompositeOperation = particle.originalBlendMode ?? defaultCompositeValue;
particle.originalBlendMode = undefined;
}
drawParticleSetup(context, particle) {
if (!particle.options.blend?.enable) {
return;
}
particle.originalBlendMode = context.globalCompositeOperation;
context.globalCompositeOperation = particle.options.blend.mode;
}
drawSettingsCleanup(context) {
if (!this.#defaultCompositeValue) {
return;
}
context.globalCompositeOperation = this.#defaultCompositeValue;
}
drawSettingsSetup(context) {
const previousComposite = context.globalCompositeOperation, blend = this.#container.actualOptions.blend;
this.#defaultCompositeValue = previousComposite;
context.globalCompositeOperation = blend?.enable ? blend.mode : previousComposite;
}
}
var BlendPluginInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
BlendPluginInstance: BlendPluginInstance
});
const moveSpeedFactor = 60, minSpinRadius = 0, spinFactor = 0.01, defaultPathDelay = 0, noDecay = 1;
function applyDistance(particle) {
const initialPosition = particle.initialPosition, { dx, dy } = getDistances(initialPosition, particle.position), dxFixed = Math.abs(dx), dyFixed = Math.abs(dy), { maxDistance } = particle.retina, hDistance = maxDistance.horizontal, vDistance = maxDistance.vertical;
if (!hDistance && !vDistance) {
return;
}
const hasHDistance = (hDistance && dxFixed >= hDistance) ?? false, hasVDistance = (vDistance && dyFixed >= vDistance) ?? false;
if ((hasHDistance || hasVDistance) && !particle.misplaced) {
particle.misplaced = (!!hDistance && dxFixed > hDistance) || (!!vDistance && dyFixed > vDistance);
if (hDistance) {
particle.velocity.x = particle.velocity.y * half - particle.velocity.x;
}
if (vDistance) {
particle.velocity.y = particle.velocity.x * half - particle.velocity.y;
}
}
else if ((!hDistance || dxFixed < hDistance) && (!vDistance || dyFixed < vDistance) && particle.misplaced) {
particle.misplaced = false;
}
else if (particle.misplaced) {
const pos = particle.position, vel = particle.velocity;
if (hDistance &&
((pos.x < initialPosition.x && vel.x < minVelocity) || (pos.x > initialPosition.x && vel.x > minVelocity))) {
vel.x *= -getRandom();
}
if (vDistance &&
((pos.y < initialPosition.y && vel.y < minVelocity) || (pos.y > initialPosition.y && vel.y > minVelocity))) {
vel.y *= -getRandom();
}
}
}
function move(particle, moveOptions, moveSpeed, maxSpeed, moveDrift, reduceFactor, delta) {
applyPath(particle, delta);
const gravityOptions = particle.gravity, gravityFactor = gravityOptions?.enable && gravityOptions.inverse ? -identity$2 : identity$2;
if (moveDrift && moveSpeed) {
particle.velocity.x += (moveDrift * delta.factor) / (moveSpeedFactor * moveSpeed);
}
if (gravityOptions?.enable && moveSpeed) {
particle.velocity.y +=
(gravityFactor * (gravityOptions.acceleration * delta.factor)) / (moveSpeedFactor * moveSpeed);
}
const decay = particle.moveDecay;
particle.velocity.multTo(decay ?? noDecay);
const velocity = particle.velocity.mult(moveSpeed);
if (gravityOptions?.enable &&
maxSpeed > minVelocity &&
((!gravityOptions.inverse && velocity.y >= minVelocity && velocity.y >= maxSpeed) ||
(gravityOptions.inverse && velocity.y <= minVelocity && velocity.y <= -maxSpeed))) {
velocity.y = gravityFactor * maxSpeed;
if (moveSpeed) {
particle.velocity.y = velocity.y / moveSpeed;
}
}
const zIndexOptions = particle.options.zIndex, zVelocityFactor = (identity$2 - particle.zIndexFactor) ** zIndexOptions.velocityRate;
velocity.multTo(zVelocityFactor);
velocity.multTo(reduceFactor);
const { position } = particle;
position.addTo(velocity);
if (moveOptions.vibrate) {
position.x += Math.sin(position.x * Math.cos(position.y)) * reduceFactor;
position.y += Math.cos(position.y * Math.sin(position.x)) * reduceFactor;
}
}
function spin(container, particle, moveSpeed, reduceFactor) {
if (!particle.spin) {
return;
}
const spinClockwise = particle.spin.direction === exports.RotateDirection.clockwise, updateFunc = {
x: spinClockwise ? Math.cos : Math.sin,
y: spinClockwise ? Math.sin : Math.cos,
};
particle.position.x =
particle.spin.center.x + particle.spin.radius * updateFunc.x(particle.spin.angle) * reduceFactor;
particle.position.y =
particle.spin.center.y + particle.spin.radius * updateFunc.y(particle.spin.angle) * reduceFactor;
particle.spin.radius += particle.spin.acceleration * reduceFactor;
const maxCanvasSize = Math.max(container.canvas.size.width, container.canvas.size.height), halfMaxSize = maxCanvasSize * half;
if (particle.spin.radius > halfMaxSize) {
particle.spin.radius = halfMaxSize;
particle.spin.acceleration *= -identity$2;
}
else if (particle.spin.radius < minSpinRadius) {
particle.spin.radius = minSpinRadius;
particle.spin.acceleration *= -identity$2;
}
particle.spin.angle += moveSpeed * spinFactor * (identity$2 - particle.spin.radius / maxCanvasSize);
}
function applyPath(particle, delta) {
const particlesOptions = particle.options, pathOptions = particlesOptions.move.path, pathEnabled = pathOptions.enable;
if (!pathEnabled) {
return;
}
const pathDelay = particle.pathDelay ?? defaultPathDelay;
if (particle.lastPathTime <= pathDelay) {
particle.lastPathTime += delta.value;
return;
}
const path = particle.pathGenerator?.generate(particle, delta);
if (path) {
particle.velocity.addTo(path);
}
if (pathOptions.clamp) {
particle.velocity.x = clamp(particle.velocity.x, -identity$2, identity$2);
particle.velocity.y = clamp(particle.velocity.y, -identity$2, identity$2);
}
particle.lastPathTime -= pathDelay;
}
function getProximitySpeedFactor(particle) {
const mod = particle.getModifier("slow");
return mod?.enabled ? (mod.speedFactor ?? identity$2) : identity$2;
}
function initSpin(container, particle) {
const options = particle.options, spinOptions = options.move.spin;
if (!spinOptions.enable) {
return;
}
const spinPos = spinOptions.position ?? { x: 50, y: 50 }, spinFactor = 0.01, spinCenter = {
x: spinPos.x * spinFactor * container.canvas.size.width,
y: spinPos.y * spinFactor * container.canvas.size.height,
}, pos = particle.getPosition(), distance = getDistance(pos, spinCenter), spinAcceleration = getRangeValue(spinOptions.acceleration);
particle.retina.spinAcceleration = spinAcceleration * container.retina.pixelRatio;
particle.spin = {
center: spinCenter,
direction: particle.velocity.x >= minVelocity ? exports.RotateDirection.clockwise : exports.RotateDirection.counterClockwise,
angle: getRandom() * doublePI,
radius: distance,
acceleration: particle.retina.spinAcceleration,
};
}
const defaultSizeFactor = 1, defaultDeltaFactor = 1;
class MovePluginInstance {
availablePathGenerators;
pathGenerators;
#container;
#pluginManager;
constructor(pluginManager, container) {
this.#pluginManager = pluginManager;
this.#container = container;
this.availablePathGenerators = new Map();
this.pathGenerators = new Map();
}
destroy() {
this.availablePathGenerators = new Map();
this.pathGenerators = new Map();
}
isEnabled(particle) {
return !particle.destroyed && particle.options.move.enable;
}
particleCreated(particle) {
const options = particle.options, moveOptions = options.move, gravityOptions = moveOptions.gravity, pathOptions = moveOptions.path;
particle.moveDecay = decayOffset - getRangeValue(moveOptions.decay);
particle.pathDelay = getRangeValue(pathOptions.delay.value) * millisecondsToSeconds;
if (pathOptions.generator) {
let pathGenerator = this.pathGenerators.get(pathOptions.generator);
if (!pathGenerator) {
pathGenerator = this.availablePathGenerators.get(pathOptions.generator);
if (pathGenerator) {
this.pathGenerators.set(pathOptions.generator, pathGenerator);
pathGenerator.init();
}
}
particle.pathGenerator = pathGenerator;
}
particle.gravity = {
enable: gravityOptions.enable,
acceleration: getRangeValue(gravityOptions.acceleration),
inverse: gravityOptions.inverse,
};
initSpin(this.#container, particle);
}
particleDestroyed(particle) {
const pathGenerator = particle.pathGenerator;
pathGenerator?.reset(particle);
}
particleUpdate(particle, delta) {
const particleOptions = particle.options, moveOptions = particleOptions.move;
if (!moveOptions.enable) {
return;
}
const container = this.#container, slowFactor = getProximitySpeedFactor(particle), reduceFactor = container.retina.reduceFactor, baseSpeed = particle.retina.moveSpeed, moveDrift = particle.retina.moveDrift, maxSize = particle.size.max, sizeFactor = moveOptions.size ? particle.getRadius() / maxSize : defaultSizeFactor, deltaFactor = delta.factor || defaultDeltaFactor, moveSpeed = baseSpeed * sizeFactor * slowFactor * deltaFactor * half, maxSpeed = particle.retina.maxSpeed;
if (moveOptions.spin.enable) {
spin(container, particle, moveSpeed, reduceFactor);
}
else {
move(particle, moveOptions, moveSpeed, maxSpeed, moveDrift, reduceFactor, delta);
}
applyDistance(particle);
}
preInit() {
return this.#init();
}
redrawInit() {
return this.#init();
}
update() {
for (const pathGenerator of this.pathGenerators.values()) {
pathGenerator.update();
}
}
async #init() {
const availablePathGenerators = await this.#pluginManager.getPathGenerators?.(this.#container, true);
if (!availablePathGenerators) {
return;
}
this.availablePathGenerators = availablePathGenerators;
this.pathGenerators = new Map();
for (const pathGenerator of this.pathGenerators.values()) {
pathGenerator.init();
}
}
}
var MovePluginInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
MovePluginInstance: MovePluginInstance
});
class ImagePreloaderInstance {
#container;
#engine;
constructor(engine, container) {
this.#engine = engine;
this.#container = container;
}
destroy() {
this.#engine.images?.delete(this.#container);
}
}
var ImagePreloaderInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
ImagePreloaderInstance: ImagePreloaderInstance
});
const minRetries = 0;
class OverlapPluginInstance {
#container;
constructor(container) {
this.#container = container;
}
checkParticlePosition(particle, position, tryCount) {
return !this.#hasOverlaps(particle, position, tryCount);
}
#hasOverlaps(particle, pos, tryCount) {
const collisionsOptions = particle.options.collisions;
if (!collisionsOptions?.enable) {
return false;
}
const overlapOptions = collisionsOptions.overlap;
if (overlapOptions.enable) {
return false;
}
const retries = overlapOptions.retries;
if (retries >= minRetries && tryCount > retries) {
throw new Error(`Particle is overlapping and can't be placed`);
}
return !!this.#container.particles.find(p => getDistance(pos, p.position) < particle.getRadius() + p.getRadius());
}
}
var OverlapPluginInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
OverlapPluginInstance: OverlapPluginInstance
});
function getLinkKey(ids) {
return [...ids].sort((a, b) => a - b).join("_");
}
function setLinkFrequency(particles, dictionary) {
const key = getLinkKey(particles.map(t => t.id));
let res = dictionary.get(key);
if (res === undefined) {
res = getRandom();
dictionary.set(key, res);
}
return res;
}
const minOpacity = 0, minWidth = 0, minDistance = 0, maxFrequency = 1, defaultFrequency = 0;
class LinkInstance {
#colorCache = new Map();
#container;
#freqs;
#pluginManager;
constructor(pluginManager, container) {
this.#pluginManager = pluginManager;
this.#container = container;
this.#freqs = { links: new Map(), triangles: new Map() };
}
drawParticle(context, particle) {
const { links, options } = particle;
if (!links?.length || !options.links) {
return;
}
const linkOpts = options.links, width = particle.retina.linksWidth ?? minWidth, pos1 = particle.getPosition(), twinkle = particle.options["twinkle"]?.links, trianglesEnabled = linkOpts.triangles.enable, p1Destinations = trianglesEnabled ? new Set(links.map(l => l.destination.id)) : null, originalAlpha = context.globalAlpha;
let currentColorStyle = "", currentWidth = -1, currentAlpha = -1, pathOpen = false;
const flushLines = () => {
if (pathOpen) {
context.stroke();
pathOpen = false;
}
};
for (const link of links) {
if (linkOpts.frequency < maxFrequency &&
this.#getLinkFrequency(particle, link.destination) > linkOpts.frequency) {
continue;
}
const pos2 = link.destination.getPosition();
if (trianglesEnabled && !link.isWarped && p1Destinations) {
flushLines();
this.#drawTriangles(options, particle, link, p1Destinations, pos1, pos2, context);
}
if (link.opacity <= minOpacity || width <= minWidth) {
continue;
}
if (!linkOpts.enable) {
continue;
}
let opacity = link.opacity, colorLine = link.color;
const twinkleRgb = twinkle?.enable && getRandom() < twinkle.frequency
? rangeColorToRgb(this.#pluginManager, twinkle.color)
: undefined;
if (twinkle && twinkleRgb) {
colorLine = twinkleRgb;
opacity = getRangeValue(twinkle.opacity);
}
if (!colorLine) {
const linkColor = linkOpts.id !== undefined
? this.#container.particles.linksColors.get(linkOpts.id)
: this.#container.particles.linksColor;
colorLine = getLinkColor(particle, link.destination, linkColor);
}
if (!colorLine) {
continue;
}
const colorStyle = this.#getCachedStyle(colorLine);
if (colorStyle !== currentColorStyle || width !== currentWidth || opacity !== currentAlpha) {
flushLines();
context.strokeStyle = colorStyle;
context.lineWidth = width;
context.globalAlpha = opacity;
currentColorStyle = colorStyle;
currentWidth = width;
currentAlpha = opacity;
context.beginPath();
pathOpen = true;
}
if (link.isWarped) {
const canvasSize = this.#container.canvas.size, dx = pos2.x - pos1.x, dy = pos2.y - pos1.y;
let sx = originPoint.x, sy = originPoint.y;
if (Math.abs(dx) > canvasSize.width * half) {
sx = dx > minDistance ? -canvasSize.width : canvasSize.width;
}
if (Math.abs(dy) > canvasSize.height * half) {
sy = dy > minDistance ? -canvasSize.height : canvasSize.height;
}
context.moveTo(pos1.x, pos1.y);
context.lineTo(pos2.x + sx, pos2.y + sy);
context.moveTo(pos1.x - sx, pos1.y - sy);
context.lineTo(pos2.x, pos2.y);
}
else {
context.moveTo(pos1.x, pos1.y);
context.lineTo(pos2.x, pos2.y);
}
}
flushLines();
context.globalAlpha = originalAlpha;
}
init() {
this.#freqs.links.clear();
this.#freqs.triangles.clear();
this.#colorCache.clear();
return Promise.resolve();
}
particleCreated(particle) {
particle.links = [];
if (!particle.options.links) {
return;
}
particle.linksDistance = particle.options.links.distance;
particle.linksWidth = particle.options.links.width;
const ratio = this.#container.retina.pixelRatio;
particle.retina.linksDistance = particle.linksDistance * ratio;
particle.retina.linksWidth = particle.linksWidth * ratio;
}
particleDestroyed(particle) {
particle.links = [];
}
#drawTriangles(options, p1, link, p1Destinations, pos1, pos2, context) {
const p2 = link.destination, triangleOptions = options.links?.triangles;
if (!triangleOptions?.enable || !p2.options.links?.triangles.enable) {
return;
}
const p2Links = p2.links;
if (!p2Links?.length) {
return;
}
for (const vertex of p2Links) {
if (vertex.isWarped ||
this.#getLinkFrequency(p2, vertex.destination) > p2.options.links.frequency ||
!p1Destinations.has(vertex.destination.id)) {
continue;
}
const p3 = vertex.destination;
if (this.#getTriangleFrequency(p1, p2, p3) > (options.links?.triangles.frequency ?? defaultFrequency)) {
continue;
}
const opacityTriangle = triangleOptions.opacity ?? (link.opacity + vertex.opacity) * half, colorTriangle = rangeColorToRgb(this.#pluginManager, triangleOptions.color) ?? link.color;
if (!colorTriangle || opacityTriangle <= minOpacity) {
continue;
}
const pos3 = p3.getPosition();
context.save();
context.fillStyle = this.#getCachedStyle(colorTriangle);
context.globalAlpha = opacityTriangle;
context.beginPath();
context.moveTo(pos1.x, pos1.y);
context.lineTo(pos2.x, pos2.y);
context.lineTo(pos3.x, pos3.y);
context.closePath();
context.fill();
context.restore();
}
}
#getCachedStyle(rgb) {
const key = `${rgb.r},${rgb.g},${rgb.b}-${this.#container.hdr}-${this.#container.peakNits}-${this.#container.hdrMode}`;
let style = this.#colorCache.get(key);
if (!style) {
style = getStyleFromRgb(rgb, this.#container.hdr, undefined, this.#container.peakNits, this.#container.hdrMode);
this.#colorCache.set(key, style);
}
return style;
}
#getLinkFrequency(p1, p2) {
return setLinkFrequency([p1, p2], this.#freqs.links);
}
#getTriangleFrequency(p1, p2, p3) {
return setLinkFrequency([p1, p2, p3], this.#freqs.triangles);
}
}
var LinkInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
LinkInstance: LinkInstance
});
const absorbFactor = 0.033, minOrbitLength = 0, minRadius = 0, minMass = 0, minAngle = 0, maxAngle = doublePI, maxDegreeAngle = 360, angleIncrementFactor = identity$2 / maxDegreeAngle, defaultLifeDelay$1 = 0, minLifeCount$1 = 0, defaultSpawnDelay$1 = 0, defaultLifeCount$1 = -1;
class AbsorberInstance {
color;
limit;
mass;
name;
opacity;
options;
position;
size;
#container;
#currentDuration;
#currentSpawnDelay;
#duration;
#firstSpawn;
#immortal;
#initialPosition;
#lifeCount;
#pluginManager;
#spawnDelay;
constructor(pluginManager, container, options, position) {
this.#container = container;
this.#pluginManager = pluginManager;
this.#currentDuration = 0;
this.#currentSpawnDelay = 0;
this.#initialPosition = position ? Vector.create(position.x, position.y) : undefined;
if (options instanceof Absorber) {
this.options = options;
}
else {
this.options = new Absorber();
this.options.load(options);
}
this.name = this.options.name;
this.opacity = this.options.opacity;
this.size = getRangeValue(this.options.size.value) * container.retina.pixelRatio;
this.mass = this.size * this.options.size.density * container.retina.reduceFactor;
const limit = this.options.size.limit;
this.limit = {
radius: limit.radius * container.retina.pixelRatio * container.retina.reduceFactor,
mass: limit.mass,
};
this.color = rangeColorToRgb(this.#pluginManager, this.options.color) ?? {
b: 0,
g: 0,
r: 0,
};
this.position = this.#initialPosition?.copy() ?? this.#calcPosition();
this.#firstSpawn = !this.options.life.wait;
this.#lifeCount = this.options.life.count ?? defaultLifeCount$1;
this.#immortal = this.#lifeCount <= minLifeCount$1;
this.#spawnDelay = container.retina.reduceFactor
? (getRangeValue(this.options.life.delay ?? defaultLifeDelay$1) * millisecondsToSeconds) /
container.retina.reduceFactor
: Infinity;
}
attract(particle, delta) {
const container = this.#container, options = this.options, pos = particle.getPosition(), { dx, dy, distance } = getDistances(this.position, pos), v = Vector.create(dx, dy);
v.length = (this.mass / Math.pow(distance, squareExp)) * container.retina.reduceFactor;
if (distance < this.size + particle.getRadius()) {
const sizeFactor = particle.getRadius() * absorbFactor * container.retina.pixelRatio * delta.factor;
if ((this.size > particle.getRadius() && distance < this.size - particle.getRadius()) ||
(particle.absorberOrbit !== undefined && particle.absorberOrbit.length < minOrbitLength)) {
if (options.destroy) {
particle.destroy();
}
else {
particle.needsNewPosition = true;
this.#updateParticlePosition(particle, delta, v);
}
}
else {
if (options.destroy) {
particle.size.value -= sizeFactor;
}
this.#updateParticlePosition(particle, delta, v);
}
if (this.limit.radius <= minRadius || this.size < this.limit.radius) {
this.size += sizeFactor;
}
if (this.limit.mass <= minMass || this.mass < this.limit.mass) {
this.mass += sizeFactor * this.options.size.density * container.retina.reduceFactor;
}
}
else {
this.#updateParticlePosition(particle, delta, v);
}
}
draw(context) {
context.translate(this.position.x, this.position.y);
context.beginPath();
context.arc(originPoint.x, originPoint.y, this.size, minAngle, maxAngle, false);
context.closePath();
context.fillStyle = getStyleFromRgb(this.color, this.#container.hdr, this.opacity, this.#container.peakNits, this.#container.hdrMode);
context.fill();
}
resize() {
const initialPosition = this.#initialPosition;
this.position =
initialPosition && isPointInside(initialPosition, this.#container.canvas.size, Vector.origin)
? initialPosition
: this.#calcPosition();
}
update(delta) {
if (this.#firstSpawn) {
this.#firstSpawn = false;
this.#currentSpawnDelay = this.#spawnDelay ?? defaultSpawnDelay$1;
}
if (this.#duration !== undefined) {
this.#currentDuration += delta.value;
if (this.#currentDuration >= this.#duration) {
if (!this.#immortal) {
this.#lifeCount--;
}
if (this.#lifeCount > minLifeCount$1 || this.#immortal) {
this.position = this.#calcPosition();
this.#spawnDelay = this.#container.retina.reduceFactor
? (getRangeValue(this.options.life.delay ?? defaultLifeDelay$1) * millisecondsToSeconds) /
this.#container.retina.reduceFactor
: Infinity;
}
this.#currentDuration -= this.#duration;
this.#duration = undefined;
}
}
if (this.#spawnDelay !== undefined) {
this.#currentSpawnDelay += delta.value;
if (this.#currentSpawnDelay >= this.#spawnDelay) {
this.#play();
this.#currentSpawnDelay -= this.#spawnDelay;
this.#spawnDelay = undefined;
}
}
}
#calcPosition() {
const exactPosition = calcPositionOrRandomFromSizeRanged({
size: this.#container.canvas.size,
position: this.options.position,
});
return Vector.create(exactPosition.x, exactPosition.y);
}
#play() {
if (!((this.#lifeCount > minLifeCount$1 || this.#immortal || !this.options.life.count) &&
(this.#firstSpawn || this.#currentSpawnDelay >= (this.#spawnDelay ?? defaultSpawnDelay$1)))) {
return;
}
if (this.#lifeCount > minLifeCount$1 || this.#immortal) {
this.#prepareToDie();
}
}
#prepareToDie() {
const duration = this.options.life.duration !== undefined ? getRangeValue(this.options.life.duration) : undefined, minDuration = 0;
if ((this.#lifeCount > minLifeCount$1 || this.#immortal) && duration !== undefined && duration > minDuration) {
this.#duration = duration * millisecondsToSeconds;
}
}
#updateParticlePosition(particle, delta, v) {
if (particle.destroyed) {
return;
}
const container = this.#container, canvasSize = container.canvas.size;
if (particle.needsNewPosition) {
const newPosition = calcPositionOrRandomFromSize({ size: canvasSize });
particle.position.setTo(newPosition);
particle.velocity.setTo(particle.initialVelocity);
particle.absorberOrbit = undefined;
particle.needsNewPosition = false;
}
if (this.options.orbits) {
if (particle.absorberOrbit === undefined) {
particle.absorberOrbit = Vector.origin;
particle.absorberOrbit.length = getDistance(particle.getPosition(), this.position);
particle.absorberOrbit.angle = getRandom() * maxAngle;
}
if (particle.absorberOrbit.length <= this.size && !this.options.destroy) {
const minSize = Math.min(canvasSize.width, canvasSize.height), offset = 1, randomOffset = 0.1, randomFactor = 0.2;
particle.absorberOrbit.length = minSize * (offset + (getRandom() * randomFactor - randomOffset));
}
particle.absorberOrbitDirection ??=
particle.velocity.x >= minVelocity ? exports.RotateDirection.clockwise : exports.RotateDirection.counterClockwise;
const orbitRadius = particle.absorberOrbit.length, orbitAngle = particle.absorberOrbit.angle, orbitDirection = particle.absorberOrbitDirection;
particle.velocity.setTo(Vector.origin);
const maxSize = particle.size.max, sizeFactor = particle.options.move.size ? particle.getRadius() / maxSize : identity$2, deltaFactor = delta.factor || identity$2, baseSpeed = particle.retina.moveSpeed, moveSpeed = baseSpeed * sizeFactor * deltaFactor * half;
particle.position.x = this.position.x + orbitRadius * Math.cos(orbitAngle);
particle.position.y =
this.position.y +
orbitRadius * (orbitDirection === exports.RotateDirection.clockwise ? identity$2 : -identity$2) * Math.sin(orbitAngle);
particle.absorberOrbit.length = Math.max(minOrbitLength, particle.absorberOrbit.length - v.length);
particle.absorberOrbit.angle += moveSpeed * angleIncrementFactor * container.retina.reduceFactor;
}
else {
particle.velocity.addTo(v);
}
}
}
var AbsorberInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
AbsorberInstance: AbsorberInstance
});
const defaultLifeDelay = 0, minLifeCount = 0, defaultSpawnDelay = 0, defaultEmitDelay = 0, defaultLifeCount = -1, defaultColorAnimationFactor = 1, colorFactor = 3.6, defaultStrokeWidth = 1;
function setParticlesOptionsFillColor(particlesOptions, color, opacity, enable) {
const paint = (particlesOptions.paint ??= new Paint());
paint.fill = {
color: AnimatableColor.create(undefined, { value: color }),
enable,
opacity,
};
}
function setParticlesOptionsStrokeColor(particlesOptions, color, opacity, width) {
const paint = (particlesOptions.paint ??= new Paint());
paint.stroke = {
color: AnimatableColor.create(undefined, { value: color }),
opacity,
width,
};
}
class EmitterInstance {
fill;
name;
options;
position;
size;
spawnFillColor;
spawnFillEnabled;
spawnFillOpacity;
spawnStrokeColor;
spawnStrokeOpacity;
spawnStrokeWidth;
#container;
#currentDuration;
#currentEmitDelay;
#currentSpawnDelay;
#duration;
#emitDelay;
#firstSpawn;
#immortal;
#initialPosition;
#lifeCount;
#mutationObserver;
#particlesOptions;
#paused;
#pluginManager;
#removeCallback;
#resizeObserver;
#shape;
#size;
#spawnDelay;
#startParticlesAdded;
constructor(pluginManager, container, removeCallback, options, position) {
this.#pluginManager = pluginManager;
this.#container = container;
this.#removeCallback = removeCallback;
this.#currentDuration = 0;
this.#currentEmitDelay = 0;
this.#currentSpawnDelay = 0;
this.#initialPosition = position;
if (options instanceof Emitter) {
this.options = options;
}
else {
this.options = new Emitter();
this.options.load(options);
}
this.#spawnDelay = container.retina.reduceFactor
? (getRangeValue(this.options.life.delay ?? defaultLifeDelay) * millisecondsToSeconds) /
container.retina.reduceFactor
: Infinity;
this.position = this.#initialPosition ?? this.#calcPosition();
this.name = this.options.name;
this.fill = this.options.fill;
this.#firstSpawn = !this.options.life.wait;
this.#startParticlesAdded = false;
const particlesOptions = deepExtend({}, this.options.particles);
particlesOptions.move ??= {};
particlesOptions.move.direction ??= this.options.direction;
if (this.options.spawn.fill?.color) {
this.spawnFillColor = rangeColorToHsl(this.#pluginManager, this.options.spawn.fill.color);
}
if (this.options.spawn.stroke?.color) {
this.spawnStrokeColor = rangeColorToHsl(this.#pluginManager, this.options.spawn.stroke.color);
}
this.#paused = !this.options.autoPlay;
this.#particlesOptions = particlesOptions;
this.#size = this.#calcSize();
this.size =
this.#size.mode === exports.PixelMode.percent
? {
width: (this.#size.width / percentDenominator) * this.#container.canvas.size.width,
height: (this.#size.height / percentDenominator) * this.#container.canvas.size.height,
}
: { width: this.#size.width, height: this.#size.height };
this.#lifeCount = this.options.life.count ?? defaultLifeCount;
this.#immortal = this.#lifeCount <= minLifeCount;
if (this.options.domId) {
const element = safeDocument().getElementById(this.options.domId);
if (element) {
this.#mutationObserver = new MutationObserver(() => {
this.resize();
});
this.#resizeObserver = new ResizeObserver(() => {
this.resize();
});
this.#mutationObserver.observe(element, {
attributes: true,
attributeFilter: ["style", "width", "height"],
});
this.#resizeObserver.observe(element);
}
}
const shapeOptions = this.options.shape, shapeGenerator = this.#pluginManager.emitterShapeManager?.getShapeGenerator(shapeOptions.type);
if (shapeGenerator) {
this.#shape = shapeGenerator.generate(this.#container, this.position, this.size, this.fill, shapeOptions.options);
}
this.#container.dispatchEvent("emitterCreated", {
emitter: this,
});
this.play();
}
externalPause() {
this.#paused = true;
this.pause();
}
externalPlay() {
this.#paused = false;
this.play();
}
async init() {
await this.#shape?.init();
}
pause() {
if (this.#paused) {
return;
}
this.#emitDelay = undefined;
}
play() {
if (this.#paused) {
return;
}
if (!((this.#lifeCount > minLifeCount || this.#immortal || !this.options.life.count) &&
(this.#firstSpawn || this.#currentSpawnDelay >= (this.#spawnDelay ?? defaultSpawnDelay)))) {
return;
}
const container = this.#container;
if (this.#emitDelay === undefined) {
const delay = getRangeValue(this.options.rate.delay);
this.#emitDelay = container.retina.reduceFactor
? (delay * millisecondsToSeconds) / container.retina.reduceFactor
: Infinity;
}
if (this.#lifeCount > minLifeCount || this.#immortal) {
this.#prepareToDie();
}
}
resize() {
const initialPosition = this.#initialPosition, container = this.#container;
this.position =
initialPosition && isPointInside(initialPosition, container.canvas.size, Vector.origin)
? initialPosition
: this.#calcPosition();
this.#size = this.#calcSize();
this.size =
this.#size.mode === exports.PixelMode.percent
? {
width: (this.#size.width / percentDenominator) * container.canvas.size.width,
height: (this.#size.height / percentDenominator) * container.canvas.size.height,
}
: { width: this.#size.width, height: this.#size.height };
this.#shape?.resize(this.position, this.size);
}
update(delta) {
if (this.#paused) {
return;
}
const container = this.#container;
if (this.#firstSpawn) {
this.#firstSpawn = false;
this.#currentSpawnDelay = this.#spawnDelay ?? defaultSpawnDelay;
this.#currentEmitDelay = this.#emitDelay ?? defaultEmitDelay;
}
if (!this.#startParticlesAdded) {
this.#startParticlesAdded = true;
this.#emitParticles(this.options.startCount);
}
if (this.#duration !== undefined) {
this.#currentDuration += delta.value;
if (this.#currentDuration >= this.#duration) {
this.pause();
if (this.#spawnDelay !== undefined) {
this.#spawnDelay = undefined;
}
if (!this.#immortal) {
this.#lifeCount--;
}
if (this.#lifeCount > minLifeCount || this.#immortal) {
this.position = this.#calcPosition();
this.#shape?.resize(this.position, this.size);
this.#spawnDelay = container.retina.reduceFactor
? (getRangeValue(this.options.life.delay ?? defaultLifeDelay) * millisecondsToSeconds) /
container.retina.reduceFactor
: Infinity;
}
else {
this.#destroy();
}
this.#currentDuration -= this.#duration;
this.#duration = undefined;
}
}
if (this.#spawnDelay !== undefined) {
this.#currentSpawnDelay += delta.value;
if (this.#currentSpawnDelay >= this.#spawnDelay) {
this.#container.dispatchEvent("emitterPlay");
this.play();
this.#currentSpawnDelay -= this.#spawnDelay;
this.#spawnDelay = undefined;
}
}
if (this.#emitDelay !== undefined) {
this.#currentEmitDelay += delta.value;
if (this.#currentEmitDelay >= this.#emitDelay) {
this.#emit();
this.#currentEmitDelay -= this.#emitDelay;
}
}
}
#calcPosition() {
const container = this.#container;
if (this.options.domId) {
const element = safeDocument().getElementById(this.options.domId);
if (element) {
const elRect = element.getBoundingClientRect(), pxRatio = container.retina.pixelRatio;
return {
x: (elRect.x + elRect.width * half) * pxRatio,
y: (elRect.y + elRect.height * half) * pxRatio,
};
}
}
return calcPositionOrRandomFromSizeRanged({
size: container.canvas.size,
position: this.options.position,
});
}
#calcSize() {
const container = this.#container;
if (this.options.domId) {
const element = safeDocument().getElementById(this.options.domId);
if (element) {
const elRect = element.getBoundingClientRect();
return {
width: elRect.width * container.retina.pixelRatio,
height: elRect.height * container.retina.pixelRatio,
mode: exports.PixelMode.precise,
};
}
}
return (this.options.size ??
(() => {
const size = new EmitterSize();
size.load({
height: 0,
mode: exports.PixelMode.percent,
width: 0,
});
return size;
})());
}
#destroy() {
this.#mutationObserver?.disconnect();
this.#mutationObserver = undefined;
this.#resizeObserver?.disconnect();
this.#resizeObserver = undefined;
this.#removeCallback(this);
this.#container.dispatchEvent("emitterDestroyed", {
emitter: this,
});
}
#emit() {
if (this.#paused) {
return;
}
const quantity = getRangeValue(this.options.rate.quantity);
this.#emitParticles(quantity);
}
#emitParticles(quantity) {
const singleParticlesOptions = (itemFromSingleOrMultiple(this.#particlesOptions) ??
{}), fillHslAnimation = this.options.spawn.fill?.color?.animation, fillEnabled = this.options.spawn.fill?.enable ?? !!this.options.spawn.fill?.color, fillOpacity = this.options.spawn.fill?.opacity === undefined
? defaultOpacity$3
: getRangeValue(this.options.spawn.fill.opacity), strokeHslAnimation = this.options.spawn.stroke?.color?.animation, strokeOpacity = this.options.spawn.stroke?.opacity === undefined
? defaultOpacity$3
: getRangeValue(this.options.spawn.stroke.opacity), strokeWidth = this.options.spawn.stroke?.width === undefined
? defaultStrokeWidth
: getRangeValue(this.options.spawn.stroke.width), reduceFactor = this.#container.retina.reduceFactor, needsFillColorAnimation = !!fillHslAnimation, needsStrokeColorAnimation = !!strokeHslAnimation, needsShapeData = !!this.#shape, needsColorAnimation = needsFillColorAnimation || needsStrokeColorAnimation, needsCopy = needsColorAnimation || needsShapeData, maxValues = needsColorAnimation ? { h: hMax, s: sMax, l: lMax } : null, shapeOptions = this.options.shape;
for (let i = 0; i < quantity * reduceFactor; i++) {
const particlesOptions = needsCopy
? deepExtend({}, singleParticlesOptions)
: singleParticlesOptions;
this.spawnFillOpacity = fillOpacity;
this.spawnFillEnabled = fillEnabled;
this.spawnStrokeOpacity = strokeOpacity;
this.spawnStrokeWidth = strokeWidth;
if (this.spawnFillColor) {
if (fillHslAnimation && maxValues) {
this.spawnFillColor.h = this.#setColorAnimation(fillHslAnimation.h, this.spawnFillColor.h, maxValues.h, colorFactor);
this.spawnFillColor.s = this.#setColorAnimation(fillHslAnimation.s, this.spawnFillColor.s, maxValues.s);
this.spawnFillColor.l = this.#setColorAnimation(fillHslAnimation.l, this.spawnFillColor.l, maxValues.l);
}
setParticlesOptionsFillColor(particlesOptions, this.spawnFillColor, this.spawnFillOpacity, this.spawnFillEnabled);
}
if (this.spawnStrokeColor) {
if (strokeHslAnimation && maxValues) {
this.spawnStrokeColor.h = this.#setColorAnimation(strokeHslAnimation.h, this.spawnStrokeColor.h, maxValues.h, colorFactor);
this.spawnStrokeColor.s = this.#setColorAnimation(strokeHslAnimation.s, this.spawnStrokeColor.s, maxValues.s);
this.spawnStrokeColor.l = this.#setColorAnimation(strokeHslAnimation.l, this.spawnStrokeColor.l, maxValues.l);
}
setParticlesOptionsStrokeColor(particlesOptions, this.spawnStrokeColor, this.spawnStrokeOpacity, this.spawnStrokeWidth);
}
let position = this.position;
if (this.#shape) {
const shapePosData = this.#shape.randomPosition();
if (shapePosData) {
position = shapePosData.position;
const replaceData = shapeOptions.replace;
if (replaceData.color && shapePosData.color) {
setParticlesOptionsFillColor(particlesOptions, shapePosData.color, replaceData.opacity ? (shapePosData.opacity ?? defaultOpacity$3) : defaultOpacity$3, true);
}
}
else {
position = null;
}
}
if (position) {
this.#container.particles.addParticle(position, particlesOptions);
}
}
}
#prepareToDie() {
if (this.#paused) {
return;
}
const duration = this.options.life.duration !== undefined ? getRangeValue(this.options.life.duration) : undefined, minDuration = 0, minLifeCount = 0;
if ((this.#lifeCount > minLifeCount || this.#immortal) && duration !== undefined && duration > minDuration) {
this.#duration = duration * millisecondsToSeconds;
}
}
#setColorAnimation(animation, initValue, maxValue, factor = defaultColorAnimationFactor) {
const container = this.#container;
if (!animation.enable) {
return initValue;
}
const colorOffset = randomInRangeValue(animation.offset), delay = getRangeValue(this.options.rate.delay), emitFactor = container.retina.reduceFactor
? (delay * millisecondsToSeconds) / container.retina.reduceFactor
: Infinity, colorSpeed = getRangeValue(animation.speed);
return (initValue + (colorSpeed * container.fpsLimit) / emitFactor + colorOffset * factor) % maxValue;
}
}
var EmitterInstance$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
EmitterInstance: EmitterInstance
});
exports.AnimatableColor = AnimatableColor;
exports.AnimationOptions = AnimationOptions;
exports.AnimationValueWithRandom = AnimationValueWithRandom;
exports.Background = Background;
exports.BaseRange = BaseRange;
exports.Circle = Circle;
exports.ColorAnimation = ColorAnimation;
exports.Fill = Fill;
exports.FullScreen = FullScreen;
exports.HDROptions = HDROptions;
exports.HslAnimation = HslAnimation;
exports.Move = Move;
exports.MoveAngle = MoveAngle;
exports.MoveCenter = MoveCenter;
exports.MoveGravity = MoveGravity;
exports.MovePath = MovePath;
exports.OptionLoader = OptionLoader;
exports.Options = Options;
exports.OptionsColor = OptionsColor;
exports.OutModes = OutModes;
exports.Paint = Paint;
exports.ParticlesBounce = ParticlesBounce;
exports.ParticlesBounceFactor = ParticlesBounceFactor;
exports.ParticlesDensity = ParticlesDensity;
exports.ParticlesNumber = ParticlesNumber;
exports.ParticlesNumberLimit = ParticlesNumberLimit;
exports.ParticlesOptions = ParticlesOptions;
exports.RangedAnimationOptions = RangedAnimationOptions;
exports.RangedAnimationValueWithRandom = RangedAnimationValueWithRandom;
exports.Rectangle = Rectangle;
exports.ResizeEvent = ResizeEvent;
exports.Shape = Shape;
exports.Spin = Spin;
exports.Stroke = Stroke;
exports.ValueWithRandom = ValueWithRandom;
exports.Vector = Vector;
exports.Vector3d = Vector3d;
exports.ZIndex = ZIndex;
exports.alterHsl = alterHsl;
exports.animate = animate;
exports.areBoundsInside = areBoundsInside;
exports.calcExactPositionOrRandomFromSize = calcExactPositionOrRandomFromSize;
exports.calcPositionFromSize = calcPositionFromSize;
exports.calcPositionOrRandomFromSize = calcPositionOrRandomFromSize;
exports.calcPositionOrRandomFromSizeRanged = calcPositionOrRandomFromSizeRanged;
exports.calculateBounds = calculateBounds;
exports.cancelAnimation = cancelAnimation;
exports.canvasFirstIndex = canvasFirstIndex;
exports.canvasTag = canvasTag;
exports.checkDistance = checkDistance;
exports.circleBounce = circleBounce;
exports.circleBounceDataFromParticle = circleBounceDataFromParticle;
exports.clamp = clamp;
exports.cloneStyle = cloneStyle;
exports.collisionVelocity = collisionVelocity;
exports.colorMix = colorMix;
exports.colorToRgb = colorToRgb;
exports.countOffset = countOffset;
exports.decayOffset = decayOffset;
exports.deepExtend = deepExtend;
exports.defaultAlpha = defaultAlpha$1;
exports.defaultAngle = defaultAngle;
exports.defaultCompositeValue = defaultCompositeValue;
exports.defaultDensityFactor = defaultDensityFactor;
exports.defaultFps = defaultFps;
exports.defaultFpsLimit = defaultFpsLimit;
exports.defaultLoops = defaultLoops;
exports.defaultOpacity = defaultOpacity$3;
exports.defaultRatio = defaultRatio$1;
exports.defaultReduceFactor = defaultReduceFactor;
exports.defaultRemoveQuantity = defaultRemoveQuantity;
exports.defaultRetryCount = defaultRetryCount;
exports.defaultRgbMin = defaultRgbMin;
exports.defaultTime = defaultTime;
exports.defaultTransform = defaultTransform;
exports.defaultTransformValue = defaultTransformValue;
exports.defaultVelocity = defaultVelocity;
exports.defaultZoom = defaultZoom;
exports.degToRad = degToRad;
exports.deleteCount = deleteCount;
exports.double = double;
exports.doublePI = doublePI;
exports.empty = empty;
exports.executeOnSingleOrMultiple = executeOnSingleOrMultiple;
exports.generatedAttribute = generatedAttribute;
exports.generatedFalse = generatedFalse;
exports.generatedTrue = generatedTrue;
exports.getDistance = getDistance;
exports.getDistanceSq = getDistanceSq;
exports.getDistances = getDistances;
exports.getFullScreenStyle = getFullScreenStyle;
exports.getHslAnimationFromHsl = getHslAnimationFromHsl;
exports.getHslFromAnimation = getHslFromAnimation;
exports.getItemMapFromInitializer = getItemMapFromInitializer;
exports.getItemsFromInitializer = getItemsFromInitializer;
exports.getLinkColor = getLinkColor;
exports.getLinkRandomColor = getLinkRandomColor;
exports.getLogger = getLogger;
exports.getParticleBaseVelocity = getParticleBaseVelocity;
exports.getParticleDirectionAngle = getParticleDirectionAngle;
exports.getPosition = getPosition;
exports.getRandom = getRandom;
exports.getRandomFn = getRandomFn;
exports.getRandomInRange = getRandomInRange;
exports.getRandomRgbColor = getRandomRgbColor;
exports.getRangeMax = getRangeMax;
exports.getRangeMin = getRangeMin;
exports.getRangeValue = getRangeValue;
exports.getStyleFromHsl = getStyleFromHsl;
exports.getStyleFromRgb = getStyleFromRgb;
exports.hMax = hMax;
exports.hMin = hMin;
exports.hPhase = hPhase;
exports.half = half;
exports.hslToRgb = hslToRgb;
exports.hslToRgbFloat = hslToRgbFloat;
exports.hslaToRgba = hslaToRgba;
exports.identity = identity$2;
exports.inverseFactorNumerator = inverseFactorNumerator;
exports.isArray = isArray;
exports.isBoolean = isBoolean;
exports.isFunction = isFunction;
exports.isInArray = isInArray;
exports.isNull = isNull;
exports.isNumber = isNumber;
exports.isObject = isObject;
exports.isPointInside = isPointInside;
exports.isString = isString;
exports.itemFromArray = itemFromArray;
exports.itemFromSingleOrMultiple = itemFromSingleOrMultiple;
exports.lFactor = lFactor;
exports.lMax = lMax;
exports.lMin = lMin;
exports.lengthOffset = lengthOffset;
exports.loadExtendProperty = loadExtendProperty;
exports.loadFull = loadFull;
exports.loadLazyProperty = loadLazyProperty;
exports.loadMinIndex = loadMinIndex;
exports.loadNestedProperty = loadNestedProperty;
exports.loadOptionProperty = loadOptionProperty;
exports.loadOptions = loadOptions;
exports.loadParticlesOptions = loadParticlesOptions;
exports.loadProperty = loadProperty;
exports.loadRandomFactor = loadRandomFactor;
exports.loadRangeProperty = loadRangeProperty;
exports.manageListener = manageListener;
exports.maxNits = maxNits;
exports.midColorValue = midColorValue;
exports.millisecondsToSeconds = millisecondsToSeconds;
exports.minCount = minCount;
exports.minDistance = minDistance$6;
exports.minFpsLimit = minFpsLimit;
exports.minIndex = minIndex;
exports.minLimit = minLimit;
exports.minRadius = minRadius$5;
exports.minStrokeWidth = minStrokeWidth;
exports.minVelocity = minVelocity;
exports.minZ = minZ;
exports.minimumSize = minimumSize;
exports.mix = mix;
exports.none = none;
exports.one = one;
exports.originPoint = originPoint;
exports.parseAlpha = parseAlpha;
exports.percentDenominator = percentDenominator;
exports.phaseNumerator = phaseNumerator;
exports.quarter = quarter;
exports.randomColorValue = randomColorValue;
exports.randomInRangeValue = randomInRangeValue;
exports.rangeColorToHsl = rangeColorToHsl;
exports.rangeColorToRgb = rangeColorToRgb;
exports.removeDeleteCount = removeDeleteCount;
exports.removeMinIndex = removeMinIndex;
exports.resizeEvent = resizeEvent;
exports.rgbMax = rgbMax;
exports.rgbToHsl = rgbToHsl;
exports.sMax = sMax;
exports.sMin = sMin;
exports.sNormalizedOffset = sNormalizedOffset;
exports.safeDocument = safeDocument;
exports.safeMatchMedia = safeMatchMedia;
exports.safeMutationObserver = safeMutationObserver;
exports.setAnimationFunctions = setAnimationFunctions;
exports.setLogger = setLogger;
exports.setRandom = setRandom;
exports.setRangeValue = setRangeValue;
exports.sextuple = sextuple;
exports.spatialHashGridCellSize = spatialHashGridCellSize;
exports.squareExp = squareExp;
exports.stringToAlpha = stringToAlpha;
exports.stringToRgb = stringToRgb;
exports.threeQuarter = threeQuarter;
exports.triple = triple;
exports.tryCountIncrement = tryCountIncrement;
exports.tsParticles = tsParticles;
exports.updateColor = updateColor;
exports.updateColorValue = updateColorValue;
exports.visibilityChangeEvent = visibilityChangeEvent;
exports.zIndexFactorOffset = zIndexFactorOffset;
}));
Object.assign(globalThis.window || globalThis, { loadExtendProperty: (globalThis.__tsParticlesInternals.bundles.full || {}).loadExtendProperty, loadFull: (globalThis.__tsParticlesInternals.bundles.full || {}).loadFull, loadLazyProperty: (globalThis.__tsParticlesInternals.bundles.full || {}).loadLazyProperty, loadMinIndex: (globalThis.__tsParticlesInternals.bundles.full || {}).loadMinIndex, loadNestedProperty: (globalThis.__tsParticlesInternals.bundles.full || {}).loadNestedProperty, loadOptionProperty: (globalThis.__tsParticlesInternals.bundles.full || {}).loadOptionProperty, loadOptions: (globalThis.__tsParticlesInternals.bundles.full || {}).loadOptions, loadParticlesOptions: (globalThis.__tsParticlesInternals.bundles.full || {}).loadParticlesOptions, loadProperty: (globalThis.__tsParticlesInternals.bundles.full || {}).loadProperty, loadRandomFactor: (globalThis.__tsParticlesInternals.bundles.full || {}).loadRandomFactor, loadRangeProperty: (globalThis.__tsParticlesInternals.bundles.full || {}).loadRangeProperty, tsParticles: (globalThis.__tsParticlesInternals.bundles.full || {}).tsParticles });
globalThis.__tsParticlesInternals = globalThis.__tsParticlesInternals || {};
if (!globalThis.__tsParticlesInternals.engine || !globalThis.__tsParticlesInternals.engine.tsParticles) {
globalThis.__tsParticlesInternals.engine = globalThis.__tsParticlesInternals.bundles.full || {};
}
delete (globalThis.window || globalThis).tsparticlesInternalExports;