@tsparticles/engine
Version:
Easily create highly customizable particle, confetti and fireworks animations and use them as animated backgrounds for your website. Ready to use components available also for React, Vue.js (2.x and 3.x), Angular, Svelte, jQuery, Preact, Riot.js, Inferno.
4,995 lines • 200 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 Engine 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.engine = global.__tsParticlesInternals.engine || {})));
})(this, (function (exports) { 'use strict';
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;
}
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 = {}));
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, generatedTrue = "true", generatedFalse = "false", canvasTag = "canvas", defaultRetryCount = 0, squareExp = 2, spatialHashGridCellSize = 100, defaultRemoveQuantity = 1, defaultRatio = 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 = 0, minRadius$1 = 0, defaultTransformValue = 1, minimumSize = 0, zIndexFactorOffset = 1, defaultOpacity = 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 = 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;
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 = 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), 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 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, 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, 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).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).toString()})`;
}
function getStyleFromHsl(color, hdr, opacity, peakNits, mode) {
const hslPrecision = hdr ? hdrHslFixedPrecision : hslFixedPrecision, op = opacity ?? defaultOpacity, 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();
const globalObject = globalThis;
globalObject.__tsParticlesInternals = globalObject.__tsParticlesInternals ?? {};
globalObject.tsParticles = tsParticles;
globalObject.getParticlesLogger = getLogger;
globalObject.setParticlesLogger = setLogger;
globalObject.getParticlesRandom = getRandom;
globalObject.getParticlesRandomFn = getRandomFn;
globalObject.setParticlesRandom = setRandom;
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,
opacity: defaultOpacity,
strokeOpacity: defaultOpacity,
};
#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), modifierOpacity = this.#applyModifiers(undefined, m => m.opacity), opacity = modifierOpacity ?? baseOpacity, fillOpacity = this.fillOpacity ?? defaultOpacity, strokeOpacity = this.strokeOpacity ?? defaultOpacity;
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)
: (externalTransform.b ?? defaultTransform.b);
this.#cachedTransform.c = rotating
? -rotateData.sin * (externalTransform.c ?? identity)
: (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);
}
}
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;
this.reduceFactor = defaultReduceFactor;
}
init() {
const container = this.#container, options = container.actualOptions;
this.pixelRatio = options.detectRetina ? devicePixelRatio : defaultRatio;
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
});
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;
exports.defaultAngle = defaultAngle;
exports.defaultCompositeValue = defaultCompositeValue;
exports.defaultDensityFactor = defaultDensityFactor;
exports.defaultFps = defaultFps;
exports.defaultFpsLimit = defaultFpsLimit;
exports.defaultLoops = defaultLoops;
exports.defaultOpacity = defaultOpacity;
exports.defaultRatio = defaultRatio;
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;
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.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;
exports.minFpsLimit = minFpsLimit;
exports.minIndex = minIndex;
exports.minLimit = minLimit;
exports.minRadius = minRadius$1;
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, { tsParticles: (globalThis.__tsParticlesInternals.engine || {}).tsParticles });
delete (globalThis.window || globalThis).tsparticlesInternalExports;